# HG changeset patch # User Patrick Mezard # Date 1200167347 -3600 # Node ID deb0d35186744a9e85f47cb32faeb65ac5f85c88 # Parent a763957136910253513dee1147ecb9744fb68217# Parent 03ce5a919ae36ac5622562f5b3d06945c99d0340 Merge with crew-stable diff -r 03ce5a919ae3 -r deb0d3518674 .hgignore --- a/.hgignore Sat Jan 12 19:35:11 2008 +0100 +++ b/.hgignore Sat Jan 12 20:49:07 2008 +0100 @@ -22,8 +22,10 @@ MANIFEST patches mercurial/__version__.py +Output/Mercurial-*.exe .DS_Store +tags +cscope.* syntax: regexp ^\.pc/ -Output/Mercurial-[0-9.]*.exe diff -r 03ce5a919ae3 -r deb0d3518674 CONTRIBUTORS --- a/CONTRIBUTORS Sat Jan 12 19:35:11 2008 +0100 +++ b/CONTRIBUTORS Sat Jan 12 20:49:07 2008 +0100 @@ -1,4 +1,7 @@ -Andrea Arcangeli +[This file is here for historical purposes, all recent contributors +should appear in the changelog directly] + +Andrea Arcangeli Thomas Arendsen Hein Goffredo Baroncelli Muli Ben-Yehuda @@ -36,5 +39,3 @@ Rafael Villar Burke Tristan Wibberley Mark Williamson - -If you are a contributor and don't see your name here, please let me know. diff -r 03ce5a919ae3 -r deb0d3518674 contrib/bash_completion --- a/contrib/bash_completion Sat Jan 12 19:35:11 2008 +0100 +++ b/contrib/bash_completion Sat Jan 12 20:49:07 2008 +0100 @@ -305,6 +305,15 @@ _hg_ext_mq_patchlist qunapplied } +_hg_cmd_qgoto() +{ + if [[ "$prev" = @(-n|--name) ]]; then + _hg_ext_mq_queues + return + fi + _hg_ext_mq_patchlist qseries +} + _hg_cmd_qdelete() { local qcmd=qunapplied diff -r 03ce5a919ae3 -r deb0d3518674 contrib/churn.py --- a/contrib/churn.py Sat Jan 12 19:35:11 2008 +0100 +++ b/contrib/churn.py Sat Jan 12 20:49:07 2008 +0100 @@ -125,6 +125,7 @@ ui.note("rev %d: %d lines by %s\n" % (rev, lines, who)) if progress: + nr_revs = max(nr_revs, 1) if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs): ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),)) sys.stdout.flush() @@ -144,6 +145,7 @@ return s[0:l] def graph(n, maximum, width, char): + maximum = max(1, maximum) n = int(n * width / float(maximum)) return char * (n) @@ -178,6 +180,8 @@ ordered = stats.items() ordered.sort(lambda x, y: cmp(y[1], x[1])) + if not ordered: + return maximum = ordered[0][1] width = get_tty_width() diff -r 03ce5a919ae3 -r deb0d3518674 contrib/hgk --- a/contrib/hgk Sat Jan 12 19:35:11 2008 +0100 +++ b/contrib/hgk Sat Jan 12 20:49:07 2008 +0100 @@ -649,7 +649,7 @@ if {$stuffsaved} return if {![winfo viewable .]} return catch { - set f [open "~/.gitk-new" w] + set f [open "~/.hgk-new" w] puts $f [list set mainfont $mainfont] puts $f [list set curidfont $curidfont] puts $f [list set textfont $textfont] @@ -687,7 +687,7 @@ puts $f "#" puts $f "set authorcolors {$authorcolors}" close $f - file rename -force "~/.gitk-new" "~/.gitk" + file rename -force "~/.hgk-new" "~/.hgk" } set stuffsaved 1 } @@ -3844,10 +3844,10 @@ set colors {green red blue magenta darkgrey brown orange} set authorcolors { - deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey + black blue deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey } -catch {source ~/.gitk} +catch {source ~/.hgk} if {$curidfont == ""} { # initialize late based on current mainfont set curidfont "$mainfont bold italic underline" diff -r 03ce5a919ae3 -r deb0d3518674 contrib/win32/ReadMe.html --- a/contrib/win32/ReadMe.html Sat Jan 12 19:35:11 2008 +0100 +++ b/contrib/win32/ReadMe.html Sat Jan 12 20:49:07 2008 +0100 @@ -33,7 +33,7 @@ href="http://hgbook.red-bean.com/">Distributed revision control with Mercurial.

-

By default, Mercurial installs to C:\Mercurial. The +

By default, Mercurial installs to C:\Program Files\Mercurial. The Mercurial command is called hg.exe.

Testing Mercurial after you've installed it

