# HG changeset patch
# User Patrick Mezard
# Date 1200750511 -3600
# Node ID ff7fb4f0db53b0430e63595428ea2cfee580a551
# Parent f1ac41359b365daad6fdf6ad8c395e4cd4636aef# Parent b32a0596b2d75f6c54e13ee4432d4630e31d0474
Merge with crew-stable
diff -r b32a0596b2d7 -r ff7fb4f0db53 .hgignore
--- a/.hgignore Sat Jan 19 14:39:30 2008 +0100
+++ b/.hgignore Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 CONTRIBUTORS
--- a/CONTRIBUTORS Sat Jan 19 14:39:30 2008 +0100
+++ b/CONTRIBUTORS Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 contrib/bash_completion
--- a/contrib/bash_completion Sat Jan 19 14:39:30 2008 +0100
+++ b/contrib/bash_completion Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 contrib/churn.py
--- a/contrib/churn.py Sat Jan 19 14:39:30 2008 +0100
+++ b/contrib/churn.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 contrib/hgk
--- a/contrib/hgk Sat Jan 19 14:39:30 2008 +0100
+++ b/contrib/hgk Sat Jan 19 14:48:31 2008 +0100
@@ -274,6 +274,7 @@
set comname {}
set comdate {}
set rev {}
+ set branch {}
if {![info exists nchildren($id)]} {
set children($id) {}
set nchildren($id) 0
@@ -310,6 +311,8 @@
set comname [join [lrange $line 1 [expr {$x - 1}]]]
} elseif {$tag == "revision"} {
set rev [lindex $line 1]
+ } elseif {$tag == "branch"} {
+ set branch [join [lrange $line 1 end]]
}
}
} else {
@@ -334,7 +337,7 @@
set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
}
set commitinfo($id) [list $headline $auname $audate \
- $comname $comdate $comment $rev]
+ $comname $comdate $comment $rev $branch]
}
proc readrefs {} {
@@ -649,7 +652,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 +690,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
}
@@ -2286,6 +2289,9 @@
$ctext mark gravity fmark.0 left
set info $commitinfo($id)
$ctext insert end "Revision: [lindex $info 6]\n"
+ if {[llength [lindex $info 7]] > 0} {
+ $ctext insert end "Branch: [lindex $info 7]\n"
+ }
$ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
$ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
if {[info exists idtags($id)]} {
@@ -3844,10 +3850,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 b32a0596b2d7 -r ff7fb4f0db53 contrib/win32/ReadMe.html
--- a/contrib/win32/ReadMe.html Sat Jan 19 14:39:30 2008 +0100
+++ b/contrib/win32/ReadMe.html Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 contrib/win32/mercurial.iss
--- a/contrib/win32/mercurial.iss Sat Jan 19 14:39:30 2008 +0100
+++ b/contrib/win32/mercurial.iss Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 contrib/zsh_completion
--- a/contrib/zsh_completion Sat Jan 19 14:39:30 2008 +0100
+++ b/contrib/zsh_completion Sat Jan 19 14:48:31 2008 +0100
@@ -13,6 +13,9 @@
# option) any later version.
#
+emulate -LR zsh
+setopt extendedglob
+
local curcontext="$curcontext" state line
typeset -A _hg_cmd_globals
@@ -153,9 +156,9 @@
typeset -a tags
local tag rev
- _hg_cmd tags 2> /dev/null | while read tag rev
+ _hg_cmd tags 2> /dev/null | while read tag
do
- tags+=($tag)
+ tags+=(${tag/ # [0-9]#:*})
done
(( $#tags )) && _describe -t tags 'tags' tags
}
@@ -674,13 +677,13 @@
# MQ
_hg_qseries() {
typeset -a patches
- patches=($(_hg_cmd qseries 2>/dev/null))
+ patches=(${(f)"$(_hg_cmd qseries 2>/dev/null)"})
(( $#patches )) && _describe -t hg-patches 'patches' patches
}
_hg_qapplied() {
typeset -a patches
- patches=($(_hg_cmd qapplied 2>/dev/null))
+ patches=(${(f)"$(_hg_cmd qapplied 2>/dev/null)"})
if (( $#patches ))
then
patches+=(qbase qtip)
@@ -690,7 +693,7 @@
_hg_qunapplied() {
typeset -a patches
- patches=($(_hg_cmd qunapplied 2>/dev/null))
+ patches=(${(f)"$(_hg_cmd qunapplied 2>/dev/null)"})
(( $#patches )) && _describe -t hg-unapplied-patches 'unapplied patches' patches
}
@@ -730,6 +733,12 @@
'*:unapplied patch:_hg_qunapplied'
}
+_hg_cmd_qgoto() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--force -f)'{-f,--force}'[overwrite any local changes]' \
+ ':patch:_hg_qseries'
+}
+
_hg_cmd_qguard() {
_arguments -s -w : $_hg_global_opts \
'(--list -l)'{-l,--list}'[list all patches and guards]' \
diff -r b32a0596b2d7 -r ff7fb4f0db53 doc/hg.1.txt
--- a/doc/hg.1.txt Sat Jan 19 14:39:30 2008 +0100
+++ b/doc/hg.1.txt Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 doc/hgrc.5.txt
--- a/doc/hgrc.5.txt Sat Jan 19 14:39:30 2008 +0100
+++ b/doc/hgrc.5.txt Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hg
--- a/hg Sat Jan 19 14:39:30 2008 +0100
+++ b/hg Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/color.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/color.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/__init__.py
--- a/hgext/convert/__init__.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/__init__.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/common.py
--- a/hgext/convert/common.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/common.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/convcmd.py
--- a/hgext/convert/convcmd.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/convcmd.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/cvs.py
--- a/hgext/convert/cvs.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/cvs.py Sat Jan 19 14:48:31 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 = {}
@@ -175,7 +179,7 @@
if conntype != "pserver":
if conntype == "rsh":
- rsh = os.environ.get("CVS_RSH" or "rsh")
+ rsh = os.environ.get("CVS_RSH") or "ssh"
if user:
cmd = [rsh, '-l', user, host] + cmd
else:
@@ -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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/darcs.py
--- a/hgext/convert/darcs.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/darcs.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/filemap.py
--- a/hgext/convert/filemap.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/filemap.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/git.py
--- a/hgext/convert/git.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/git.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/hg.py
--- a/hgext/convert/hg.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/hg.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/convert/subversion.py
--- a/hgext/convert/subversion.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/convert/subversion.py Sat Jan 19 14:48:31 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):
@@ -81,6 +89,9 @@
receiver)
except SubversionException, (inst, num):
pickle.dump(num, fp, protocol)
+ except IOError:
+ # Caller may interrupt the iteration
+ pickle.dump(None, fp, protocol)
else:
pickle.dump(None, fp, protocol)
fp.close()
@@ -94,7 +105,53 @@
args = decodeargs(sys.stdin.read())
get_log_child(sys.stdout, *args)
+class logstream:
+ """Interruptible revision log iterator."""
+ def __init__(self, stdout):
+ self._stdout = stdout
+
+ def __iter__(self):
+ while True:
+ entry = pickle.load(self._stdout)
+ try:
+ orig_paths, revnum, author, date, message = entry
+ except:
+ if entry is None:
+ break
+ raise SubversionException("child raised exception", entry)
+ yield entry
+
+ def close(self):
+ if self._stdout:
+ self._stdout.close()
+ self._stdout = None
+
+def get_log(url, paths, start, end, limit=0, discover_changed_paths=True,
+ strict_node_history=False):
+ args = [url, paths, start, end, limit, discover_changed_paths,
+ strict_node_history]
+ arg = encodeargs(args)
+ hgexe = util.hgexecutable()
+ cmd = '%s debugsvnlog' % util.shellquote(hgexe)
+ stdin, stdout = os.popen2(cmd, 'b')
+ stdin.write(arg)
+ stdin.close()
+ return logstream(stdout)
+
# SVN conversion code stolen from bzr-svn and tailor
+#
+# Subversion looks like a versioned filesystem, branches structures
+# are defined by conventions and not enforced by the tool. First,
+# we define the potential branches (modules) as "trunk" and "branches"
+# children directories. Revisions are then identified by their
+# module and revision number (and a repository identifier).
+#
+# The revision graph is really a tree (or a forest). By default, a
+# revision parent is the previous revision in the same module. If the
+# module directory is copied/moved from another module then the
+# revision is the module root and its parent the source revision in
+# the parent module. A revision has at most one parent.
+#
class svn_source(converter_source):
def __init__(self, ui, url, rev=None):
super(svn_source, self).__init__(ui, url, rev=rev)
@@ -125,7 +182,6 @@
self.ctx = self.transport.client
self.base = svn.ra.get_repos_root(self.ra)
self.module = self.url[len(self.base):]
- self.modulemap = {} # revision, module
self.commits = {}
self.paths = {}
self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
@@ -149,9 +205,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:
@@ -167,46 +229,48 @@
return False
def getheads(self):
- # detect standard /branches, /tags, /trunk layout
+
+ def getcfgpath(name, rev):
+ cfgpath = self.ui.config('convert', 'svn.' + name)
+ path = (cfgpath or name).strip('/')
+ if not self.exists(path, rev):
+ if cfgpath:
+ raise util.Abort(_('expected %s to be at %r, but not found')
+ % (name, path))
+ return None
+ self.ui.note(_('found %s at %r\n') % (name, path))
+ return path
+
rev = optrev(self.last_changed)
- rpath = self.url.strip('/')
- cfgtrunk = self.ui.config('convert', 'svn.trunk')
- cfgbranches = self.ui.config('convert', 'svn.branches')
- cfgtags = self.ui.config('convert', 'svn.tags')
- trunk = (cfgtrunk or 'trunk').strip('/')
- branches = (cfgbranches or 'branches').strip('/')
- tags = (cfgtags or 'tags').strip('/')
- if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
- self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
- (trunk, branches, tags))
- oldmodule = self.module
+ oldmodule = ''
+ trunk = getcfgpath('trunk', rev)
+ tags = getcfgpath('tags', rev)
+ branches = getcfgpath('branches', rev)
+
+ # If the project has a trunk or branches, we will extract heads
+ # from them. We keep the project root otherwise.
+ if trunk:
+ oldmodule = self.module or ''
self.module += '/' + trunk
lt = self.latest(self.module, self.last_changed)
self.head = self.revid(lt)
- self.heads = [self.head]
+
+ # First head in the list is the module's head
+ self.heads = [self.head]
+ self.tags = '%s/%s' % (oldmodule , (tags or 'tags'))
+
+ # Check if branches bring a few more heads to the list
+ if branches:
+ rpath = self.url.strip('/')
branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
self.ctx)
for branch in branchnames.keys():
- if oldmodule:
- module = oldmodule + '/' + branches + '/' + branch
- else:
- module = '/' + branches + '/' + branch
+ module = '%s/%s/%s' % (oldmodule, 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)
- if oldmodule:
- self.tags = '%s/%s' % (oldmodule, tags)
- else:
- self.tags = '/%s' % tags
-
- elif cfgtrunk or cfgbranches or cfgtags:
- raise util.Abort('trunk/branch/tags layout expected, but not found')
- else:
- self.ui.note('working with one branch\n')
- self.heads = [self.head]
- self.tags = tags
return self.heads
def getfile(self, file, rev):
@@ -241,45 +305,26 @@
uuid, module, revnum = self.revsplit(rev)
self.module = module
self.reparent(module)
+ # We assume that:
+ # - requests for revisions after "stop" come from the
+ # revision graph backward traversal. Cache all of them
+ # down to stop, they will be used eventually.
+ # - requests for revisions before "stop" come to get
+ # isolated branches parents. Just fetch what is needed.
stop = self.lastrevs.get(module, 0)
- self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
+ if revnum < stop:
+ stop = revnum + 1
+ self._fetch_revisions(revnum, stop)
commit = self.commits[rev]
# caller caches the result, so free it here to release memory
del self.commits[rev]
return commit
- def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
- strict_node_history=False):
-
- def parent(fp):
- while True:
- entry = pickle.load(fp)
- try:
- orig_paths, revnum, author, date, message = entry
- except:
- if entry is None:
- break
- raise SubversionException("child raised exception", entry)
- yield entry
-
- args = [self.url, paths, start, end, limit, discover_changed_paths,
- strict_node_history]
- arg = encodeargs(args)
- hgexe = util.hgexecutable()
- cmd = '%s debugsvnlog' % util.shellquote(hgexe)
- stdin, stdout = os.popen2(cmd, 'b')
-
- stdin.write(arg)
- stdin.close()
-
- for p in parent(stdout):
- yield p
-
def gettags(self):
tags = {}
start = self.revnum(self.head)
try:
- for entry in self.get_log([self.tags], 0, start):
+ for entry in get_log(self.url, [self.tags], 0, start):
orig_paths, revnum, author, date, message = entry
for path in orig_paths:
if not path.startswith(self.tags+'/'):
@@ -293,6 +338,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):
@@ -375,13 +429,11 @@
entries = []
copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
copies = {}
- revnum = self.revnum(rev)
- if revnum in self.modulemap:
- new_module = self.modulemap[revnum]
- if new_module != self.module:
- self.module = new_module
- self.reparent(self.module)
+ new_module, revnum = self.revsplit(rev)[1:]
+ if new_module != self.module:
+ self.module = new_module
+ self.reparent(self.module)
for path, ent in paths:
entrypath = get_entry_from_path(path, module=self.module)
@@ -392,7 +444,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
@@ -405,12 +459,9 @@
# 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)
+ # parents cannot be empty here, you cannot remove things from
+ # a root revision.
+ uuid, old_module, fromrev = self.revsplit(parents[0])
basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
@@ -486,6 +537,9 @@
# If the directory just had a prop change,
# then we shouldn't need to look for its children.
+ if ent.action == 'M':
+ continue
+
# 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
@@ -546,23 +600,23 @@
return (entries, copies)
- def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
+ def _fetch_revisions(self, from_revnum, to_revnum):
+ if from_revnum < to_revnum:
+ from_revnum, to_revnum = to_revnum, from_revnum
+
self.child_cset = None
def parselogentry(orig_paths, revnum, author, date, message):
+ """Return the parsed commit object or None, and True if
+ the revision is a branch root.
+ """
self.ui.debug("parsing revision %d (%d changes)\n" %
(revnum, len(orig_paths)))
- if revnum in self.modulemap:
- new_module = self.modulemap[revnum]
- if new_module != self.module:
- self.module = new_module
- self.reparent(self.module)
-
rev = self.revid(revnum)
# 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
+
+ if (rev in self.commits or revnum < to_revnum):
+ return None, False
parents = []
# check whether this revision is the start of a branch
@@ -571,15 +625,12 @@
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")
- self.modulemap[revnum] = self.module # track backwards in time
-
orig_paths = orig_paths.items()
orig_paths.sort()
paths = []
@@ -590,8 +641,6 @@
continue
paths.append((path, ent))
- self.paths[rev] = (paths, parents)
-
# Example SVN datetime. Includes microseconds.
# ISO-8601 conformant
# '2007-01-04T17:35:00.902377Z'
@@ -614,23 +663,50 @@
rev=rev.encode('utf-8'))
self.commits[rev] = cset
+ # The parents list is *shared* among self.paths and the
+ # commit object. Both will be updated below.
+ self.paths[rev] = (paths, cset.parents)
if self.child_cset and not self.child_cset.parents:
- self.child_cset.parents = [rev]
+ self.child_cset.parents[:] = [rev]
self.child_cset = cset
+ return cset, len(parents) > 0
self.ui.note('fetching revision log for "%s" from %d to %d\n' %
(self.module, from_revnum, to_revnum))
try:
- for entry in self.get_log([self.module], from_revnum, to_revnum):
- orig_paths, revnum, author, date, message = entry
- if self.is_blacklisted(revnum):
- self.ui.note('skipping blacklisted revision %d\n' % revnum)
- continue
- if orig_paths is None:
- self.ui.debug('revision %d has no entries\n' % revnum)
- continue
- parselogentry(orig_paths, revnum, author, date, message)
+ firstcset = None
+ stream = get_log(self.url, [self.module], from_revnum, to_revnum)
+ try:
+ for entry in stream:
+ paths, revnum, author, date, message = entry
+ if self.is_blacklisted(revnum):
+ self.ui.note('skipping blacklisted revision %d\n'
+ % revnum)
+ continue
+ if paths is None:
+ self.ui.debug('revision %d has no entries\n' % revnum)
+ continue
+ cset, branched = parselogentry(paths, revnum, author,
+ date, message)
+ if cset:
+ firstcset = cset
+ if branched:
+ break
+ finally:
+ stream.close()
+
+ if firstcset and not firstcset.parents:
+ # The first revision of the sequence (the last fetched one)
+ # has invalid parents if not a branch root. Find the parent
+ # revision now, if any.
+ try:
+ firstrevnum = self.revnum(firstcset.rev)
+ if firstrevnum > 1:
+ latest = self.latest(self.module, firstrevnum - 1)
+ firstcset.parents.append(self.revid(latest))
+ except util.Abort:
+ pass
except SubversionException, (inst, num):
if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
raise NoSuchRevision(branch=self,
@@ -642,9 +718,9 @@
# 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]
+ new_module, revnum = self.revsplit(rev)[1:]
+ if self.module != new_module:
+ self.module = new_module
self.reparent(self.module)
info = svn.ra.get_file(self.ra, file, revnum, io)
if isinstance(info, list):
@@ -669,3 +745,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 b32a0596b2d7 -r ff7fb4f0db53 hgext/fetch.py
--- a/hgext/fetch.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/fetch.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/gpg.py
--- a/hgext/gpg.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/gpg.py Sat Jan 19 14:48:31 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/hbisect.py
--- a/hgext/hbisect.py Sat Jan 19 14:39:30 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 b32a0596b2d7 -r ff7fb4f0db53 hgext/hgk.py
--- a/hgext/hgk.py Sat Jan 19 14:39:30 2008 +0100
+++ b/hgext/hgk.py Sat Jan 19 14:48:31 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
@@ -45,7 +45,7 @@
# Revisions context menu will now display additional entries to fire
# vdiff on hovered and selected revisions.
-import sys, os
+import os
from mercurial import hg, fancyopts, commands, ui, util, patch, revlog
def difftree(ui, repo, node1=None, node2=None, *files, **opts):
@@ -61,17 +61,14 @@
for f in modified:
# TODO get file permissions
- print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]),
- hg.short(mmap2[f]),
- f, f)
+ ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
+ (hg.short(mmap[f]), hg.short(mmap2[f]), f, f))
for f in added:
- print ":000000 100664 %s %s N\t%s\t%s" % (empty,
- hg.short(mmap2[f]),
- f, f)
+ ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
+ (empty, hg.short(mmap2[f]), f, f))
for f in removed:
- print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]),
- empty,
- f, f)
+ ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
+ (hg.short(mmap[f]), empty, f, f))
##
while True:
@@ -93,7 +90,7 @@
node1 = repo.changelog.parents(node1)[0]
if opts['patch']:
if opts['pretty']:
- catcommit(repo, node2, "")
+ catcommit(ui, repo, node2, "")
patch.diff(repo, node1, node2,
files=files,
opts=patch.diffopts(ui, {'git': True}))
@@ -102,14 +99,14 @@
if not opts['stdin']:
break
-def catcommit(repo, n, prefix, ctx=None):
+def catcommit(ui, repo, n, prefix, ctx=None):
nlprefix = '\n' + prefix;
if ctx is None:
ctx = repo.changectx(n)
(p1, p2) = ctx.parents()
- print "tree %s" % (hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
- if p1: print "parent %s" % (hg.short(p1.node()))
- if p2: print "parent %s" % (hg.short(p2.node()))
+ ui.write("tree %s\n" % hg.short(ctx.changeset()[0])) # use ctx.node() instead ??
+ if p1: ui.write("parent %s\n" % hg.short(p1.node()))
+ if p2: ui.write("parent %s\n" % hg.short(p2.node()))
date = ctx.date()
description = ctx.description().replace("\0", "")
lines = description.splitlines()
@@ -118,23 +115,24 @@
else:
committer = ctx.user()
- print "author %s %s %s" % (ctx.user(), int(date[0]), date[1])
- print "committer %s %s %s" % (committer, int(date[0]), date[1])
- print "revision %d" % ctx.rev()
- print ""
+ ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
+ ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
+ ui.write("revision %d\n" % ctx.rev())
+ ui.write("branch %s\n\n" % ctx.branch())
+
if prefix != "":
- print "%s%s" % (prefix, description.replace('\n', nlprefix).strip())
+ ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
else:
- print description
+ ui.write(description + "\n")
if prefix:
- sys.stdout.write('\0')
+ ui.write('\0')
def base(ui, repo, node1, node2):
"""Output common ancestor information"""
node1 = repo.lookup(node1)
node2 = repo.lookup(node2)
n = repo.changelog.ancestor(node1, node2)
- print hg.short(n)
+ ui.write(hg.short(n) + "\n")
def catfile(ui, repo, type=None, r=None, **opts):
"""cat a specific revision"""
@@ -157,10 +155,10 @@
while r:
if type != "commit":
- sys.stderr.write("aborting hg cat-file only understands commits\n")
- sys.exit(1);
+ ui.warn("aborting hg cat-file only understands commits\n")
+ return 1;
n = repo.lookup(r)
- catcommit(repo, n, prefix)
+ catcommit(ui, repo, n, prefix)
if opts['stdin']:
try:
(type, r) = raw_input().split(' ');
@@ -174,7 +172,7 @@
# telling you which commits are reachable from the supplied ones via
# a bitmask based on arg position.
# you can specify a commit to stop at by starting the sha1 with ^
-def revtree(args, repo, full="tree", maxnr=0, parents=False):
+def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
def chlogwalk():
count = repo.changelog.count()
i = count
@@ -259,24 +257,24 @@
if pp[1] != hg.nullid:
parentstr += " " + hg.short(pp[1])
if not full:
- print hg.short(n) + parentstr
+ ui.write("%s%s\n" % (hg.short(n), parentstr))
elif full == "commit":
- print hg.short(n) + parentstr
- catcommit(repo, n, ' ', ctx)
+ ui.write("%s%s\n" % (hg.short(n), parentstr))
+ catcommit(ui, repo, n, ' ', ctx)
else:
(p1, p2) = repo.changelog.parents(n)
(h, h1, h2) = map(hg.short, (n, p1, p2))
(i1, i2) = map(repo.changelog.rev, (p1, p2))
date = ctx.date()[0]
- print "%s %s:%s" % (date, h, mask),
+ ui.write("%s %s:%s" % (date, h, mask))
mask = is_reachable(want_sha1, reachable, p1)
if i1 != hg.nullrev and mask > 0:
- print "%s:%s " % (h1, mask),
+ ui.write("%s:%s " % (h1, mask)),
mask = is_reachable(want_sha1, reachable, p2)
if i2 != hg.nullrev and mask > 0:
- print "%s:%s " % (h2, mask),
- print ""
+ ui.write("%s:%s " % (h2, mask))
+ ui.write("\n")
if maxnr and count >= maxnr:
break
count += 1
@@ -304,15 +302,15 @@
else:
full = None
copy = [x for x in revs]
- revtree(copy, repo, full, opts['max_count'], opts['parents'])
+ revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
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 b32a0596b2d7 -r ff7fb4f0db53 hgext/highlight.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/highlight.py Sat Jan 19 14:48:31 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 =