diff -r 03ce5a919ae3 -r deb0d3518674 contrib/win32/mercurial.iss --- a/contrib/win32/mercurial.iss Sat Jan 12 19:35:11 2008 +0100 +++ b/contrib/win32/mercurial.iss Sat Jan 12 20:49:07 2008 +0100 @@ -15,8 +15,8 @@ AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3} AppContact=mercurial@selenic.com OutputBaseFilename=Mercurial-snapshot -DefaultDirName={sd}\Mercurial -SourceDir=C:\hg\hg-release +DefaultDirName={pf}\Mercurial +SourceDir=..\.. VersionInfoDescription=Mercurial distributed SCM VersionInfoCopyright=Copyright 2005-2007 Matt Mackall and others VersionInfoCompany=Matt Mackall and others @@ -29,17 +29,17 @@ [Files] Source: contrib\mercurial.el; DestDir: {app}/Contrib +Source: contrib\vim\*.*; DestDir: {app}/Contrib/Vim +Source: contrib\zsh_completion; DestDir: {app}/Contrib Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt Source: dist\hg.exe; DestDir: {app}; AfterInstall: Touch('{app}\hg.exe.local') Source: dist\library.zip; DestDir: {app} -Source: dist\patch.exe; DestDir: {app} Source: dist\mfc71.dll; DestDir: {app} Source: dist\msvcr71.dll; DestDir: {app} Source: dist\w9xpopen.exe; DestDir: {app} Source: dist\add_path.exe; DestDir: {app} -Source: doc\*.txt; DestDir: {app}\Docs Source: doc\*.html; DestDir: {app}\Docs Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt diff -r 03ce5a919ae3 -r deb0d3518674 doc/hg.1.txt --- a/doc/hg.1.txt Sat Jan 12 19:35:11 2008 +0100 +++ b/doc/hg.1.txt Sat Jan 12 20:49:07 2008 +0100 @@ -91,11 +91,11 @@ FILES ----- - .hgignore:: + repo/.hgignore:: This file contains regular expressions (one per line) that describe file names that should be ignored by hg. For details, see hgignore(5). - .hgtags:: + repo/.hgtags:: This file contains changeset hash values and text tag names (one of each separated by spaces) that correspond to tagged versions of the repository contents. diff -r 03ce5a919ae3 -r deb0d3518674 doc/hgrc.5.txt --- a/doc/hgrc.5.txt Sat Jan 12 19:35:11 2008 +0100 +++ b/doc/hgrc.5.txt Sat Jan 12 20:49:07 2008 +0100 @@ -17,7 +17,9 @@ Mercurial reads configuration data from several files, if they exist. The names of these files depend on the system on which Mercurial is -installed. +installed. Windows registry keys contain PATH-like strings, every +part must reference a Mercurial.ini file or be a directory where *.rc +files will be read. (Unix) /etc/mercurial/hgrc.d/*.rc:: (Unix) /etc/mercurial/hgrc:: @@ -29,6 +31,8 @@ (Unix) /etc/mercurial/hgrc.d/*.rc:: (Unix) /etc/mercurial/hgrc:: +(Windows) HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial:: + or:: (Windows) C:\Mercurial\Mercurial.ini:: Per-system configuration files, for the system on which Mercurial is running. Options in these files apply to all Mercurial @@ -120,21 +124,26 @@ NOTE: the tempfile mechanism is recommended for Windows systems, where the standard shell I/O redirection operators often have - strange effects. In particular, if you are doing line ending - conversion on Windows using the popular dos2unix and unix2dos - programs, you *must* use the tempfile mechanism, as using pipes will - corrupt the contents of your files. + strange effects and may corrupt the contents of your files. - Tempfile example: + The most common usage is for LF <-> CRLF translation on Windows. + For this, use the "smart" convertors which check for binary files: + [extensions] + hgext.win32text = [encode] - # convert files to unix line ending conventions on checkin - **.txt = tempfile: dos2unix -n INFILE OUTFILE - + ** = cleverencode: [decode] - # convert files to windows line ending conventions when writing - # them to the working dir - **.txt = tempfile: unix2dos -n INFILE OUTFILE + ** = cleverdecode: + + or if you only want to translate certain files: + + [extensions] + hgext.win32text = + [encode] + **.txt = dumbencode: + [decode] + **.txt = dumbdecode: defaults:: Use the [defaults] section to define command defaults, i.e. the @@ -277,7 +286,7 @@ commit to proceed. Non-zero status will cause the commit to fail. Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2. preoutgoing;; - Run before computing changes to send from the local repository to + Run before collecting changes to send from the local repository to another. Non-zero status will cause failure. This lets you prevent pull over http or ssh. Also prevents against local pull, push (outbound) or bundle commands, but not effective, since you @@ -515,7 +524,7 @@ Example: "http://hgserver/repos/" contact;; Name or email address of the person in charge of the repository. - Default is "unknown". + Defaults to ui.username or $EMAIL or "unknown" if unset or empty. deny_push;; Whether to deny pushing to the repository. If empty or not set, push is not denied. If the special value "*", all remote users diff -r 03ce5a919ae3 -r deb0d3518674 hg --- a/hg Sat Jan 12 19:35:11 2008 +0100 +++ b/hg Sat Jan 12 20:49:07 2008 +0100 @@ -10,5 +10,11 @@ # enable importing on demand to reduce startup time from mercurial import demandimport; demandimport.enable() +import sys +import mercurial.util import mercurial.dispatch + +for fp in (sys.stdin, sys.stdout, sys.stderr): + mercurial.util.set_binary(fp) + mercurial.dispatch.run() diff -r 03ce5a919ae3 -r deb0d3518674 hgext/color.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/color.py Sat Jan 12 20:49:07 2008 +0100 @@ -0,0 +1,219 @@ +# color.py color output for the status and qseries commands +# +# Copyright (C) 2007 Kevin Christen +# +# 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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +'''add color output to the status and qseries commands + +This extension modifies the status command to add color to its output to +reflect file status, and the qseries command to add color to reflect patch +status (applied, unapplied, missing). Other effects in addition to color, +like bold and underlined text, are also available. Effects are rendered +with the ECMA-48 SGR control function (aka ANSI escape codes). This module +also provides the render_text function, which can be used to add effects to +any text. + +To enable this extension, add this to your .hgrc file: +[extensions] +color = + +Default effects my be overriden from the .hgrc file: + +[color] +status.modified = blue bold underline red_background +status.added = green bold +status.removed = red bold blue_background +status.deleted = cyan bold underline +status.unknown = magenta bold underline +status.ignored = black bold + + 'none' turns off all effects +status.clean = none +status.copied = none + +qseries.applied = blue bold underline +qseries.unapplied = black bold +qseries.missing = red bold +''' + +import re, sys + +from mercurial import commands, cmdutil, ui +from mercurial.i18n import _ + +# start and stop parameters for effects +_effect_params = { 'none': (0, 0), + 'black': (30, 39), + 'red': (31, 39), + 'green': (32, 39), + 'yellow': (33, 39), + 'blue': (34, 39), + 'magenta': (35, 39), + 'cyan': (36, 39), + 'white': (37, 39), + 'bold': (1, 22), + 'italic': (3, 23), + 'underline': (4, 24), + 'inverse': (7, 27), + 'black_background': (40, 49), + 'red_background': (41, 49), + 'green_background': (42, 49), + 'yellow_background': (43, 49), + 'blue_background': (44, 49), + 'purple_background': (45, 49), + 'cyan_background': (46, 49), + 'white_background': (47, 49), } + +def render_effects(text, *effects): + 'Wrap text in commands to turn on each effect.' + start = [] + stop = [] + for effect in effects: + start.append(str(_effect_params[effect][0])) + stop.append(str(_effect_params[effect][1])) + start = '\033[' + ';'.join(start) + 'm' + stop = '\033[' + ';'.join(stop) + 'm' + return start + text + stop + +def colorstatus(statusfunc, ui, repo, *pats, **opts): + '''run the status command with colored output''' + + delimiter = opts['print0'] and '\0' or '\n' + + # run status and capture it's output + ui.pushbuffer() + retval = statusfunc(ui, repo, *pats, **opts) + # filter out empty strings + lines = [ line for line in ui.popbuffer().split(delimiter) if line ] + + if opts['no_status']: + # if --no-status, run the command again without that option to get + # output with status abbreviations + opts['no_status'] = False + ui.pushbuffer() + statusfunc(ui, repo, *pats, **opts) + # filter out empty strings + lines_with_status = [ line for + line in ui.popbuffer().split(delimiter) if line ] + else: + lines_with_status = lines + + # apply color to output and display it + for i in xrange(0, len(lines)): + status = _status_abbreviations[lines_with_status[i][0]] + effects = _status_effects[status] + if effects: + lines[i] = render_effects(lines[i], *effects) + sys.stdout.write(lines[i] + delimiter) + return retval + +_status_abbreviations = { 'M': 'modified', + 'A': 'added', + 'R': 'removed', + '!': 'deleted', + '?': 'unknown', + 'I': 'ignored', + 'C': 'clean', + ' ': 'copied', } + +_status_effects = { 'modified': ('blue', 'bold'), + 'added': ('green', 'bold'), + 'removed': ('red', 'bold'), + 'deleted': ('cyan', 'bold', 'underline'), + 'unknown': ('magenta', 'bold', 'underline'), + 'ignored': ('black', 'bold'), + 'clean': ('none', ), + 'copied': ('none', ), } + +def colorqseries(qseriesfunc, ui, repo, *dummy, **opts): + '''run the qseries command with colored output''' + ui.pushbuffer() + retval = qseriesfunc(ui, repo, **opts) + patches = ui.popbuffer().splitlines() + for patch in patches: + if opts['missing']: + effects = _patch_effects['missing'] + # Determine if patch is applied. Search for beginning of output + # line in the applied patch list, in case --summary has been used + # and output line isn't just the patch name. + elif [ applied for applied in repo.mq.applied + if patch.startswith(applied.name) ]: + effects = _patch_effects['applied'] + else: + effects = _patch_effects['unapplied'] + sys.stdout.write(render_effects(patch, *effects) + '\n') + return retval + +_patch_effects = { 'applied': ('blue', 'bold', 'underline'), + 'missing': ('red', 'bold'), + 'unapplied': ('black', 'bold'), } + +def uisetup(ui): + '''Initialize the extension.''' + nocoloropt = ('', 'no-color', None, _("don't colorize output")) + _decoratecmd(ui, 'status', commands.table, colorstatus, nocoloropt) + _configcmdeffects(ui, 'status', _status_effects); + if ui.config('extensions', 'hgext.mq', default=None) is not None: + from hgext import mq + _decoratecmd(ui, 'qseries', mq.cmdtable, colorqseries, nocoloropt) + _configcmdeffects(ui, 'qseries', _patch_effects); + +def _decoratecmd(ui, cmd, table, delegate, *delegateoptions): + '''Replace the function that implements cmd in table with a decorator. + + The decorator that becomes the new implementation of cmd calls + delegate. The delegate's first argument is the replaced function, + followed by the normal Mercurial command arguments (ui, repo, ...). If + the delegate adds command options, supply them as delegateoptions. + ''' + cmdkey, cmdentry = _cmdtableitem(ui, cmd, table) + decorator = lambda ui, repo, *args, **opts: \ + _colordecorator(delegate, cmdentry[0], + ui, repo, *args, **opts) + # make sure 'hg help cmd' still works + decorator.__doc__ = cmdentry[0].__doc__ + decoratorentry = (decorator,) + cmdentry[1:] + for option in delegateoptions: + decoratorentry[1].append(option) + table[cmdkey] = decoratorentry + +def _cmdtableitem(ui, cmd, table): + '''Return key, value from table for cmd, or None if not found.''' + aliases, entry = cmdutil.findcmd(ui, cmd, table) + for candidatekey, candidateentry in table.iteritems(): + if candidateentry is entry: + return candidatekey, entry + +def _colordecorator(colorfunc, nocolorfunc, ui, repo, *args, **opts): + '''Delegate to colorfunc or nocolorfunc, depending on conditions. + + Delegate to colorfunc unless --no-color option is set or output is not + to a tty. + ''' + if opts['no_color'] or not sys.stdout.isatty(): + return nocolorfunc(ui, repo, *args, **opts) + return colorfunc(nocolorfunc, ui, repo, *args, **opts) + +def _configcmdeffects(ui, cmdname, effectsmap): + '''Override default effects for cmdname with those from .hgrc file. + + Entries in the .hgrc file are in the [color] section, and look like + 'cmdname'.'status' (for instance, 'status.modified = blue bold inverse'). + ''' + for status in effectsmap: + effects = ui.config('color', cmdname + '.' + status) + if effects: + effectsmap[status] = re.split('\W+', effects) diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/__init__.py --- a/hgext/convert/__init__.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/__init__.py Sat Jan 12 20:49:07 2008 +0100 @@ -22,6 +22,7 @@ Accepted destination formats: - Mercurial + - Subversion (history on branches is not preserved) If no revision is given, all revisions will be converted. Otherwise, convert will only import up to the named revision (given in a format @@ -56,7 +57,7 @@ exclude path/to/file rename from/file to/file - + The 'include' directive causes a file, or all files under a directory, to be included in the destination repository, and the exclusion of all other files and dirs not explicitely included. @@ -64,6 +65,24 @@ The 'rename' directive renames a file or directory. To rename from a subdirectory into the root of the repository, use '.' as the path to rename to. + + Back end options: + + --config convert.hg.clonebranches=False (boolean) + hg target: XXX not documented + --config convert.hg.saverev=True (boolean) + hg source: allow target to preserve source revision ID + --config convert.hg.tagsbranch=default (branch name) + hg target: XXX not documented + --config convert.hg.usebranchnames=True (boolean) + hg target: preserve branch names + + --config convert.svn.branches=branches (directory name) + svn source: specify the directory containing branches + --config convert.svn.tags=tags (directory name) + svn source: specify the directory containing tags + --config convert.svn.trunk=trunk (directory name) + svn source: specify the name of the trunk branch """ return convcmd.convert(ui, src, dest, revmapfile, **opts) diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/common.py --- a/hgext/convert/common.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/common.py Sat Jan 12 20:49:07 2008 +0100 @@ -1,6 +1,9 @@ # common code for the convert extension -import base64 +import base64, errno +import os import cPickle as pickle +from mercurial import util +from mercurial.i18n import _ def encodeargs(args): def encodearg(s): @@ -15,6 +18,11 @@ s = base64.decodestring(s) return pickle.loads(s) +def checktool(exe, name=None): + name = name or exe + if not util.find_exe(exe): + raise util.Abort('cannot find required "%s" tool' % name) + class NoRepo(Exception): pass SKIPREV = 'SKIP' @@ -33,7 +41,7 @@ class converter_source(object): """Conversion source interface""" - def __init__(self, ui, path, rev=None): + def __init__(self, ui, path=None, rev=None): """Initialize conversion source (or raise NoRepo("message") exception if path is not a valid repository)""" self.ui = ui @@ -48,11 +56,8 @@ def after(self): pass - def setrevmap(self, revmap, order): - """set the map of already-converted revisions - - order is a list with the keys from revmap in the order they - appear in the revision map file.""" + def setrevmap(self, revmap): + """set the map of already-converted revisions""" pass def getheads(self): @@ -100,17 +105,22 @@ def getchangedfiles(self, rev, i): """Return the files changed by rev compared to parent[i]. - + i is an index selecting one of the parents of rev. The return value should be the list of files that are different in rev and this parent. If rev has no parents, i is None. - + This function is only needed to support --filemap """ raise NotImplementedError() + def converted(self, rev, sinkrev): + '''Notify the source that a revision has been converted.''' + pass + + class converter_sink(object): """Conversion sink (target) interface""" @@ -184,3 +194,149 @@ filter empty revisions. """ pass + + def before(self): + pass + + def after(self): + pass + + +class commandline(object): + def __init__(self, ui, command): + self.ui = ui + self.command = command + + def prerun(self): + pass + + def postrun(self): + pass + + def _cmdline(self, cmd, *args, **kwargs): + cmdline = [self.command, cmd] + list(args) + for k, v in kwargs.iteritems(): + if len(k) == 1: + cmdline.append('-' + k) + else: + cmdline.append('--' + k.replace('_', '-')) + try: + if len(k) == 1: + cmdline.append('' + v) + else: + cmdline[-1] += '=' + v + except TypeError: + pass + cmdline = [util.shellquote(arg) for arg in cmdline] + cmdline += ['<', util.nulldev] + cmdline = ' '.join(cmdline) + self.ui.debug(cmdline, '\n') + return cmdline + + def _run(self, cmd, *args, **kwargs): + cmdline = self._cmdline(cmd, *args, **kwargs) + self.prerun() + try: + return util.popen(cmdline) + finally: + self.postrun() + + def run(self, cmd, *args, **kwargs): + fp = self._run(cmd, *args, **kwargs) + output = fp.read() + self.ui.debug(output) + return output, fp.close() + + def checkexit(self, status, output=''): + if status: + if output: + self.ui.warn(_('%s error:\n') % self.command) + self.ui.warn(output) + msg = util.explain_exit(status)[0] + raise util.Abort(_('%s %s') % (self.command, msg)) + + def run0(self, cmd, *args, **kwargs): + output, status = self.run(cmd, *args, **kwargs) + self.checkexit(status, output) + return output + + def getargmax(self): + if '_argmax' in self.__dict__: + return self._argmax + + # POSIX requires at least 4096 bytes for ARG_MAX + self._argmax = 4096 + try: + self._argmax = os.sysconf("SC_ARG_MAX") + except: + pass + + # Windows shells impose their own limits on command line length, + # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes + # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for + # details about cmd.exe limitations. + + # Since ARG_MAX is for command line _and_ environment, lower our limit + # (and make happy Windows shells while doing this). + + self._argmax = self._argmax/2 - 1 + return self._argmax + + def limit_arglist(self, arglist, cmd, *args, **kwargs): + limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs)) + bytes = 0 + fl = [] + for fn in arglist: + b = len(fn) + 3 + if bytes + b < limit or len(fl) == 0: + fl.append(fn) + bytes += b + else: + yield fl + fl = [fn] + bytes = b + if fl: + yield fl + + def xargs(self, arglist, cmd, *args, **kwargs): + for l in self.limit_arglist(arglist, cmd, *args, **kwargs): + self.run0(cmd, *(list(args) + l), **kwargs) + +class mapfile(dict): + def __init__(self, ui, path): + super(mapfile, self).__init__() + self.ui = ui + self.path = path + self.fp = None + self.order = [] + self._read() + + def _read(self): + try: + fp = open(self.path, 'r') + except IOError, err: + if err.errno != errno.ENOENT: + raise + return + for line in fp: + key, value = line[:-1].split(' ', 1) + if key not in self: + self.order.append(key) + super(mapfile, self).__setitem__(key, value) + fp.close() + + def __setitem__(self, key, value): + if self.fp is None: + try: + self.fp = open(self.path, 'a') + except IOError, err: + raise util.Abort(_('could not open map file %r: %s') % + (self.path, err.strerror)) + self.fp.write('%s %s\n' % (key, value)) + self.fp.flush() + super(mapfile, self).__setitem__(key, value) + + def close(self): + if self.fp: + self.fp.close() + self.fp = None diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/convcmd.py Sat Jan 12 20:49:07 2008 +0100 @@ -5,12 +5,12 @@ # 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, SKIPREV, converter_source, converter_sink +from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile from cvs import convert_cvs from darcs import darcs_source from git import convert_git from hg import mercurial_source, mercurial_sink -from subversion import svn_source, debugsvnlog +from subversion import debugsvnlog, svn_source, svn_sink import filemap import os, shutil @@ -27,6 +27,7 @@ sink_converters = [ ('hg', mercurial_sink), + ('svn', svn_sink), ] def convertsource(ui, path, type, rev): @@ -59,23 +60,10 @@ self.ui = ui self.opts = opts self.commitcache = {} - self.revmapfile = revmapfile - self.revmapfilefd = None self.authors = {} self.authorfile = None - self.maporder = [] - self.map = {} - try: - origrevmapfile = open(self.revmapfile, 'r') - for l in origrevmapfile: - sv, dv = l[:-1].split() - if sv not in self.map: - self.maporder.append(sv) - self.map[sv] = dv - origrevmapfile.close() - except IOError: - pass + self.map = mapfile(ui, revmapfile) # Read first the dst author map if any authorfile = self.dest.authorfile() @@ -157,22 +145,13 @@ if pl: depth[n] = max([depth[p] for p in pl]) + 1 - s = [(depth[n], self.commitcache[n].date, n) for n in s] + s = [(depth[n], util.parsedate(self.commitcache[n].date), n) + for n in s] s.sort() s = [e[2] for e in s] return s - def mapentry(self, src, dst): - if self.revmapfilefd is None: - try: - self.revmapfilefd = open(self.revmapfile, "a") - except IOError, (errno, strerror): - raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror)) - self.map[src] = dst - self.revmapfilefd.write("%s %s\n" % (src, dst)) - self.revmapfilefd.flush() - def writeauthormap(self): authorfile = self.authorfile if authorfile: @@ -219,7 +198,7 @@ dest = SKIPREV else: dest = self.map[changes] - self.mapentry(rev, dest) + self.map[rev] = dest return files, copies = changes parents = [self.map[r] for r in commit.parents] @@ -247,13 +226,14 @@ self.dest.copyfile(copyf, f) newnode = self.dest.putcommit(filenames, parents, commit) - self.mapentry(rev, newnode) + self.source.converted(rev, newnode) + self.map[rev] = newnode def convert(self): try: self.source.before() self.dest.before() - self.source.setrevmap(self.map, self.maporder) + self.source.setrevmap(self.map) self.ui.status("scanning source...\n") heads = self.source.getheads() parents = self.walktree(heads) @@ -268,6 +248,10 @@ desc = self.commitcache[c].desc if "\n" in desc: desc = desc.splitlines()[0] + # convert log message to local encoding without using + # tolocal() because util._encoding conver() use it as + # 'utf-8' + desc = desc.decode('utf-8').encode(orig_encoding, 'replace') self.ui.status("%d %s\n" % (num, desc)) self.copy(c) @@ -283,7 +267,7 @@ # write another hash correspondence to override the previous # one so we don't end up with extra tag heads if nrev: - self.mapentry(c, nrev) + self.map[c] = nrev self.writeauthormap() finally: @@ -294,10 +278,13 @@ self.dest.after() finally: self.source.after() - if self.revmapfilefd: - self.revmapfilefd.close() + self.map.close() + +orig_encoding = 'ascii' def convert(ui, src, dest=None, revmapfile=None, **opts): + global orig_encoding + orig_encoding = util._encoding util._encoding = 'UTF-8' if not dest: diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/cvs.py --- a/hgext/convert/cvs.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/cvs.py Sat Jan 12 20:49:07 2008 +0100 @@ -1,9 +1,10 @@ # CVS conversion code inspired by hg-cvs-import and git-cvsimport import os, locale, re, socket +from cStringIO import StringIO from mercurial import util -from common import NoRepo, commit, converter_source +from common import NoRepo, commit, converter_source, checktool class convert_cvs(converter_source): def __init__(self, ui, path, rev=None): @@ -13,6 +14,9 @@ if not os.path.exists(cvs): raise NoRepo("%s does not look like a CVS checkout" % path) + for tool in ('cvsps', 'cvs'): + checktool(tool) + self.changeset = {} self.files = {} self.tags = {} @@ -206,6 +210,20 @@ return self.heads def _getfile(self, name, rev): + + def chunkedread(fp, count): + # file-objects returned by socked.makefile() do not handle + # large read() requests very well. + chunksize = 65536 + output = StringIO() + while count > 0: + data = fp.read(min(count, chunksize)) + if not data: + raise util.Abort("%d bytes missing from remote file" % count) + count -= len(data) + output.write(data) + return output.getvalue() + if rev.endswith("(DEAD)"): raise IOError @@ -224,14 +242,14 @@ self.readp.readline() # entries mode = self.readp.readline()[:-1] count = int(self.readp.readline()[:-1]) - data = self.readp.read(count) + data = chunkedread(self.readp, count) elif line.startswith(" "): data += line[1:] elif line.startswith("M "): pass elif line.startswith("Mbinary "): count = int(self.readp.readline()[:-1]) - data = self.readp.read(count) + data = chunkedread(self.readp, count) else: if line == "ok\n": return (data, "x" in mode and "x" or "") diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/darcs.py --- a/hgext/convert/darcs.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/darcs.py Sat Jan 12 20:49:07 2008 +0100 @@ -1,6 +1,6 @@ # darcs support for the convert extension -from common import NoRepo, commit, converter_source +from common import NoRepo, checktool, commandline, commit, converter_source from mercurial.i18n import _ from mercurial import util import os, shutil, tempfile @@ -17,15 +17,18 @@ except ImportError: ElementTree = None -class darcs_source(converter_source): +class darcs_source(converter_source, commandline): def __init__(self, ui, path, rev=None): - super(darcs_source, self).__init__(ui, path, rev=rev) + converter_source.__init__(self, ui, path, rev=rev) + commandline.__init__(self, ui, 'darcs') # check for _darcs, ElementTree, _darcs/inventory so that we can # easily skip test-convert-darcs if ElementTree is not around if not os.path.exists(os.path.join(path, '_darcs')): raise NoRepo("%s does not look like a darcs repo" % path) + checktool('darcs') + if ElementTree is None: raise util.Abort(_("Python ElementTree module is not available")) @@ -45,7 +48,8 @@ output, status = self.run('init', repodir=self.tmppath) self.checkexit(status) - tree = self.xml('changes', '--xml-output', '--summary') + tree = self.xml('changes', xml_output=True, summary=True, + repodir=self.path) tagname = None child = None for elt in tree.findall('patch'): @@ -65,31 +69,9 @@ self.ui.debug('cleaning up %s\n' % self.tmppath) shutil.rmtree(self.tmppath, ignore_errors=True) - def _run(self, cmd, *args, **kwargs): - cmdline = ['darcs', cmd, '--repodir', kwargs.get('repodir', self.path)] - cmdline += args - cmdline = [util.shellquote(arg) for arg in cmdline] - cmdline += ['<', util.nulldev] - cmdline = ' '.join(cmdline) - self.ui.debug(cmdline, '\n') - return util.popen(cmdline) - - def run(self, cmd, *args, **kwargs): - fp = self._run(cmd, *args, **kwargs) - output = fp.read() - return output, fp.close() - - def checkexit(self, status, output=''): - if status: - if output: - self.ui.warn(_('darcs error:\n')) - self.ui.warn(output) - msg = util.explain_exit(status)[0] - raise util.Abort(_('darcs %s') % msg) - - def xml(self, cmd, *opts): + def xml(self, cmd, **kwargs): etree = ElementTree() - fp = self._run(cmd, *opts) + fp = self._run(cmd, **kwargs) etree.parse(fp) self.checkexit(fp.close()) return etree.getroot() @@ -105,15 +87,15 @@ desc=desc.strip(), parents=self.parents[rev]) def pull(self, rev): - output, status = self.run('pull', self.path, '--all', - '--match', 'hash %s' % rev, - '--no-test', '--no-posthook', - '--external-merge', '/bin/false', + output, status = self.run('pull', self.path, all=True, + match='hash %s' % rev, + no_test=True, no_posthook=True, + external_merge='/bin/false', repodir=self.tmppath) if status: if output.find('We have conflicts in') == -1: self.checkexit(status, output) - output, status = self.run('revert', '--all', repodir=self.tmppath) + output, status = self.run('revert', all=True, repodir=self.tmppath) self.checkexit(status, output) def getchanges(self, rev): diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/filemap.py --- a/hgext/convert/filemap.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/filemap.py Sat Jan 12 20:49:07 2008 +0100 @@ -7,9 +7,10 @@ import shlex from mercurial.i18n import _ from mercurial import util -from common import SKIPREV +from common import SKIPREV, converter_source def rpairs(name): + yield '.', name e = len(name) while e != -1: yield name[:e], name[e+1:] @@ -110,9 +111,9 @@ # touch files we're interested in, but also merges that merge two # or more interesting revisions. -class filemap_source(object): +class filemap_source(converter_source): def __init__(self, ui, baseconverter, filemap): - self.ui = ui + super(filemap_source, self).__init__(ui) self.base = baseconverter self.filemapper = filemapper(ui, filemap) self.commits = {} @@ -134,7 +135,7 @@ def after(self): self.base.after() - def setrevmap(self, revmap, order): + def setrevmap(self, revmap): # rebuild our state to make things restartable # # To avoid calling getcommit for every revision that has already @@ -149,7 +150,7 @@ seen = {SKIPREV: SKIPREV} dummyset = util.set() converted = [] - for rev in order: + for rev in revmap.order: mapped = revmap[rev] wanted = mapped not in seen if wanted: @@ -163,7 +164,7 @@ arg = None converted.append((rev, wanted, arg)) self.convertedorder = converted - return self.base.setrevmap(revmap, order) + return self.base.setrevmap(revmap) def rebuild(self): if self._rebuilt: @@ -350,9 +351,3 @@ def gettags(self): return self.base.gettags() - - def before(self): - pass - - def after(self): - pass diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/git.py --- a/hgext/convert/git.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/git.py Sat Jan 12 20:49:07 2008 +0100 @@ -3,7 +3,7 @@ import os from mercurial import util -from common import NoRepo, commit, converter_source +from common import NoRepo, commit, converter_source, checktool class convert_git(converter_source): # Windows does not support GIT_DIR= construct while other systems @@ -31,6 +31,9 @@ path += "/.git" if not os.path.exists(path + "/objects"): raise NoRepo("%s does not look like a Git repo" % path) + + checktool('git-rev-parse', 'git') + self.path = path def getheads(self): diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/hg.py --- a/hgext/convert/hg.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/hg.py Sat Jan 12 20:49:07 2008 +0100 @@ -1,10 +1,16 @@ # hg backend for convert extension -# Note for hg->hg conversion: Old versions of Mercurial didn't trim -# the whitespace from the ends of commit messages, but new versions -# do. Changesets created by those older versions, then converted, may -# thus have different hashes for changesets that are otherwise -# identical. +# Notes for hg->hg conversion: +# +# * Old versions of Mercurial didn't trim the whitespace from the ends +# of commit messages, but new versions do. Changesets created by +# those older versions, then converted, may thus have different +# hashes for changesets that are otherwise identical. +# +# * By default, the source revision is stored in the converted +# revision. This will cause the converted revision to have a +# different identity than the source. To avoid this, use the +# following option: "--config convert.hg.saverev=false" import os, time @@ -24,8 +30,6 @@ if os.path.isdir(path) and len(os.listdir(path)) > 0: try: self.repo = hg.repository(self.ui, path) - ui.status(_('destination %s is a Mercurial repository\n') % - path) except hg.RepoError, err: ui.print_exc() raise NoRepo(err.args[0]) @@ -42,11 +46,13 @@ self.filemapmode = False def before(self): + self.ui.debug(_('run hg sink pre-conversion action\n')) self.wlock = self.repo.wlock() self.lock = self.repo.lock() self.repo.dirstate.clear() def after(self): + self.ui.debug(_('run hg sink post-conversion action\n')) self.repo.dirstate.invalidate() self.lock = None self.wlock = None @@ -174,7 +180,7 @@ except hg.RepoError, inst: tagparent = nullid self.repo.rawcommit([".hgtags"], "update tags", "convert-repo", - date, tagparent, nullid) + date, tagparent, nullid, extra=extra) return hex(self.repo.changelog.tip()) def setfilemapmode(self, active): @@ -183,6 +189,7 @@ class mercurial_source(converter_source): def __init__(self, ui, path, rev=None): converter_source.__init__(self, ui, path, rev) + self.saverev = ui.configbool('convert', 'hg.saverev', True) try: self.repo = hg.repository(self.ui, path) # try to provoke an exception if this isn't really a hg @@ -195,6 +202,7 @@ self.lastrev = None self.lastctx = None self._changescache = None + self.convertfp = None def changectx(self, rev): if self.lastrev != rev: @@ -240,8 +248,12 @@ def getcommit(self, rev): ctx = self.changectx(rev) parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid] + if self.saverev: + crev = rev + else: + crev = None return commit(author=ctx.user(), date=util.datestr(ctx.date()), - desc=ctx.description(), parents=parents, + desc=ctx.description(), rev=crev, parents=parents, branch=ctx.branch(), extra=ctx.extra()) def gettags(self): @@ -258,3 +270,15 @@ return changes[0] + changes[1] + changes[2] + def converted(self, rev, destrev): + if self.convertfp is None: + self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'), + 'a') + self.convertfp.write('%s %s\n' % (destrev, rev)) + self.convertfp.flush() + + def before(self): + self.ui.debug(_('run hg source pre-conversion action\n')) + + def after(self): + self.ui.debug(_('run hg source post-conversion action\n')) diff -r 03ce5a919ae3 -r deb0d3518674 hgext/convert/subversion.py --- a/hgext/convert/subversion.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/convert/subversion.py Sat Jan 12 20:49:07 2008 +0100 @@ -17,9 +17,13 @@ import locale import os +import re import sys import cPickle as pickle -from mercurial import util +import tempfile + +from mercurial import strutil, util +from mercurial.i18n import _ # Subversion stuff. Works best with very recent Python SVN bindings # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing @@ -28,6 +32,7 @@ from cStringIO import StringIO from common import NoRepo, commit, converter_source, encodeargs, decodeargs +from common import commandline, converter_sink, mapfile try: from svn.core import SubversionException, Pool @@ -46,7 +51,10 @@ except SubversionException: pass if os.path.isdir(path): - return 'file://%s' % os.path.normpath(os.path.abspath(path)) + path = os.path.normpath(os.path.abspath(path)) + if os.name == 'nt': + path = '/' + util.normpath(path) + return 'file://%s' % path return path def optrev(number): @@ -149,9 +157,15 @@ self.head = self.revid(self.last_changed) self._changescache = None - def setrevmap(self, revmap, order): + if os.path.exists(os.path.join(url, '.svn/entries')): + self.wc = url + else: + self.wc = None + self.convertfp = None + + def setrevmap(self, revmap): lastrevs = {} - for revid in revmap.keys(): + for revid in revmap.iterkeys(): uuid, module, revnum = self.revsplit(revid) lastrevnum = lastrevs.setdefault(module, revnum) if revnum > lastrevnum: @@ -197,7 +211,7 @@ self.heads.append(brev) if oldmodule: - self.tags = '%s/%s' % (oldmodule, tags) + self.tags = '%s/%s' % (oldmodule, tags) else: self.tags = '/%s' % tags @@ -293,6 +307,15 @@ self.ui.note('no tags found at revision %d\n' % start) return tags + def converted(self, rev, destrev): + if not self.wc: + return + if self.convertfp is None: + self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'), + 'a') + self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev))) + self.convertfp.flush() + # -- helper functions -- def revid(self, revnum, module=None): @@ -392,7 +415,9 @@ 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)) + self.ui.debug("Copied to %s from %s@%s\n" % + (entrypath, 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 @@ -664,3 +689,240 @@ pool = Pool() rpath = '/'.join([self.base, path]).strip('/') return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()] + +pre_revprop_change = '''#!/bin/sh + +REPOS="$1" +REV="$2" +USER="$3" +PROPNAME="$4" +ACTION="$5" + +if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi +if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi +if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi + +echo "Changing prohibited revision property" >&2 +exit 1 +''' + +class svn_sink(converter_sink, commandline): + commit_re = re.compile(r'Committed revision (\d+).', re.M) + + def prerun(self): + if self.wc: + os.chdir(self.wc) + + def postrun(self): + if self.wc: + os.chdir(self.cwd) + + def join(self, name): + return os.path.join(self.wc, '.svn', name) + + def revmapfile(self): + return self.join('hg-shamap') + + def authorfile(self): + return self.join('hg-authormap') + + def __init__(self, ui, path): + converter_sink.__init__(self, ui, path) + commandline.__init__(self, ui, 'svn') + self.delete = [] + self.setexec = [] + self.delexec = [] + self.copies = [] + self.wc = None + self.cwd = os.getcwd() + + path = os.path.realpath(path) + + created = False + if os.path.isfile(os.path.join(path, '.svn', 'entries')): + self.wc = path + self.run0('update') + else: + wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc') + + if os.path.isdir(os.path.dirname(path)): + if not os.path.exists(os.path.join(path, 'db', 'fs-type')): + ui.status(_('initializing svn repo %r\n') % + os.path.basename(path)) + commandline(ui, 'svnadmin').run0('create', path) + created = path + path = util.normpath(path) + if not path.startswith('/'): + path = '/' + path + path = 'file://' + path + + ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath)) + self.run0('checkout', path, wcpath) + + self.wc = wcpath + self.opener = util.opener(self.wc) + self.wopener = util.opener(self.wc) + self.childmap = mapfile(ui, self.join('hg-childmap')) + self.is_exec = util.checkexec(self.wc) and util.is_exec or None + + if created: + hook = os.path.join(created, 'hooks', 'pre-revprop-change') + fp = open(hook, 'w') + fp.write(pre_revprop_change) + fp.close() + util.set_flags(hook, "x") + + xport = transport.SvnRaTransport(url=geturl(path)) + self.uuid = svn.ra.get_uuid(xport.ra) + + def wjoin(self, *names): + return os.path.join(self.wc, *names) + + def putfile(self, filename, flags, data): + if 'l' in flags: + self.wopener.symlink(data, filename) + else: + try: + if os.path.islink(self.wjoin(filename)): + os.unlink(filename) + except OSError: + pass + self.wopener(filename, 'w').write(data) + + if self.is_exec: + was_exec = self.is_exec(self.wjoin(filename)) + else: + # On filesystems not supporting execute-bit, there is no way + # to know if it is set but asking subversion. Setting it + # systematically is just as expensive and much simpler. + was_exec = 'x' not in flags + + util.set_flags(self.wjoin(filename), flags) + if was_exec: + if 'x' not in flags: + self.delexec.append(filename) + else: + if 'x' in flags: + self.setexec.append(filename) + + def delfile(self, name): + self.delete.append(name) + + def copyfile(self, source, dest): + self.copies.append([source, dest]) + + def _copyfile(self, source, dest): + # SVN's copy command pukes if the destination file exists, but + # our copyfile method expects to record a copy that has + # already occurred. Cross the semantic gap. + wdest = self.wjoin(dest) + exists = os.path.exists(wdest) + if exists: + fd, tempname = tempfile.mkstemp( + prefix='hg-copy-', dir=os.path.dirname(wdest)) + os.close(fd) + os.unlink(tempname) + os.rename(wdest, tempname) + try: + self.run0('copy', source, dest) + finally: + if exists: + try: + os.unlink(wdest) + except OSError: + pass + os.rename(tempname, wdest) + + def dirs_of(self, files): + dirs = set() + for f in files: + if os.path.isdir(self.wjoin(f)): + dirs.add(f) + for i in strutil.rfindall(f, '/'): + dirs.add(f[:i]) + return dirs + + def add_dirs(self, files): + add_dirs = [d for d in self.dirs_of(files) + if not os.path.exists(self.wjoin(d, '.svn', 'entries'))] + if add_dirs: + add_dirs.sort() + self.xargs(add_dirs, 'add', non_recursive=True, quiet=True) + return add_dirs + + def add_files(self, files): + if files: + self.xargs(files, 'add', quiet=True) + return files + + def tidy_dirs(self, names): + dirs = list(self.dirs_of(names)) + dirs.sort(reverse=True) + deleted = [] + for d in dirs: + wd = self.wjoin(d) + if os.listdir(wd) == '.svn': + self.run0('delete', d) + deleted.append(d) + return deleted + + def addchild(self, parent, child): + self.childmap[parent] = child + + def revid(self, rev): + return u"svn:%s@%s" % (self.uuid, rev) + + def putcommit(self, files, parents, commit): + for parent in parents: + try: + return self.revid(self.childmap[parent]) + except KeyError: + pass + entries = set(self.delete) + files = util.frozenset(files) + entries.update(self.add_dirs(files.difference(entries))) + if self.copies: + for s, d in self.copies: + self._copyfile(s, d) + self.copies = [] + if self.delete: + self.xargs(self.delete, 'delete') + self.delete = [] + entries.update(self.add_files(files.difference(entries))) + entries.update(self.tidy_dirs(entries)) + if self.delexec: + self.xargs(self.delexec, 'propdel', 'svn:executable') + self.delexec = [] + if self.setexec: + self.xargs(self.setexec, 'propset', 'svn:executable', '*') + self.setexec = [] + + fd, messagefile = tempfile.mkstemp(prefix='hg-convert-') + fp = os.fdopen(fd, 'w') + fp.write(commit.desc) + fp.close() + try: + output = self.run0('commit', + username=util.shortuser(commit.author), + file=messagefile, + encoding='utf-8') + try: + rev = self.commit_re.search(output).group(1) + except AttributeError: + self.ui.warn(_('unexpected svn output:\n')) + self.ui.warn(output) + raise util.Abort(_('unable to cope with svn output')) + if commit.rev: + self.run('propset', 'hg:convert-rev', commit.rev, + revprop=True, revision=rev) + if commit.branch and commit.branch != 'default': + self.run('propset', 'hg:convert-branch', commit.branch, + revprop=True, revision=rev) + for parent in parents: + self.addchild(parent, rev) + return self.revid(rev) + finally: + os.unlink(messagefile) + + def puttags(self, tags): + self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n')) diff -r 03ce5a919ae3 -r deb0d3518674 hgext/fetch.py --- a/hgext/fetch.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/fetch.py Sat Jan 12 20:49:07 2008 +0100 @@ -43,7 +43,8 @@ if not err: mod, add, rem = repo.status()[:3] message = (cmdutil.logmessage(opts) or - (_('Automated merge with %s') % other.url())) + (_('Automated merge with %s') % + util.removeauth(other.url()))) n = repo.commit(mod + add + rem, message, opts['user'], opts['date'], force_editor=opts.get('force_editor')) @@ -54,7 +55,8 @@ cmdutil.setremoteconfig(ui, opts) other = hg.repository(ui, ui.expandpath(source)) - ui.status(_('pulling from %s\n') % ui.expandpath(source)) + ui.status(_('pulling from %s\n') % + util.hidepassword(ui.expandpath(source))) revs = None if opts['rev'] and not other.local(): raise util.Abort(_("fetch -r doesn't work for remote repositories yet")) diff -r 03ce5a919ae3 -r deb0d3518674 hgext/gpg.py --- a/hgext/gpg.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/gpg.py Sat Jan 12 20:49:07 2008 +0100 @@ -249,7 +249,7 @@ message = opts['message'] if not message: message = "\n".join([_("Added signature for changeset %s") - % hgnode.hex(n) + % hgnode.short(n) for n in nodes]) try: repo.commit([".hgsigs"], message, opts['user'], opts['date']) diff -r 03ce5a919ae3 -r deb0d3518674 hgext/hbisect.py --- a/hgext/hbisect.py Sat Jan 12 19:35:11 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,313 +0,0 @@ -# bisect extension for mercurial -# -# Copyright 2005, 2006 Benoit Boissinot -# 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, incorporated herein by reference. - -from mercurial.i18n import _ -from mercurial import hg, util, commands, cmdutil -import os, sys, sets - -versionstr = "0.0.3" - -def lookup_rev(ui, repo, rev=None): - """returns rev or the checked-out revision if rev is None""" - if not rev is None: - return repo.lookup(rev) - parents = [p for p in repo.dirstate.parents() if p != hg.nullid] - if len(parents) != 1: - raise util.Abort(_("unexpected number of parents, " - "please commit or revert")) - return parents.pop() - -def check_clean(ui, repo): - modified, added, removed, deleted, unknown = repo.status()[:5] - if modified or added or removed: - ui.warn("Repository is not clean, please commit or revert\n") - sys.exit(1) - -class bisect(object): - """dichotomic search in the DAG of changesets""" - def __init__(self, ui, repo): - self.repo = repo - self.path = repo.join("bisect") - self.opener = util.opener(self.path) - self.ui = ui - self.goodrevs = [] - self.badrev = None - self.good_path = "good" - self.bad_path = "bad" - self.is_reset = False - - if os.path.exists(os.path.join(self.path, self.good_path)): - self.goodrevs = self.opener(self.good_path).read().splitlines() - self.goodrevs = [hg.bin(x) for x in self.goodrevs] - if os.path.exists(os.path.join(self.path, self.bad_path)): - r = self.opener(self.bad_path).read().splitlines() - if r: - self.badrev = hg.bin(r.pop(0)) - - def write(self): - if self.is_reset: - return - if not os.path.isdir(self.path): - os.mkdir(self.path) - f = self.opener(self.good_path, "w") - f.write("\n".join([hg.hex(r) for r in self.goodrevs])) - if len(self.goodrevs) > 0: - f.write("\n") - f = self.opener(self.bad_path, "w") - if self.badrev: - f.write(hg.hex(self.badrev) + "\n") - - def init(self): - """start a new bisection""" - if os.path.isdir(self.path): - raise util.Abort(_("bisect directory already exists\n")) - os.mkdir(self.path) - check_clean(self.ui, self.repo) - return 0 - - def reset(self): - """finish a bisection""" - if os.path.isdir(self.path): - sl = [os.path.join(self.path, p) - for p in [self.bad_path, self.good_path]] - for s in sl: - if os.path.exists(s): - os.unlink(s) - os.rmdir(self.path) - # Not sure about this - #self.ui.write("Going back to tip\n") - #self.repo.update(self.repo.changelog.tip()) - self.is_reset = True - return 0 - - def num_ancestors(self, head=None, stop=None): - """ - returns a dict with the mapping: - node -> number of ancestors (self included) - for all nodes who are ancestor of head and - not in stop. - """ - if head is None: - head = self.badrev - return self.__ancestors_and_nb_ancestors(head, stop)[1] - - def ancestors(self, head=None, stop=None): - """ - returns the set of the ancestors of head (self included) - who are not in stop. - """ - if head is None: - head = self.badrev - return self.__ancestors_and_nb_ancestors(head, stop)[0] - - def __ancestors_and_nb_ancestors(self, head, stop=None): - """ - if stop is None then ancestors of goodrevs are used as - lower limit. - - returns (anc, n_child) where anc is the set of the ancestors of head - and n_child is a dictionary with the following mapping: - node -> number of ancestors (self included) - """ - cl = self.repo.changelog - if not stop: - stop = sets.Set([]) - for i in xrange(len(self.goodrevs)-1, -1, -1): - g = self.goodrevs[i] - if g in stop: - continue - stop.update(cl.reachable(g)) - def num_children(a): - """ - returns a dictionnary with the following mapping - node -> [number of children, empty set] - """ - d = {a: [0, sets.Set([])]} - for i in xrange(cl.rev(a)+1): - n = cl.node(i) - if not d.has_key(n): - d[n] = [0, sets.Set([])] - parents = [p for p in cl.parents(n) if p != hg.nullid] - for p in parents: - d[p][0] += 1 - return d - - if head in stop: - raise util.Abort(_("Inconsistent state, %s:%s is good and bad") - % (cl.rev(head), hg.short(head))) - n_child = num_children(head) - for i in xrange(cl.rev(head)+1): - n = cl.node(i) - parents = [p for p in cl.parents(n) if p != hg.nullid] - for p in parents: - n_child[p][0] -= 1 - if not n in stop: - n_child[n][1].union_update(n_child[p][1]) - if n_child[p][0] == 0: - n_child[p] = len(n_child[p][1]) - if not n in stop: - n_child[n][1].add(n) - if n_child[n][0] == 0: - if n == head: - anc = n_child[n][1] - n_child[n] = len(n_child[n][1]) - return anc, n_child - - def next(self): - if not self.badrev: - raise util.Abort(_("You should give at least one bad revision")) - if not self.goodrevs: - self.ui.warn(_("No good revision given\n")) - self.ui.warn(_("Marking the first revision as good\n")) - ancestors, num_ancestors = self.__ancestors_and_nb_ancestors( - self.badrev) - tot = len(ancestors) - if tot == 1: - if ancestors.pop() != self.badrev: - raise util.Abort(_("Could not find the first bad revision")) - self.ui.write(_("The first bad revision is:\n")) - displayer = cmdutil.show_changeset(self.ui, self.repo, {}) - displayer.show(changenode=self.badrev) - return None - best_rev = None - best_len = -1 - for n in ancestors: - l = num_ancestors[n] - l = min(l, tot - l) - if l > best_len: - best_len = l - best_rev = n - assert best_rev is not None - nb_tests = 0 - q, r = divmod(tot, 2) - while q: - nb_tests += 1 - q, r = divmod(q, 2) - msg = _("Testing changeset %s:%s (%s changesets remaining, " - "~%s tests)\n") % (self.repo.changelog.rev(best_rev), - hg.short(best_rev), tot, nb_tests) - self.ui.write(msg) - return best_rev - - def autonext(self): - """find and update to the next revision to test""" - check_clean(self.ui, self.repo) - rev = self.next() - if rev is not None: - return hg.clean(self.repo, rev) - - def good(self, rev): - self.goodrevs.append(rev) - - def autogood(self, rev=None): - """mark revision as good and update to the next revision to test""" - check_clean(self.ui, self.repo) - rev = lookup_rev(self.ui, self.repo, rev) - self.good(rev) - if self.badrev: - return self.autonext() - - def bad(self, rev): - self.badrev = rev - - def autobad(self, rev=None): - """mark revision as bad and update to the next revision to test""" - check_clean(self.ui, self.repo) - rev = lookup_rev(self.ui, self.repo, rev) - self.bad(rev) - if self.goodrevs: - self.autonext() - -# should we put it in the class ? -def test(ui, repo, rev): - """test the bisection code""" - b = bisect(ui, repo) - rev = repo.lookup(rev) - ui.write("testing with rev %s\n" % hg.hex(rev)) - anc = b.ancestors() - while len(anc) > 1: - if not rev in anc: - ui.warn("failure while bisecting\n") - sys.exit(1) - ui.write("it worked :)\n") - new_rev = b.next() - ui.write("choosing if good or bad\n") - if rev in b.ancestors(head=new_rev): - b.bad(new_rev) - ui.write("it is bad\n") - else: - b.good(new_rev) - ui.write("it is good\n") - anc = b.ancestors() - #repo.update(new_rev, force=True) - for v in anc: - if v != rev: - ui.warn("fail to found cset! :(\n") - return 1 - ui.write("Found bad cset: %s\n" % hg.hex(b.badrev)) - ui.write("Everything is ok :)\n") - return 0 - -def bisect_run(ui, repo, cmd=None, *args): - """Dichotomic search in the DAG of changesets - -This extension helps to find changesets which cause problems. -To use, mark the earliest changeset you know introduces 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. Once you have performed tests, mark the working directory -as bad or good and bisect will either update to another candidate -changeset or announce that it has found the bad revision. - -Note: bisect expects bad revisions to be descendants of good revisions. -If you are looking for the point at which a problem was fixed, then make -the problem-free state "bad" and the problematic state "good." - -For subcommands see "hg bisect help\" - """ - def help_(cmd=None, *args): - """show help for a given bisect subcommand or all subcommands""" - cmdtable = bisectcmdtable - if cmd: - doc = cmdtable[cmd][0].__doc__ - synopsis = cmdtable[cmd][2] - ui.write(synopsis + "\n") - ui.write("\n" + doc + "\n") - return - ui.write(_("list of subcommands for the bisect extension\n\n")) - cmds = cmdtable.keys() - cmds.sort() - m = max([len(c) for c in cmds]) - for cmd in cmds: - doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip() - ui.write(" %-*s %s\n" % (m, cmd, doc)) - - b = bisect(ui, repo) - bisectcmdtable = { - "init": (b.init, 0, _("hg bisect init")), - "bad": (b.autobad, 1, _("hg bisect bad []")), - "good": (b.autogood, 1, _("hg bisect good []")), - "next": (b.autonext, 0, _("hg bisect next")), - "reset": (b.reset, 0, _("hg bisect reset")), - "help": (help_, 1, _("hg bisect help []")), - } - - if not bisectcmdtable.has_key(cmd): - ui.warn(_("bisect: Unknown sub-command\n")) - return help_() - if len(args) > bisectcmdtable[cmd][1]: - ui.warn(_("bisect: Too many arguments\n")) - return help_() - ret = bisectcmdtable[cmd][0](*args) - b.write() - return ret - -cmdtable = { - "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")), - #"bisect-test": (test, [], "hg bisect-test rev"), -} diff -r 03ce5a919ae3 -r deb0d3518674 hgext/hgk.py --- a/hgext/hgk.py Sat Jan 12 19:35:11 2008 +0100 +++ b/hgext/hgk.py Sat Jan 12 20:49:07 2008 +0100 @@ -13,7 +13,7 @@ # querying of information, and an extension to mercurial named hgk.py, # which provides hooks for hgk to get information. hgk can be found in # the contrib directory, and hgk.py can be found in the hgext -# directory. +# directory. # # To load the hgext.py extension, add it to your .hgrc file (you have # to use your global $HOME/.hgrc file, not one in a repository). You @@ -309,10 +309,10 @@ def config(ui, repo, **opts): """print extension options""" def writeopt(name, value): - ui.write('k=%s\nv=%s\n' % (name, value)) + ui.write('k=%s\nv=%s\n' % (name, value)) writeopt('vdiff', ui.config('hgk', 'vdiff', '')) - + def view(ui, repo, *etc, **opts): "start interactive history viewer" diff -r 03ce5a919ae3 -r deb0d3518674 hgext/highlight.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/highlight.py Sat Jan 12 20:49:07 2008 +0100 @@ -0,0 +1,151 @@ +""" +This is Mercurial extension for syntax highlighting in the file +revision view of hgweb. + +It depends on the pygments syntax highlighting library: +http://pygments.org/ + +To enable the extension add this to hgrc: + +[extensions] +hgext.highlight = + +There is a single configuration option: + +[web] +pygments_style =