# HG changeset patch # User Benoit Boissinot # Date 1230732258 -3600 # Node ID a9221c7f51a4a38a2f362799b2d2c5819ae25b61 # Parent 013a2baf8f85aa1fad87dfcf4f84b6ac70d536ce# Parent cab1cf26ca58aa42d438247b4f19180c5a2493a0 merge bookmarks diff -r cab1cf26ca58 -r a9221c7f51a4 .hgignore --- a/.hgignore Wed Dec 31 14:29:51 2008 +0100 +++ b/.hgignore Wed Dec 31 15:04:18 2008 +0100 @@ -30,3 +30,4 @@ syntax: regexp ^\.pc/ +^\.(pydev)?project diff -r cab1cf26ca58 -r a9221c7f51a4 .hgsigs --- a/.hgsigs Wed Dec 31 14:29:51 2008 +0100 +++ b/.hgsigs Wed Dec 31 15:04:18 2008 +0100 @@ -7,3 +7,5 @@ bae2e9c838e90a393bae3973a7850280413e091a 0 iD8DBQBH6DO5ywK+sNU5EO8RAsfrAJ0e4r9c9GF/MJsM7Xjd3NesLRC3+ACffj6+6HXdZf8cswAoFPO+DY00oD0= d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 0 iD8DBQBINdwsywK+sNU5EO8RAjIUAKCPmlFJSpsPAAUKF+iNHAwVnwmzeQCdEXrL27CWclXuUKdbQC8De7LICtE= d2375bbee6d47e62ba8e415c86e83a465dc4dce9 0 iD8DBQBIo1wpywK+sNU5EO8RAmRNAJ94x3OFt6blbqu/yBoypm/AJ44fuACfUaldXcV5z9tht97hSp22DVTEPGc= +2a67430f92f15ea5159c26b09ec4839a0c549a26 0 iEYEABECAAYFAkk1hykACgkQywK+sNU5EO85QACeNJNUanjc2tl4wUoPHNuv+lSj0ZMAoIm93wSTc/feyYnO2YCaQ1iyd9Nu +3773e510d433969e277b1863c317b674cbee2065 0 iEYEABECAAYFAklNbbAACgkQywK+sNU5EO8o+gCfeb2/lfIJZMvyDA1m+G1CsBAxfFsAoIa6iAMG8SBY7hW1Q85Yf/LXEvaE diff -r cab1cf26ca58 -r a9221c7f51a4 .hgtags --- a/.hgtags Wed Dec 31 14:29:51 2008 +0100 +++ b/.hgtags Wed Dec 31 15:04:18 2008 +0100 @@ -19,3 +19,5 @@ bae2e9c838e90a393bae3973a7850280413e091a 1.0 d5cbbe2c49cee22a9fbeb9ea41daa0ac4e26b846 1.0.1 d2375bbee6d47e62ba8e415c86e83a465dc4dce9 1.0.2 +2a67430f92f15ea5159c26b09ec4839a0c549a26 1.1 +3773e510d433969e277b1863c317b674cbee2065 1.1.1 diff -r cab1cf26ca58 -r a9221c7f51a4 contrib/buildrpm --- a/contrib/buildrpm Wed Dec 31 14:29:51 2008 +0100 +++ b/contrib/buildrpm Wed Dec 31 15:04:18 2008 +0100 @@ -3,9 +3,16 @@ # Build a Mercurial RPM in place. # Known to work on: # - Fedora 9 +# - Fedora 10 # # Bryan O'Sullivan +if hg --version > /dev/null 2>&1; then : +else + echo 'hg command not available!' 1>&2 + exit 1 +fi + root="`hg root 2>/dev/null`" specfile=contrib/mercurial.spec diff -r cab1cf26ca58 -r a9221c7f51a4 contrib/dumprevlog --- a/contrib/dumprevlog Wed Dec 31 14:29:51 2008 +0100 +++ b/contrib/dumprevlog Wed Dec 31 15:04:18 2008 +0100 @@ -17,7 +17,7 @@ p = r.parents(n) d = r.revision(n) print "node:", node.hex(n) - print "linkrev:", r.linkrev(n) + print "linkrev:", r.linkrev(i) print "parents:", node.hex(p[0]), node.hex(p[1]) print "length:", len(d) print "-start-" diff -r cab1cf26ca58 -r a9221c7f51a4 contrib/hgwebdir.fcgi --- a/contrib/hgwebdir.fcgi Wed Dec 31 14:29:51 2008 +0100 +++ b/contrib/hgwebdir.fcgi Wed Dec 31 15:04:18 2008 +0100 @@ -29,12 +29,28 @@ # repos, collections of repos in a directory tree, or both. # # [paths] -# virtual/path = /real/path -# virtual/path = /real/path +# virtual/path1 = /real/path1 +# virtual/path2 = /real/path2 +# virtual/root = /real/root/* +# / = /real/root2/* # # [collections] # /prefix/to/strip/off = /root/of/tree/full/of/repos # +# paths example: +# +# * First two lines mount one repository into one virtual path, like +# '/real/path1' into 'virtual/path1'. +# +# * The third entry tells every mercurial repository found in +# '/real/root', recursively, should be mounted in 'virtual/root'. This +# format is preferred over the [collections] one, using absolute paths +# as configuration keys is not supported on every platform (including +# Windows). +# +# * The last entry is a special case mounting all repositories in +# '/real/root2' in the root of the virtual directory. +# # collections example: say directory tree /foo contains repos /foo/bar, # /foo/quux/baz. Give this config section: # [collections] diff -r cab1cf26ca58 -r a9221c7f51a4 contrib/mercurial.spec --- a/contrib/mercurial.spec Wed Dec 31 14:29:51 2008 +0100 +++ b/contrib/mercurial.spec Wed Dec 31 15:04:18 2008 +0100 @@ -6,7 +6,7 @@ Group: Development/Tools Source: http://www.selenic.com/mercurial/release/%{name}-%{version}.tar.gz URL: http://www.selenic.com/mercurial -BuildRoot: /tmp/build.%{name}-%{version}-%{release} +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root # From the README: # @@ -28,13 +28,13 @@ for efficient handling of very large distributed projects. %prep -rm -rf $RPM_BUILD_ROOT %setup -q %build make all %install +rm -rf $RPM_BUILD_ROOT python setup.py install --root $RPM_BUILD_ROOT --prefix %{_prefix} make install-doc DESTDIR=$RPM_BUILD_ROOT MANDIR=%{_mandir} @@ -55,9 +55,6 @@ mkdir -p $lisp_dir install contrib/mercurial.el $lisp_dir -# We don't want this, do we? -rm -f $RPM_BUILD_ROOT%{pythonlib}/../mercurial-*-py2.5.egg-info - %clean rm -rf $RPM_BUILD_ROOT @@ -65,8 +62,6 @@ %defattr(-,root,root,-) %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html doc/ja *.cgi %{_mandir}/man?/hg*.gz -%dir %{pythonlib} -%dir %{hgext} %{_sysconfdir}/bash_completion.d/mercurial.sh %{_datadir}/zsh/site-functions/_mercurial %{_datadir}/emacs/site-lisp/mercurial.el @@ -76,12 +71,6 @@ %{_bindir}/hg-viz %{_bindir}/git-rev-tree %{_bindir}/mercurial-convert-repo -%{pythonlib}/templates -%{pythonlib}/*.py* -%{pythonlib}/hgweb/*.py* -%{pythonlib}/*.so -%{hgext}/*.py* -%{hgext}/convert/*.py* -%{hgext}/inotify/*.py* -%{hgext}/highlight/*.py* -%{hgext}/inotify/linux/ +%{_libdir}/python%{pythonver}/site-packages/%{name}-*-py2.5.egg-info +%{pythonlib} +%{hgext} diff -r cab1cf26ca58 -r a9221c7f51a4 contrib/mergetools.hgrc --- a/contrib/mergetools.hgrc Wed Dec 31 14:29:51 2008 +0100 +++ b/contrib/mergetools.hgrc Wed Dec 31 15:04:18 2008 +0100 @@ -46,5 +46,6 @@ ecmerge.regkey=Software\Elli\xc3\xa9 Computing\Merge ecmerge.gui=True +filemerge.executable=/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge filemerge.args=-left $other -right $local -ancestor $base -merge $output filemerge.gui=True diff -r cab1cf26ca58 -r a9221c7f51a4 contrib/perf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/perf.py Wed Dec 31 15:04:18 2008 +0100 @@ -0,0 +1,117 @@ +# perf.py - performance test routines + +from mercurial.i18n import _ +from mercurial import cmdutil, match +import time, os, sys + +def timer(func): + results = [] + begin = time.time() + count = 0 + while 1: + ostart = os.times() + cstart = time.time() + r = func() + cstop = time.time() + ostop = os.times() + count += 1 + a, b = ostart, ostop + results.append((cstop - cstart, b[0] - a[0], b[1]-a[1])) + if cstop - begin > 3 and count >= 100: + break + if cstop - begin > 10 and count >= 3: + break + if r: + sys.stderr.write("! result: %s\n" % r) + m = min(results) + sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n" + % (m[0], m[1] + m[2], m[1], m[2], count)) + +def perfwalk(ui, repo, *pats): + try: + m = cmdutil.match(repo, pats, {}) + timer(lambda: len(list(repo.dirstate.walk(m, True, False)))) + except: + try: + m = cmdutil.match(repo, pats, {}) + timer(lambda: len([b for a,b,c in repo.dirstate.statwalk([], m)])) + except: + timer(lambda: len(list(cmdutil.walk(repo, pats, {})))) + +def perfstatus(ui, repo, *pats): + #m = match.always(repo.root, repo.getcwd()) + #timer(lambda: sum(map(len, repo.dirstate.status(m, False, False, False)))) + timer(lambda: sum(map(len, repo.status()))) + +def perfheads(ui, repo): + timer(lambda: len(repo.changelog.heads())) + +def perftags(ui, repo): + import mercurial.changelog, mercurial.manifest + def t(): + repo.changelog = mercurial.changelog.changelog(repo.sopener) + repo.manifest = mercurial.manifest.manifest(repo.sopener) + repo.tagscache = None + return len(repo.tags()) + timer(t) + +def perfdirstate(ui, repo): + "a" in repo.dirstate + def d(): + repo.dirstate.invalidate() + "a" in repo.dirstate + timer(d) + +def perfdirstatedirs(ui, repo): + "a" in repo.dirstate + def d(): + "a" in repo.dirstate._dirs + del repo.dirstate._dirs + timer(d) + +def perfmanifest(ui, repo): + def d(): + t = repo.manifest.tip() + m = repo.manifest.read(t) + repo.manifest.mapcache = None + repo.manifest._cache = None + timer(d) + +def perfindex(ui, repo): + import mercurial.changelog + def d(): + t = repo.changelog.tip() + repo.changelog = mercurial.changelog.changelog(repo.sopener) + repo.changelog._loadindexmap() + timer(d) + +def perfstartup(ui, repo): + cmd = sys.argv[0] + def d(): + os.system("HGRCPATH= %s version -q > /dev/null" % cmd) + timer(d) + +def perfparents(ui, repo): + nl = [repo.changelog.node(i) for i in xrange(1000)] + def d(): + for n in nl: + repo.changelog.parents(n) + timer(d) + +def perflookup(ui, repo, rev): + timer(lambda: len(repo.lookup(rev))) + +cmdtable = { + 'perflookup': (perflookup, []), + 'perfparents': (perfparents, []), + 'perfstartup': (perfstartup, []), + 'perfstatus': (perfstatus, []), + 'perfwalk': (perfwalk, []), + 'perfmanifest': (perfmanifest, []), + 'perfindex': (perfindex, []), + 'perfheads': (perfheads, []), + 'perftags': (perftags, []), + 'perfdirstate': (perfdirstate, []), + 'perfdirstatedirs': (perfdirstate, []), +} + diff -r cab1cf26ca58 -r a9221c7f51a4 contrib/zsh_completion --- a/contrib/zsh_completion Wed Dec 31 14:29:51 2008 +0100 +++ b/contrib/zsh_completion Wed Dec 31 15:04:18 2008 +0100 @@ -4,14 +4,13 @@ # it into your zsh function path (/usr/share/zsh/site-functions for # instance) # -# Copyright (C) 2005 Steve Borho -# Copyright (C) 2006 Brendan Cully +# Copyright (C) 2005-6 Steve Borho +# Copyright (C) 2006-8 Brendan Cully # # This 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. -# emulate -LR zsh setopt extendedglob @@ -118,27 +117,17 @@ typeset -ga _hg_cmd_list typeset -gA _hg_alias_list local hline cmd cmdalias - _call_program help hg --verbose help | while read -A hline + + _call_program hg hg debugcomplete -v 2>/dev/null | while read -A hline do - cmd="$hline[1]" - case $cmd in - *:) - cmd=${cmd%:} - _hg_cmd_list+=($cmd) - ;; - *,) - cmd=${cmd%,} - _hg_cmd_list+=($cmd) - integer i=2 - while (( i <= $#hline )) - do - cmdalias=${hline[$i]%(:|,)} - _hg_cmd_list+=($cmdalias) - _hg_alias_list+=($cmdalias $cmd) - (( i++ )) - done - ;; - esac + cmd=$hline[1] + _hg_cmd_list+=($cmd) + + for cmdalias in $hline[2,-1] + do + _hg_cmd_list+=($cmdalias) + _hg_alias_list+=($cmdalias $cmd) + done done } @@ -203,6 +192,30 @@ _wanted files expl 'modified files' _multi_parts / status_files } +_hg_resolve() { + local rstate rpah + + [[ -d $PREFIX ]] || PREFIX=$PREFIX:h + + _hg_cmd resolve -l ./$PREFIX 2> /dev/null | while read rstate rpath + do + [[ $rstate == 'R' ]] && resolved_files+=($rpath) + [[ $rstate == 'U' ]] && unresolved_files+=($rpath) + done +} + +_hg_resolved() { + typeset -a resolved_files unresolved_files + _hg_resolve + _wanted files expl 'resolved files' _multi_parts / resolved_files +} + +_hg_unresolved() { + typeset -a resolved_files unresolved_files + _hg_resolve + _wanted files expl 'unresolved files' _multi_parts / unresolved_files +} + _hg_config() { typeset -a items items=(${${(%f)"$(_call_program hg hg showconfig)"}%%\=*}) @@ -580,7 +593,27 @@ '*:file:_hg_files' } +_hg_cmd_resolve() { + local context state line + typeset -A opt_args + + _arguments -s -w : $_hg_global_opts \ + '(--list -l --mark -m --unmark -u)'{-l,--list}'[list state of files needing merge]:*:merged files:->resolve_files' \ + '(--mark -m --list -l --unmark -u)'{-m,--mark}'[mark files as resolved]:*:unresolved files:_hg_unresolved' \ + '(--unmark -u --list -l --mark -m)'{-u,--unmark}'[unmark files as resolved]:*:resolved files:_hg_resolved' \ + '*:file:_hg_unresolved' + + if [[ $state == 'resolve_files' ]] + then + _alternative 'files:resolved files:_hg_resolved' \ + 'files:unresolved files:_hg_unresolved' + fi +} + _hg_cmd_revert() { + local context state line + typeset -A opt_args + _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \ '(--all -a :)'{-a,--all}'[revert all changes when no arguments given]' \ '(--rev -r)'{-r+,--rev}'[revision to revert to]:revision:_hg_tags' \ diff -r cab1cf26ca58 -r a9221c7f51a4 doc/gendoc.py --- a/doc/gendoc.py Wed Dec 31 14:29:51 2008 +0100 +++ b/doc/gendoc.py Wed Dec 31 15:04:18 2008 +0100 @@ -36,14 +36,21 @@ attr = table[cmd] cmds = cmd.lstrip("^").split("|") - d['synopsis'] = attr[2] d['cmd'] = cmds[0] d['aliases'] = cmd.split("|")[1:] d['desc'] = get_desc(attr[0].__doc__) d['opts'] = list(get_opts(attr[1])) + + s = 'hg ' + cmds[0] + if len(attr) > 2: + if not attr[2].startswith('hg'): + s += attr[2] + else: + s = attr[2] + d['synopsis'] = s + return d - def show_doc(ui): def bold(s, text=""): ui.write("%s\n%s\n%s\n" % (s, "="*len(s), text)) diff -r cab1cf26ca58 -r a9221c7f51a4 doc/hgrc.5.txt --- a/doc/hgrc.5.txt Wed Dec 31 14:29:51 2008 +0100 +++ b/doc/hgrc.5.txt Wed Dec 31 15:04:18 2008 +0100 @@ -219,7 +219,7 @@ convenient for recipients. Addresses, headers, and parts not containing patches of outgoing messages will be encoded in the first charset to which conversion from local encoding - (ui.encoding, ui.fallbackencoding) succeeds. If correct + ($HGENCODING, ui.fallbackencoding) succeeds. If correct conversion fails, the text in question is sent as is. Defaults to empty (explicit) list. @@ -228,7 +228,7 @@ us-ascii always first, regardless of settings email.charsets in order given by user ui.fallbackencoding if not in email.charsets - ui.encoding if not in email.charsets + $HGENCODING if not in email.charsets utf-8 always last, regardless of settings Email example: @@ -676,6 +676,16 @@ must be present in this list (separated by whitespace or ","). The contents of the allow_push list are examined after the deny_push list. + allow_read;; + If the user has not already been denied repository access due to the + contents of deny_read, this list determines whether to grant repository + access to the user. If this list is not empty, and the user is + unauthenticated or not present in the list (separated by whitespace or ","), + then access is denied for the user. If the list is empty or not set, then + access is permitted to all users by default. Setting allow_read to the + special value "*" is equivalent to it not being set (i.e. access is + permitted to all users). The contents of the allow_read list are examined + after the deny_read list. allowzip;; (DEPRECATED) Whether to allow .zip downloading of repo revisions. Default is false. This feature creates temporary files. @@ -693,6 +703,18 @@ and any authenticated user name present in this list (separated by whitespace or ",") is also denied. The contents of the deny_push list are examined before the allow_push list. + deny_read;; + Whether to deny reading/viewing of the repository. If this list is not + empty, unauthenticated users are all denied, and any authenticated user name + present in this list (separated by whitespace or ",") is also denied access + to the repository. If set to the special value "*", all remote users are + denied access (rarely needed ;). If deny_read is empty or not set, the + determination of repository access depends on the presence and content of + the allow_read list (see description). If both deny_read and allow_read are + empty or not set, then access is permitted to all users by default. If the + repository is being served via hgwebdir, denied users will not be able to + see it in the list of repositories. The contents of the deny_read list have + priority over (are examined before) the contents of the allow_read list. description;; Textual description of the repository's purpose or contents. Default is "unknown". diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/bookmarks.py --- a/hgext/bookmarks.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/bookmarks.py Wed Dec 31 15:04:18 2008 +0100 @@ -101,7 +101,7 @@ refs = parse(repo) - 'do not update if we do update to an rev equal to the current bookmark' + # do not update if we do update to a rev equal to the current bookmark if (mark not in refs and current(repo) and refs[current(repo)] == repo.changectx('.').node()): return diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/bugzilla.py --- a/hgext/bugzilla.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/bugzilla.py Wed Dec 31 15:04:18 2008 +0100 @@ -4,53 +4,107 @@ # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -# -# hook extension to update comments of bugzilla bugs when changesets -# that refer to bugs by id are seen. this hook does not change bug -# status, only comments. -# -# to configure, add items to '[bugzilla]' section of hgrc. -# -# to use, configure bugzilla extension and enable like this: -# -# [extensions] -# hgext.bugzilla = -# -# [hooks] -# # run bugzilla hook on every change pulled or pushed in here -# incoming.bugzilla = python:hgext.bugzilla.hook -# -# config items: -# -# section name is 'bugzilla'. -# [bugzilla] -# -# REQUIRED: -# host = bugzilla # mysql server where bugzilla database lives -# password = ** # user's password -# version = 2.16 # version of bugzilla installed -# -# OPTIONAL: -# bzuser = ... # fallback bugzilla user name to record comments with -# db = bugs # database to connect to -# notify = ... # command to run to get bugzilla to send mail -# regexp = ... # regexp to match bug ids (must contain one "()" group) -# strip = 0 # number of slashes to strip for url paths -# style = ... # style file to use when formatting comments -# template = ... # template to use when formatting comments -# timeout = 5 # database connection timeout (seconds) -# user = bugs # user to connect to database as -# [web] -# baseurl = http://hgserver/... # root of hg web site for browsing commits -# -# if hg committer names are not same as bugzilla user names, use -# "usermap" feature to map from committer email to bugzilla user name. -# usermap can be in hgrc or separate config file. -# -# [bugzilla] -# usermap = filename # cfg file with "committer"="bugzilla user" info -# [usermap] -# committer_email = bugzilla_user_name + +'''Bugzilla integration + +This hook extension adds comments on bugs in Bugzilla when changesets +that refer to bugs by Bugzilla ID are seen. The hook does not change bug +status. + +The hook updates the Bugzilla database directly. Only Bugzilla installations +using MySQL are supported. + +The hook relies on a Bugzilla script to send bug change notification emails. +That script changes between Bugzilla versions; the 'processmail' script used +prior to 2.18 is replaced in 2.18 and subsequent versions by +'config/sendbugmail.pl'. Note that these will be run by Mercurial as the user +pushing the change; you will need to ensure the Bugzilla install file +permissions are set appropriately. + +Configuring the extension: + + [bugzilla] + host Hostname of the MySQL server holding the Bugzilla database. + db Name of the Bugzilla database in MySQL. Default 'bugs'. + user Username to use to access MySQL server. Default 'bugs'. + password Password to use to access MySQL server. + timeout Database connection timeout (seconds). Default 5. + version Bugzilla version. Specify '3.0' for Bugzilla versions from + 3.0 onwards, and '2.16' for versions prior to 3.0. + bzuser Fallback Bugzilla user name to record comments with, if + changeset committer cannot be found as a Bugzilla user. + notify The command to run to get Bugzilla to send bug change + notification emails. Substitutes one string parameter, + the bug ID. Default 'cd /var/www/html/bugzilla && ' + './processmail %s nobody@nowhere.com'. + regexp Regular expression to match bug IDs in changeset commit message. + Must contain one "()" group. The default expression matches + 'Bug 1234', 'Bug no. 1234', 'Bug number 1234', + 'Bugs 1234,5678', 'Bug 1234 and 5678' and variations thereof. + Matching is case insensitive. + style The style file to use when formatting comments. + template Template to use when formatting comments. Overrides + style if specified. In addition to the usual Mercurial + keywords, the extension specifies: + {bug} The Bugzilla bug ID. + {root} The full pathname of the Mercurial repository. + {webroot} Stripped pathname of the Mercurial repository. + {hgweb} Base URL for browsing Mercurial repositories. + Default 'changeset {node|short} in repo {root} refers ' + 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}' + strip The number of slashes to strip from the front of {root} + to produce {webroot}. Default 0. + usermap Path of file containing Mercurial committer ID to Bugzilla user + ID mappings. If specified, the file should contain one mapping + per line, "committer"="Bugzilla user". See also the + [usermap] section. + + [usermap] + Any entries in this section specify mappings of Mercurial committer ID + to Bugzilla user ID. See also [bugzilla].usermap. + "committer"="Bugzilla user" + + [web] + baseurl Base URL for browsing Mercurial repositories. Reference from + templates as {hgweb}. + +Activating the extension: + + [extensions] + hgext.bugzilla = + + [hooks] + # run bugzilla hook on every change pulled or pushed in here + incoming.bugzilla = python:hgext.bugzilla.hook + +Example configuration: + +This example configuration is for a collection of Mercurial repositories +in /var/local/hg/repos/ used with a local Bugzilla 3.2 installation in +/opt/bugzilla-3.2. + + [bugzilla] + host=localhost + password=XYZZY + version=3.0 + bzuser=unknown@domain.com + notify=cd /opt/bugzilla-3.2 && perl -T contrib/sendbugmail.pl %%s bugmail@domain.com + template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n + strip=5 + + [web] + baseurl=http://dev.domain.com/hg + + [usermap] + user@emaildomain.com=user.name@bugzilladomain.com + +Commits add a comment to the Bugzilla bug record of the form: + + Changeset 3b16791d6642 in repository-name. + http://dev.domain.com/hg/repository-name/rev/3b16791d6642 + + Changeset commit comment. Bug 1234. +''' from mercurial.i18n import _ from mercurial.node import short @@ -185,6 +239,7 @@ self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid) values (%s, %s, %s, %s)''', (bugid, userid, now, self.longdesc_id)) + self.conn.commit() class bugzilla_3_0(bugzilla_2_16): '''support for bugzilla 3.0 series.''' @@ -291,7 +346,7 @@ tmpl = templater.parsestring(tmpl, quoted=False) t.use_template(tmpl) self.ui.pushbuffer() - t.show(changenode=ctx.node(), changes=ctx.changeset(), + t.show(ctx, changes=ctx.changeset(), bug=str(bugid), hgweb=self.ui.config('web', 'baseurl'), root=self.repo.root, diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/children.py --- a/hgext/children.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/children.py Wed Dec 31 15:04:18 2008 +0100 @@ -28,8 +28,8 @@ ctx = repo[rev] displayer = cmdutil.show_changeset(ui, repo, opts) - for node in [cp.node() for cp in ctx.children()]: - displayer.show(changenode=node) + for cctx in ctx.children(): + displayer.show(cctx) cmdtable = { diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/churn.py --- a/hgext/churn.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/churn.py Wed Dec 31 15:04:18 2008 +0100 @@ -12,27 +12,6 @@ import os, sys import time, datetime -def get_tty_width(): - if 'COLUMNS' in os.environ: - try: - return int(os.environ['COLUMNS']) - except ValueError: - pass - try: - import termios, array, fcntl - for dev in (sys.stdout, sys.stdin): - try: - fd = dev.fileno() - if not os.isatty(fd): - continue - arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8) - return array.array('h', arri)[1] - except ValueError: - pass - except ImportError: - pass - return 80 - def maketemplater(ui, repo, tmpl): tmpl = templater.parsestring(tmpl, quoted=False) try: @@ -63,7 +42,7 @@ tmpl = maketemplater(ui, repo, tmpl) def getkey(ctx): ui.pushbuffer() - tmpl.show(changenode=ctx.node()) + tmpl.show(ctx) return ui.popbuffer() count = pct = 0 @@ -157,7 +136,7 @@ maxcount = float(max([v for k, v in rate])) maxname = max([len(k) for k, v in rate]) - ttywidth = get_tty_width() + ttywidth = util.termwidth() ui.debug(_("assuming %i character terminal\n") % ttywidth) width = ttywidth - maxname - 2 - 6 - 2 - 2 diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/color.py --- a/hgext/color.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/color.py Wed Dec 31 15:04:18 2008 +0100 @@ -16,15 +16,17 @@ # 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 +'''add color output to status, qseries, and diff-related 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. +reflect file status, the qseries command to add color to reflect patch status +(applied, unapplied, missing), and to diff-related commands to highlight +additions, removals, diff headers, and trailing whitespace. + +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] @@ -47,70 +49,70 @@ qseries.applied = blue bold underline qseries.unapplied = black bold qseries.missing = red bold + +diff.diffline = bold +diff.extended = cyan bold +diff.file_a = red bold +diff.file_b = green bold +diff.hunk = magenta +diff.deleted = red +diff.inserted = green +diff.changed = white +diff.trailingwhitespace = bold red_background ''' -import re, sys +import os, re, sys -from mercurial import commands, cmdutil, extensions +from mercurial import cmdutil, commands, extensions 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), } +_effect_params = {'none': 0, + 'black': 30, + 'red': 31, + 'green': 32, + 'yellow': 33, + 'blue': 34, + 'magenta': 35, + 'cyan': 36, + 'white': 37, + 'bold': 1, + 'italic': 3, + 'underline': 4, + 'inverse': 7, + 'black_background': 40, + 'red_background': 41, + 'green_background': 42, + 'yellow_background': 43, + 'blue_background': 44, + 'purple_background': 45, + 'cyan_background': 46, + 'white_background': 47} def render_effects(text, *effects): 'Wrap text in commands to turn on each effect.' - start = [ str(_effect_params['none'][0]) ] - stop = [] - for effect in effects: - start.append(str(_effect_params[effect][0])) - stop.append(str(_effect_params[effect][1])) - stop.append(str(_effect_params['none'][1])) + start = [str(_effect_params[e]) for e in ('none',) + effects] start = '\033[' + ';'.join(start) + 'm' - stop = '\033[' + ';'.join(stop) + 'm' - return start + text + stop + stop = '\033[' + str(_effect_params['none']) + 'm' + return ''.join([start, text, stop]) def colorstatus(orig, 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 + nostatus = opts.get('no_status') + opts['no_status'] = False + # run status and capture its output ui.pushbuffer() retval = orig(ui, repo, *pats, **opts) # filter out empty strings - lines = [ line for line in ui.popbuffer().split(delimiter) if line ] + lines_with_status = [ 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 ] + if nostatus: + lines = [l[2:] for l in lines_with_status] else: - lines_with_status = lines + lines = lines_with_status # apply color to output and display it for i in xrange(0, len(lines)): @@ -118,7 +120,7 @@ effects = _status_effects[status] if effects: lines[i] = render_effects(lines[i], *effects) - sys.stdout.write(lines[i] + delimiter) + ui.write(lines[i] + delimiter) return retval _status_abbreviations = { 'M': 'modified', @@ -159,30 +161,105 @@ effects = _patch_effects['applied'] else: effects = _patch_effects['unapplied'] - sys.stdout.write(render_effects(patch, *effects) + '\n') + ui.write(render_effects(patch, *effects) + '\n') return retval _patch_effects = { 'applied': ('blue', 'bold', 'underline'), 'missing': ('red', 'bold'), 'unapplied': ('black', 'bold'), } +def colorwrap(orig, s): + '''wrap ui.write for colored diff output''' + lines = s.split('\n') + for i, line in enumerate(lines): + stripline = line + if line and line[0] in '+-': + # highlight trailing whitespace, but only in changed lines + stripline = line.rstrip() + for prefix, style in _diff_prefixes: + if stripline.startswith(prefix): + lines[i] = render_effects(stripline, *_diff_effects[style]) + break + if line != stripline: + lines[i] += render_effects( + line[len(stripline):], *_diff_effects['trailingwhitespace']) + orig('\n'.join(lines)) + +def colorshowpatch(orig, self, node): + '''wrap cmdutil.changeset_printer.showpatch with colored output''' + oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap) + try: + orig(self, node) + finally: + self.ui.write = oldwrite + +def colordiff(orig, ui, repo, *pats, **opts): + '''run the diff command with colored output''' + oldwrite = extensions.wrapfunction(ui, 'write', colorwrap) + try: + orig(ui, repo, *pats, **opts) + finally: + ui.write = oldwrite + +_diff_prefixes = [('diff', 'diffline'), + ('copy', 'extended'), + ('rename', 'extended'), + ('old', 'extended'), + ('new', 'extended'), + ('deleted', 'extended'), + ('---', 'file_a'), + ('+++', 'file_b'), + ('@', 'hunk'), + ('-', 'deleted'), + ('+', 'inserted')] + +_diff_effects = {'diffline': ('bold',), + 'extended': ('cyan', 'bold'), + 'file_a': ('red', 'bold'), + 'file_b': ('green', 'bold'), + 'hunk': ('magenta',), + 'deleted': ('red',), + 'inserted': ('green',), + 'changed': ('white',), + 'trailingwhitespace': ('bold', 'red_background'),} + def uisetup(ui): '''Initialize the extension.''' + _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects) + _setupcmd(ui, 'incoming', commands.table, None, _diff_effects) + _setupcmd(ui, 'log', commands.table, None, _diff_effects) + _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects) + _setupcmd(ui, 'tip', commands.table, None, _diff_effects) _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects) if ui.config('extensions', 'hgext.mq') is not None or \ ui.config('extensions', 'mq') is not None: from hgext import mq + _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects) _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects) def _setupcmd(ui, cmd, table, func, effectsmap): '''patch in command to command table and load effect map''' - def nocolor(orig, *args, **kwargs): - if kwargs['no_color']: - return orig(*args, **kwargs) - return func(orig, *args, **kwargs) + def nocolor(orig, *args, **opts): + + if (opts['no_color'] or opts['color'] == 'never' or + (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb' + or not sys.__stdout__.isatty()))): + return orig(*args, **opts) + + oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer, + 'showpatch', colorshowpatch) + try: + if func is not None: + return func(orig, *args, **opts) + return orig(*args, **opts) + finally: + cmdutil.changeset_printer.showpatch = oldshowpatch entry = extensions.wrapcommand(table, cmd, nocolor) - entry[1].append(('', 'no-color', None, _("don't colorize output"))) + entry[1].extend([ + ('', 'color', 'auto', _("when to colorize (always, auto, or never)")), + ('', 'no-color', None, _("don't colorize output")), + ]) for status in effectsmap: effects = ui.config('color', cmd + '.' + status) diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/convert/__init__.py --- a/hgext/convert/__init__.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/convert/__init__.py Wed Dec 31 15:04:18 2008 +0100 @@ -7,6 +7,7 @@ '''converting foreign VCS repositories to Mercurial''' import convcmd +import cvsps from mercurial import commands from mercurial.i18n import _ @@ -183,7 +184,18 @@ def debugsvnlog(ui, **opts): return convcmd.debugsvnlog(ui, **opts) -commands.norepo += " convert debugsvnlog" +def debugcvsps(ui, *args, **opts): + '''Create changeset information from CVS + + This command is intended as a debugging tool for the CVS to Mercurial + converter, and can be used as a direct replacement for cvsps. + + Hg debugcvsps reads the CVS rlog for current directory (or any named + directory) in the CVS repository, and converts the log to a series of + changesets based on matching commit log entries and dates.''' + return cvsps.debugcvsps(ui, *args, **opts) + +commands.norepo += " convert debugsvnlog debugcvsps" cmdtable = { "convert": @@ -200,4 +212,22 @@ (debugsvnlog, [], 'hg debugsvnlog'), + "debugcvsps": + (debugcvsps, + [ + # Main options shared with cvsps-2.1 + ('b', 'branches', [], _('Only return changes on specified branches')), + ('p', 'prefix', '', _('Prefix to remove from file names')), + ('r', 'revisions', [], _('Only return changes after or between specified tags')), + ('u', 'update-cache', None, _("Update cvs log cache")), + ('x', 'new-cache', None, _("Create new cvs log cache")), + ('z', 'fuzz', 60, _('Set commit time fuzz in seconds')), + ('', 'root', '', _('Specify cvsroot')), + # Options specific to builtin cvsps + ('', 'parents', '', _('Show parent changesets')), + ('', 'ancestors', '', _('Show current changeset in ancestor branches')), + # Options that are ignored for compatibility with cvsps-2.1 + ('A', 'cvs-direct', None, 'Ignored for compatibility'), + ], + 'hg debugcvsps [OPTION]... [PATH]...'), } diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/convert/cvs.py --- a/hgext/convert/cvs.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/convert/cvs.py Wed Dec 31 15:04:18 2008 +0100 @@ -1,6 +1,6 @@ # CVS conversion code inspired by hg-cvs-import and git-cvsimport -import os, locale, re, socket +import os, locale, re, socket, errno from cStringIO import StringIO from mercurial import util from mercurial.i18n import _ @@ -103,18 +103,18 @@ if maxrev and int(id) > maxrev: # ignore everything state = 3 - elif l.startswith("Date"): + elif l.startswith("Date:"): date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) date = util.datestr(date) - elif l.startswith("Branch"): + elif l.startswith("Branch:"): branch = l[8:-1] self.parent[id] = self.lastbranch.get(branch, 'bad') self.lastbranch[branch] = id - elif l.startswith("Ancestor branch"): + elif l.startswith("Ancestor branch:"): ancestor = l[17:-1] # figure out the parent later self.parent[id] = self.lastbranch[ancestor] - elif l.startswith("Author"): + elif l.startswith("Author:"): author = self.recode(l[8:-1]) elif l.startswith("Tag:") or l.startswith("Tags:"): t = l[l.index(':')+1:] @@ -144,11 +144,11 @@ if branch == "HEAD": branch = "" if branch: - latest = None + latest = 0 # the last changeset that contains a base # file is our parent for r in oldrevs: - latest = max(filerevids.get(r, None), latest) + latest = max(filerevids.get(r, 0), latest) if latest: p = [latest] @@ -201,20 +201,27 @@ if not passw: passw = "A" - pf = open(os.path.expanduser("~/.cvspass")) - for line in pf.read().splitlines(): - part1, part2 = line.split(' ', 1) - if part1 == '/1': - # /1 :pserver:user@example.com:2401/cvsroot/foo Ah -# -# This software may be used and distributed according to the terms -# of the GNU General Public License, incorporated herein by reference. - -import sys -from mercurial import util -from mercurial.i18n import _ -from optparse import OptionParser, SUPPRESS_HELP -from hgext.convert.cvsps import createlog, createchangeset, logerror - -def main(): - '''Main program to mimic cvsps.''' - - op = OptionParser(usage='%prog [-bpruvxz] path', - description='Read CVS rlog for current directory or named ' - 'path in repository, and convert the log to changesets ' - 'based on matching commit log entries and dates.') - - # Options that are ignored for compatibility with cvsps-2.1 - op.add_option('-A', dest='Ignore', action='store_true', help=SUPPRESS_HELP) - op.add_option('--cvs-direct', dest='Ignore', action='store_true', help=SUPPRESS_HELP) - op.add_option('-q', dest='Ignore', action='store_true', help=SUPPRESS_HELP) - - # Main options shared with cvsps-2.1 - op.add_option('-b', dest='Branches', action='append', default=[], - help='Only return changes on specified branches') - op.add_option('-p', dest='Prefix', action='store', default='', - help='Prefix to remove from file names') - op.add_option('-r', dest='Revisions', action='append', default=[], - help='Only return changes after or between specified tags') - op.add_option('-u', dest='Cache', action='store_const', const='update', - help="Update cvs log cache") - op.add_option('-v', dest='Verbose', action='count', default=0, - help='Be verbose') - op.add_option('-x', dest='Cache', action='store_const', const='write', - help="Create new cvs log cache") - op.add_option('-z', dest='Fuzz', action='store', type='int', default=60, - help='Set commit time fuzz', metavar='seconds') - op.add_option('--root', dest='Root', action='store', default='', - help='Specify cvsroot', metavar='cvsroot') - - # Options specific to this version - op.add_option('--parents', dest='Parents', action='store_true', - help='Show parent changesets') - op.add_option('--ancestors', dest='Ancestors', action='store_true', - help='Show current changeset in ancestor branches') - - options, args = op.parse_args() - - # Create a ui object for printing progress messages - class UI: - def __init__(self, verbose): - if verbose: - self.status = self.message - if verbose>1: - self.note = self.message - if verbose>2: - self.debug = self.message - def message(self, msg): - sys.stderr.write(msg) - def nomessage(self, msg): - pass - status = nomessage - note = nomessage - debug = nomessage - ui = UI(options.Verbose) - - try: - if args: - log = [] - for d in args: - log += createlog(ui, d, root=options.Root, cache=options.Cache) - else: - log = createlog(ui, root=options.Root, cache=options.Cache) - except logerror, e: - print e - return - - changesets = createchangeset(ui, log, options.Fuzz) - del log - - # Print changesets (optionally filtered) - - off = len(options.Revisions) - branches = {} # latest version number in each branch - ancestors = {} # parent branch - for cs in changesets: - - if options.Ancestors: - if cs.branch not in branches and cs.parents and cs.parents[0].id: - ancestors[cs.branch] = changesets[cs.parents[0].id-1].branch, cs.parents[0].id - branches[cs.branch] = cs.id - - # limit by branches - if options.Branches and (cs.branch or 'HEAD') not in options.Branches: - continue - - if not off: - # Note: trailing spaces on several lines here are needed to have - # bug-for-bug compatibility with cvsps. - print '---------------------' - print 'PatchSet %d ' % cs.id - print 'Date: %s' % util.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2') - print 'Author: %s' % cs.author - print 'Branch: %s' % (cs.branch or 'HEAD') - print 'Tag%s: %s ' % (['', 's'][len(cs.tags)>1], - ','.join(cs.tags) or '(none)') - if options.Parents and cs.parents: - if len(cs.parents)>1: - print 'Parents: %s' % (','.join([str(p.id) for p in cs.parents])) - else: - print 'Parent: %d' % cs.parents[0].id - - if options.Ancestors: - b = cs.branch - r = [] - while b: - b, c = ancestors[b] - r.append('%s:%d:%d' % (b or "HEAD", c, branches[b])) - if r: - print 'Ancestors: %s' % (','.join(r)) - - print 'Log:' - print cs.comment - print - print 'Members: ' - for f in cs.entries: - fn = f.file - if fn.startswith(options.Prefix): - fn = fn[len(options.Prefix):] - print '\t%s:%s->%s%s ' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL', - '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]) - print - - # have we seen the start tag? - if options.Revisions and off: - if options.Revisions[0] == str(cs.id) or \ - options.Revisions[0] in cs.tags: - off = False - - # see if we reached the end tag - if len(options.Revisions)>1 and not off: - if options.Revisions[1] == str(cs.id) or \ - options.Revisions[1] in cs.tags: - break - - -if __name__ == '__main__': - main() diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/convert/cvsps.py --- a/hgext/convert/cvsps.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/convert/cvsps.py Wed Dec 31 15:04:18 2008 +0100 @@ -584,3 +584,95 @@ ui.status(_('%d changeset entries\n') % len(changesets)) return changesets + + +def debugcvsps(ui, *args, **opts): + '''Read CVS rlog for current directory or named path in repository, and + convert the log to changesets based on matching commit log entries and dates.''' + + if opts["new_cache"]: + cache = "write" + elif opts["update_cache"]: + cache = "update" + else: + cache = None + + revisions = opts["revisions"] + + try: + if args: + log = [] + for d in args: + log += createlog(ui, d, root=opts["root"], cache=cache) + else: + log = createlog(ui, root=opts["root"], cache=cache) + except logerror, e: + ui.write("%r\n"%e) + return + + changesets = createchangeset(ui, log, opts["fuzz"]) + del log + + # Print changesets (optionally filtered) + + off = len(revisions) + branches = {} # latest version number in each branch + ancestors = {} # parent branch + for cs in changesets: + + if opts["ancestors"]: + if cs.branch not in branches and cs.parents and cs.parents[0].id: + ancestors[cs.branch] = changesets[cs.parents[0].id-1].branch, cs.parents[0].id + branches[cs.branch] = cs.id + + # limit by branches + if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]: + continue + + if not off: + # Note: trailing spaces on several lines here are needed to have + # bug-for-bug compatibility with cvsps. + ui.write('---------------------\n') + ui.write('PatchSet %d \n' % cs.id) + ui.write('Date: %s\n' % util.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2')) + ui.write('Author: %s\n' % cs.author) + ui.write('Branch: %s\n' % (cs.branch or 'HEAD')) + ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags)>1], + ','.join(cs.tags) or '(none)')) + if opts["parents"] and cs.parents: + if len(cs.parents)>1: + ui.write('Parents: %s\n' % (','.join([str(p.id) for p in cs.parents]))) + else: + ui.write('Parent: %d\n' % cs.parents[0].id) + + if opts["ancestors"]: + b = cs.branch + r = [] + while b: + b, c = ancestors[b] + r.append('%s:%d:%d' % (b or "HEAD", c, branches[b])) + if r: + ui.write('Ancestors: %s\n' % (','.join(r))) + + ui.write('Log:\n') + ui.write('%s\n\n' % cs.comment) + ui.write('Members: \n') + for f in cs.entries: + fn = f.file + if fn.startswith(opts["prefix"]): + fn = fn[len(opts["prefix"]):] + ui.write('\t%s:%s->%s%s \n' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL', + '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead])) + ui.write('\n') + + # have we seen the start tag? + if revisions and off: + if revisions[0] == str(cs.id) or \ + revisions[0] in cs.tags: + off = False + + # see if we reached the end tag + if len(revisions)>1 and not off: + if revisions[1] == str(cs.id) or \ + revisions[1] in cs.tags: + break diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/convert/subversion.py --- a/hgext/convert/subversion.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/convert/subversion.py Wed Dec 31 15:04:18 2008 +0100 @@ -32,8 +32,8 @@ from cStringIO import StringIO -from common import NoRepo, commit, converter_source, encodeargs, decodeargs -from common import commandline, converter_sink, mapfile +from common import NoRepo, MissingTool, commit, encodeargs, decodeargs +from common import commandline, converter_source, converter_sink, mapfile try: from svn.core import SubversionException, Pool @@ -46,6 +46,9 @@ except ImportError: pass +class SvnPathNotFound(Exception): + pass + def geturl(path): try: return svn.client.url_from_path(svn.core.svn_path_canonicalize(path)) @@ -152,7 +155,16 @@ try: SubversionException except NameError: - raise NoRepo('Subversion python bindings could not be loaded') + raise MissingTool(_('Subversion python bindings could not be loaded')) + + try: + version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR + if version < (1, 4): + raise MissingTool(_('Subversion python bindings %d.%d found, ' + '1.4 or later required') % version) + except AttributeError: + raise MissingTool(_('Subversion python bindings are too old, 1.4 ' + 'or later required')) self.encoding = locale.getpreferredencoding() self.lastrevs = {} @@ -414,9 +426,15 @@ remainings.append([source, sourcerev, tagname]) continue # From revision may be fake, get one with changes - tagid = self.latest(source, sourcerev) - if tagid: - tags[tagname] = tagid + try: + tagid = self.latest(source, sourcerev) + if tagid: + tags[tagname] = tagid + except SvnPathNotFound: + # It happens when we are following directories we assumed + # were copied with their parents but were really created + # in the tag directory. + pass pendings = remainings tagspath = srctagspath @@ -474,7 +492,7 @@ except SubversionException: dirent = None if not dirent: - raise util.Abort(_('%s not found up to revision %d') % (path, stop)) + raise SvnPathNotFound(_('%s not found up to revision %d') % (path, stop)) # stat() gives us the previous revision on this line of development, but # it might be in *another module*. Fetch the log and detect renames down @@ -706,12 +724,6 @@ self.child_cset = None - def isdescendantof(parent, child): - if not child or not parent or not child.startswith(parent): - return False - subpath = child[len(parent):] - return len(subpath) > 1 and subpath[0] == '/' - def parselogentry(orig_paths, revnum, author, date, message): """Return the parsed commit object or None, and True if the revision is a branch root. @@ -734,21 +746,10 @@ if root_paths: path, ent = root_paths[-1] if ent.copyfrom_path: - # If dir was moved while one of its file was removed - # the log may look like: - # A /dir (from /dir:x) - # A /dir/a (from /dir/a:y) - # A /dir/b (from /dir/b:z) - # ... - # for all remaining children. - # Let's take the highest child element from rev as source. - copies = [(p,e) for p,e in orig_paths[:-1] - if isdescendantof(ent.copyfrom_path, e.copyfrom_path)] - fromrev = max([e.copyfrom_rev for p,e in copies] + [ent.copyfrom_rev]) branched = True newpath = ent.copyfrom_path + self.module[len(path):] # ent.copyfrom_rev may not be the actual last revision - previd = self.latest(newpath, fromrev) + previd = self.latest(newpath, ent.copyfrom_rev) if previd is not None: prevmodule, prevnum = self.revsplit(previd)[1:] if prevnum >= self.startrev: @@ -834,7 +835,7 @@ latest = self.latest(self.module, firstrevnum - 1) if latest: firstcset.parents.append(latest) - except util.Abort: + except SvnPathNotFound: pass except SubversionException, (inst, num): if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION: @@ -842,7 +843,6 @@ raise def _getfile(self, file, rev): - io = StringIO() # TODO: ra.get_file transmits the whole file instead of diffs. mode = '' try: @@ -850,7 +850,12 @@ if self.module != new_module: self.module = new_module self.reparent(self.module) + io = StringIO() info = svn.ra.get_file(self.ra, file, revnum, io) + data = io.getvalue() + # ra.get_files() seems to keep a reference on the input buffer + # preventing collection. Release it explicitely. + io.close() if isinstance(info, list): info = info[-1] mode = ("svn:executable" in info) and 'x' or '' @@ -861,7 +866,6 @@ if e.apr_err in notfound: # File not found raise IOError() raise - data = io.getvalue() if mode == 'l': link_prefix = "link " if data.startswith(link_prefix): diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/graphlog.py --- a/hgext/graphlog.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/graphlog.py Wed Dec 31 15:04:18 2008 +0100 @@ -4,130 +4,112 @@ # # This software may be used and distributed according to the terms of # the GNU General Public License, incorporated herein by reference. -'''show revision graphs in terminal windows''' +'''show revision graphs in terminal windows + +This extension adds a --graph option to the incoming, outgoing and log +commands. When this options is given, an ascii representation of the +revision graph is also shown. +''' import os import sys from mercurial.cmdutil import revrange, show_changeset -from mercurial.commands import templateopts +from mercurial.commands import templateopts, logopts, remoteopts from mercurial.i18n import _ from mercurial.node import nullrev from mercurial.util import Abort, canonpath -from mercurial import util - -def revision_grapher(repo, start_rev, stop_rev): - """incremental revision grapher - - This generator function walks through the revision history from - revision start_rev to revision stop_rev (which must be less than - or equal to start_rev) and for each revision emits tuples with the - following elements: +from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions +from mercurial import hg, ui, url - - Current revision. - - Current node. - - Column of the current node in the set of ongoing edges. - - Edges; a list of (col, next_col) indicating the edges between - the current node and its parents. - - Number of columns (ongoing edges) in the current revision. - - The difference between the number of columns (ongoing edges) - in the next revision and the number of columns (ongoing edges) - in the current revision. That is: -1 means one column removed; - 0 means no columns added or removed; 1 means one column added. - """ - - assert start_rev >= stop_rev - curr_rev = start_rev - revs = [] - while curr_rev >= stop_rev: - node = repo.changelog.node(curr_rev) - - # Compute revs and next_revs. - if curr_rev not in revs: - # New head. - revs.append(curr_rev) - rev_index = revs.index(curr_rev) - next_revs = revs[:] +def revisions(repo, start, stop): + """cset DAG generator yielding (rev, node, [parents]) tuples - # Add parents to next_revs. - parents = get_rev_parents(repo, curr_rev) - parents_to_add = [] - for parent in parents: - if parent not in next_revs: - parents_to_add.append(parent) - next_revs[rev_index:rev_index + 1] = util.sort(parents_to_add) - - edges = [] - for parent in parents: - edges.append((rev_index, next_revs.index(parent))) - - n_columns_diff = len(next_revs) - len(revs) - yield (curr_rev, node, rev_index, edges, len(revs), n_columns_diff) - - revs = next_revs - curr_rev -= 1 + This generator function walks through the revision history from revision + start to revision stop (which must be less than or equal to start). + """ + assert start >= stop + cur = start + while cur >= stop: + ctx = repo[cur] + parents = [p.rev() for p in ctx.parents() if p.rev() != nullrev] + parents.sort() + yield (ctx, parents) + cur -= 1 -def filelog_grapher(repo, path, start_rev, stop_rev): - """incremental file log grapher - - This generator function walks through the revision history of a - single file from revision start_rev to revision stop_rev (which must - be less than or equal to start_rev) and for each revision emits - tuples with the following elements: +def filerevs(repo, path, start, stop): + """file cset DAG generator yielding (rev, node, [parents]) tuples - - Current revision. - - Current node. - - Column of the current node in the set of ongoing edges. - - Edges; a list of (col, next_col) indicating the edges between - the current node and its parents. - - Number of columns (ongoing edges) in the current revision. - - The difference between the number of columns (ongoing edges) - in the next revision and the number of columns (ongoing edges) - in the current revision. That is: -1 means one column removed; - 0 means no columns added or removed; 1 means one column added. + This generator function walks through the revision history of a single + file from revision start to revision stop (which must be less than or + equal to start). """ - - assert start_rev >= stop_rev - revs = [] + assert start >= stop filerev = len(repo.file(path)) - 1 while filerev >= 0: fctx = repo.filectx(path, fileid=filerev) - - # Compute revs and next_revs. - if filerev not in revs: - revs.append(filerev) - rev_index = revs.index(filerev) - next_revs = revs[:] - - # Add parents to next_revs. - parents = [f.filerev() for f in fctx.parents() if f.path() == path] - parents_to_add = [] - for parent in parents: - if parent not in next_revs: - parents_to_add.append(parent) - next_revs[rev_index:rev_index + 1] = util.sort(parents_to_add) - - edges = [] - for parent in parents: - edges.append((rev_index, next_revs.index(parent))) - - changerev = fctx.linkrev() - if changerev <= start_rev: - node = repo.changelog.node(changerev) - n_columns_diff = len(next_revs) - len(revs) - yield (changerev, node, rev_index, edges, len(revs), n_columns_diff) - if changerev <= stop_rev: + parents = [f.linkrev() for f in fctx.parents() if f.path() == path] + parents.sort() + if fctx.rev() <= start: + yield (fctx, parents) + if fctx.rev() <= stop: break - revs = next_revs filerev -= 1 -def get_rev_parents(repo, rev): - return [x for x in repo.changelog.parentrevs(rev) if x != nullrev] +def grapher(nodes): + """grapher for asciigraph on a list of nodes and their parents + + nodes must generate tuples (node, parents, char, lines) where + - parents must generate the parents of node, in sorted order, + and max length 2, + - char is the char to print as the node symbol, and + - lines are the lines to display next to the node. + """ + seen = [] + for node, parents, char, lines in nodes: + if node not in seen: + seen.append(node) + nodeidx = seen.index(node) + + knownparents = [] + newparents = [] + for parent in parents: + if parent in seen: + knownparents.append(parent) + else: + newparents.append(parent) + + ncols = len(seen) + nextseen = seen[:] + nextseen[nodeidx:nodeidx + 1] = newparents + edges = [(nodeidx, nextseen.index(p)) for p in knownparents] + + if len(newparents) > 0: + edges.append((nodeidx, nodeidx)) + if len(newparents) > 1: + edges.append((nodeidx, nodeidx + 1)) + nmorecols = len(nextseen) - ncols + seen = nextseen + yield (char, lines, nodeidx, edges, ncols, nmorecols) def fix_long_right_edges(edges): for (i, (start, end)) in enumerate(edges): if end > start: edges[i] = (start, end + 1) +def get_nodeline_edges_tail( + node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): + if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: + # Still going in the same non-vertical direction. + if n_columns_diff == -1: + start = max(node_index + 1, p_node_index) + tail = ["|", " "] * (start - node_index - 1) + tail.extend(["/", " "] * (n_columns - start)) + return tail + else: + return ["\\", " "] * (n_columns - node_index - 1) + else: + return ["|", " "] * (n_columns - node_index - 1) + def draw_edges(edges, nodeline, interline): for (start, end) in edges: if start == end + 1: @@ -144,24 +126,6 @@ if nodeline[i] != "+": nodeline[i] = "-" -def format_line(line, level, logstr): - text = "%-*s %s" % (2 * level, "".join(line), logstr) - return "%s\n" % text.rstrip() - -def get_nodeline_edges_tail( - node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail): - if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0: - # Still going in the same non-vertical direction. - if n_columns_diff == -1: - start = max(node_index + 1, p_node_index) - tail = ["|", " "] * (start - node_index - 1) - tail.extend(["/", " "] * (n_columns - start)) - return tail - else: - return ["\\", " "] * (n_columns - node_index - 1) - else: - return ["|", " "] * (n_columns - node_index - 1) - def get_padding_line(ni, n_columns, edges): line = [] line.extend(["|", " "] * ni) @@ -179,6 +143,108 @@ line.extend(["|", " "] * (n_columns - ni - 1)) return line +def ascii(ui, grapher): + """prints an ASCII graph of the DAG returned by the grapher + + grapher is a generator that emits tuples with the following elements: + + - Character to use as node's symbol. + - List of lines to display as the node's text. + - Column of the current node in the set of ongoing edges. + - Edges; a list of (col, next_col) indicating the edges between + the current node and its parents. + - Number of columns (ongoing edges) in the current revision. + - The difference between the number of columns (ongoing edges) + in the next revision and the number of columns (ongoing edges) + in the current revision. That is: -1 means one column removed; + 0 means no columns added or removed; 1 means one column added. + """ + prev_n_columns_diff = 0 + prev_node_index = 0 + for (node_ch, node_lines, node_index, edges, n_columns, n_columns_diff) in grapher: + + assert -2 < n_columns_diff < 2 + if n_columns_diff == -1: + # Transform + # + # | | | | | | + # o | | into o---+ + # |X / |/ / + # | | | | + fix_long_right_edges(edges) + + # add_padding_line says whether to rewrite + # + # | | | | | | | | + # | o---+ into | o---+ + # | / / | | | # <--- padding line + # o | | | / / + # o | | + add_padding_line = (len(node_lines) > 2 and + n_columns_diff == -1 and + [x for (x, y) in edges if x + 1 < y]) + + # fix_nodeline_tail says whether to rewrite + # + # | | o | | | | o | | + # | | |/ / | | |/ / + # | o | | into | o / / # <--- fixed nodeline tail + # | |/ / | |/ / + # o | | o | | + fix_nodeline_tail = len(node_lines) <= 2 and not add_padding_line + + # nodeline is the line containing the node character (typically o) + nodeline = ["|", " "] * node_index + nodeline.extend([node_ch, " "]) + + nodeline.extend( + get_nodeline_edges_tail( + node_index, prev_node_index, n_columns, n_columns_diff, + prev_n_columns_diff, fix_nodeline_tail)) + + # shift_interline is the line containing the non-vertical + # edges between this entry and the next + shift_interline = ["|", " "] * node_index + if n_columns_diff == -1: + n_spaces = 1 + edge_ch = "/" + elif n_columns_diff == 0: + n_spaces = 2 + edge_ch = "|" + else: + n_spaces = 3 + edge_ch = "\\" + shift_interline.extend(n_spaces * [" "]) + shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1)) + + # draw edges from the current node to its parents + draw_edges(edges, nodeline, shift_interline) + + # lines is the list of all graph lines to print + lines = [nodeline] + if add_padding_line: + lines.append(get_padding_line(node_index, n_columns, edges)) + lines.append(shift_interline) + + # make sure that there are as many graph lines as there are + # log strings + while len(node_lines) < len(lines): + node_lines.append("") + if len(lines) < len(node_lines): + extra_interline = ["|", " "] * (n_columns + n_columns_diff) + while len(lines) < len(node_lines): + lines.append(extra_interline) + + # print lines + indentation_level = max(n_columns, n_columns + n_columns_diff) + for (line, logstr) in zip(lines, node_lines): + ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr) + ui.write(ln.rstrip() + '\n') + + # ... and start over + prev_node_index = node_index + prev_n_columns_diff = n_columns_diff + def get_limit(limit_opt): if limit_opt: try: @@ -198,6 +264,14 @@ else: return (len(repo) - 1, 0) +def check_unsupported_flags(opts): + for op in ["follow", "follow_first", "date", "copies", "keyword", "remove", + "only_merges", "user", "only_branch", "prune", "newest_first", + "no_merges", "include", "exclude"]: + if op in opts and opts[op]: + raise Abort(_("--graph option is incompatible with --%s") % op) + + def graphlog(ui, repo, path=None, **opts): """show revision history alongside an ASCII revision graph @@ -208,112 +282,191 @@ directory. """ + check_unsupported_flags(opts) limit = get_limit(opts["limit"]) - (start_rev, stop_rev) = get_revs(repo, opts["rev"]) - stop_rev = max(stop_rev, start_rev - limit + 1) - if start_rev == nullrev: + start, stop = get_revs(repo, opts["rev"]) + stop = max(stop, start - limit + 1) + if start == nullrev: return - cs_printer = show_changeset(ui, repo, opts) + if path: path = canonpath(repo.root, os.getcwd(), path) - if path: - grapher = filelog_grapher(repo, path, start_rev, stop_rev) + if path: # could be reset in canonpath + revdag = filerevs(repo, path, start, stop) else: - grapher = revision_grapher(repo, start_rev, stop_rev) + revdag = revisions(repo, start, stop) + repo_parents = repo.dirstate.parents() - prev_n_columns_diff = 0 - prev_node_index = 0 + displayer = show_changeset(ui, repo, opts, buffered=True) + def graphabledag(): + for (ctx, parents) in revdag: + # log_strings is the list of all log strings to draw alongside + # the graph. + displayer.show(ctx) + lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] + char = ctx.node() in repo_parents and '@' or 'o' + yield (ctx.rev(), parents, char, lines) - for (rev, node, node_index, edges, n_columns, n_columns_diff) in grapher: - # log_strings is the list of all log strings to draw alongside - # the graph. - ui.pushbuffer() - cs_printer.show(rev, node) - log_strings = ui.popbuffer().split("\n")[:-1] + ascii(ui, grapher(graphabledag())) + +def outgoing_revs(ui, repo, dest, opts): + """cset DAG generator yielding (node, [parents]) tuples - if n_columns_diff == -1: - # Transform - # - # | | | | | | - # o | | into o---+ - # |X / |/ / - # | | | | - fix_long_right_edges(edges) + This generator function walks through the revisions not found + in the destination + """ + limit = cmdutil.loglimit(opts) + dest, revs, checkout = hg.parseurl( + ui.expandpath(dest or 'default-push', dest or 'default'), + opts.get('rev')) + cmdutil.setremoteconfig(ui, opts) + if revs: + revs = [repo.lookup(rev) for rev in revs] + other = hg.repository(ui, dest) + ui.status(_('comparing with %s\n') % url.hidepassword(dest)) + o = repo.findoutgoing(other, force=opts.get('force')) + if not o: + ui.status(_("no changes found\n")) + return + o = repo.changelog.nodesbetween(o, revs)[0] + o.reverse() + revdict = {} + for n in o: + revdict[repo.changectx(n).rev()]=True + count = 0 + for n in o: + if count >= limit: + break + ctx = repo.changectx(n) + parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] + parents.sort() + yield (ctx, parents) + count += 1 - # add_padding_line says whether to rewrite - # - # | | | | | | | | - # | o---+ into | o---+ - # | / / | | | # <--- padding line - # o | | | / / - # o | | - add_padding_line = (len(log_strings) > 2 and - n_columns_diff == -1 and - [x for (x, y) in edges if x + 1 < y]) +def goutgoing(ui, repo, dest=None, **opts): + """show the outgoing changesets alongside an ASCII revision graph + + Print the outgoing changesets alongside a revision graph drawn with + ASCII characters. - # fix_nodeline_tail says whether to rewrite - # - # | | o | | | | o | | - # | | |/ / | | |/ / - # | o | | into | o / / # <--- fixed nodeline tail - # | |/ / | |/ / - # o | | o | | - fix_nodeline_tail = len(log_strings) <= 2 and not add_padding_line + Nodes printed as an @ character are parents of the working + directory. + """ + check_unsupported_flags(opts) + revdag = outgoing_revs(ui, repo, dest, opts) + repo_parents = repo.dirstate.parents() + displayer = show_changeset(ui, repo, opts, buffered=True) + def graphabledag(): + for (ctx, parents) in revdag: + # log_strings is the list of all log strings to draw alongside + # the graph. + displayer.show(ctx) + lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] + char = ctx.node() in repo_parents and '@' or 'o' + yield (ctx.rev(), parents, char, lines) + + ascii(ui, grapher(graphabledag())) + +def incoming_revs(other, chlist, opts): + """cset DAG generator yielding (node, [parents]) tuples - # nodeline is the line containing the node character (@ or o). - nodeline = ["|", " "] * node_index - if node in repo_parents: - node_ch = "@" - else: - node_ch = "o" - nodeline.extend([node_ch, " "]) + This generator function walks through the revisions of the destination + not found in repo + """ + limit = cmdutil.loglimit(opts) + chlist.reverse() + revdict = {} + for n in chlist: + revdict[other.changectx(n).rev()]=True + count = 0 + for n in chlist: + if count >= limit: + break + ctx = other.changectx(n) + parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] + parents.sort() + yield (ctx, parents) + count += 1 + +def gincoming(ui, repo, source="default", **opts): + """show the incoming changesets alongside an ASCII revision graph - nodeline.extend( - get_nodeline_edges_tail( - node_index, prev_node_index, n_columns, n_columns_diff, - prev_n_columns_diff, fix_nodeline_tail)) + Print the incoming changesets alongside a revision graph drawn with + ASCII characters. + + Nodes printed as an @ character are parents of the working + directory. + """ + + check_unsupported_flags(opts) + source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev')) + cmdutil.setremoteconfig(ui, opts) + + other = hg.repository(ui, source) + ui.status(_('comparing with %s\n') % url.hidepassword(source)) + if revs: + revs = [other.lookup(rev) for rev in revs] + incoming = repo.findincoming(other, heads=revs, force=opts["force"]) + if not incoming: + try: + os.unlink(opts["bundle"]) + except: + pass + ui.status(_("no changes found\n")) + return - # shift_interline is the line containing the non-vertical - # edges between this entry and the next. - shift_interline = ["|", " "] * node_index - if n_columns_diff == -1: - n_spaces = 1 - edge_ch = "/" - elif n_columns_diff == 0: - n_spaces = 2 - edge_ch = "|" - else: - n_spaces = 3 - edge_ch = "\\" - shift_interline.extend(n_spaces * [" "]) - shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1)) - - # Draw edges from the current node to its parents. - draw_edges(edges, nodeline, shift_interline) + cleanup = None + try: + fname = opts["bundle"] + if fname or not other.local(): + # create a bundle (uncompressed if other repo is not local) + if revs is None: + cg = other.changegroup(incoming, "incoming") + else: + cg = other.changegroupsubset(incoming, revs, 'incoming') + bundletype = other.local() and "HG10BZ" or "HG10UN" + fname = cleanup = changegroup.writebundle(cg, fname, bundletype) + # keep written bundle? + if opts["bundle"]: + cleanup = None + if not other.local(): + # use the created uncompressed bundlerepo + other = bundlerepo.bundlerepository(ui, repo.root, fname) - # lines is the list of all graph lines to print. - lines = [nodeline] - if add_padding_line: - lines.append(get_padding_line(node_index, n_columns, edges)) - lines.append(shift_interline) + chlist = other.changelog.nodesbetween(incoming, revs)[0] + revdag = incoming_revs(other, chlist, opts) + other_parents = [] + displayer = show_changeset(ui, other, opts, buffered=True) + def graphabledag(): + for (ctx, parents) in revdag: + # log_strings is the list of all log strings to draw alongside + # the graph. + displayer.show(ctx) + lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] + char = ctx.node() in other_parents and '@' or 'o' + yield (ctx.rev(), parents, char, lines) - # Make sure that there are as many graph lines as there are - # log strings. - while len(log_strings) < len(lines): - log_strings.append("") - if len(lines) < len(log_strings): - extra_interline = ["|", " "] * (n_columns + n_columns_diff) - while len(lines) < len(log_strings): - lines.append(extra_interline) + ascii(ui, grapher(graphabledag())) + finally: + if hasattr(other, 'close'): + other.close() + if cleanup: + os.unlink(cleanup) - # Print lines. - indentation_level = max(n_columns, n_columns + n_columns_diff) - for (line, logstr) in zip(lines, log_strings): - ui.write(format_line(line, indentation_level, logstr)) +def uisetup(ui): + '''Initialize the extension.''' + _wrapcmd(ui, 'log', commands.table, graphlog) + _wrapcmd(ui, 'incoming', commands.table, gincoming) + _wrapcmd(ui, 'outgoing', commands.table, goutgoing) - # ...and start over. - prev_node_index = node_index - prev_n_columns_diff = n_columns_diff +def _wrapcmd(ui, cmd, table, wrapfn): + '''wrap the command''' + def graph(orig, *args, **kwargs): + if kwargs['graph']: + return wrapfn(*args, **kwargs) + return orig(*args, **kwargs) + entry = extensions.wrapcommand(table, cmd, graph) + entry[1].append(('g', 'graph', None, _("show the revision DAG"))) cmdtable = { "glog": diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/hgcia.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/hgcia.py Wed Dec 31 15:04:18 2008 +0100 @@ -0,0 +1,245 @@ +# Copyright (C) 2007-8 Brendan Cully +# Published under the GNU GPL + +"""CIA notification + +This is meant to be run as a changegroup or incoming hook. +To configure it, set the following options in your hgrc: + +[cia] +# your registered CIA user name +user = foo +# the name of the project in CIA +project = foo +# the module (subproject) (optional) +#module = foo +# Append a diffstat to the log message (optional) +#diffstat = False +# Template to use for log messages (optional) +#template = {desc}\n{baseurl}/rev/{node}-- {diffstat} +# Style to use (optional) +#style = foo +# The URL of the CIA notification service (optional) +# You can use mailto: URLs to send by email, eg +# mailto:cia@cia.vc +# Make sure to set email.from if you do this. +#url = http://cia.vc/ +# print message instead of sending it (optional) +#test = False + +[hooks] +# one of these: +changegroup.cia = python:hgcia.hook +#incoming.cia = python:hgcia.hook + +[web] +# If you want hyperlinks (optional) +baseurl = http://server/path/to/repo +""" + +from mercurial.i18n import _ +from mercurial.node import * +from mercurial import cmdutil, patch, templater, util, mail +import email.Parser + +import xmlrpclib +from xml.sax import saxutils + +socket_timeout = 30 # seconds +try: + # set a timeout for the socket so you don't have to wait so looooong + # when cia.vc is having problems. requires python >= 2.3: + import socket + socket.setdefaulttimeout(socket_timeout) +except: + pass + +HGCIA_VERSION = '0.1' +HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia' + + +class ciamsg(object): + """ A CIA message """ + def __init__(self, cia, ctx): + self.cia = cia + self.ctx = ctx + self.url = self.cia.url + + def fileelem(self, path, uri, action): + if uri: + uri = ' uri=%s' % saxutils.quoteattr(uri) + return '%s' % ( + uri, saxutils.quoteattr(action), saxutils.escape(path)) + + def fileelems(self): + n = self.ctx.node() + f = self.cia.repo.status(self.ctx.parents()[0].node(), n) + url = self.url or '' + elems = [] + for path in f[0]: + uri = '%s/diff/%s/%s' % (url, short(n), path) + elems.append(self.fileelem(path, url and uri, 'modify')) + for path in f[1]: + # TODO: copy/rename ? + uri = '%s/file/%s/%s' % (url, short(n), path) + elems.append(self.fileelem(path, url and uri, 'add')) + for path in f[2]: + elems.append(self.fileelem(path, '', 'remove')) + + return '\n'.join(elems) + + def sourceelem(self, project, module=None, branch=None): + msg = ['', '%s' % saxutils.escape(project)] + if module: + msg.append('%s' % saxutils.escape(module)) + if branch: + msg.append('%s' % saxutils.escape(branch)) + msg.append('') + + return '\n'.join(msg) + + def diffstat(self): + class patchbuf: + def __init__(self): + self.lines = [] + # diffstat is stupid + self.name = 'cia' + def write(self, data): + self.lines.append(data) + def close(self): + pass + + n = self.ctx.node() + pbuf = patchbuf() + patch.export(self.cia.repo, [n], fp=pbuf) + return patch.diffstat(pbuf.lines) or '' + + def logmsg(self): + diffstat = self.cia.diffstat and self.diffstat() or '' + self.cia.ui.pushbuffer() + self.cia.templater.show(self.ctx, changes=self.ctx.changeset(), + url=self.cia.url, diffstat=diffstat) + return self.cia.ui.popbuffer() + + def xml(self): + n = short(self.ctx.node()) + src = self.sourceelem(self.cia.project, module=self.cia.module, + branch=self.ctx.branch()) + # unix timestamp + dt = self.ctx.date() + timestamp = dt[0] + + author = saxutils.escape(self.ctx.user()) + rev = '%d:%s' % (self.ctx.rev(), n) + log = saxutils.escape(self.logmsg()) + + url = self.url and '%s/rev/%s' % (saxutils.escape(self.url), + n) or '' + + msg = """ + + + Mercurial (hgcia) + %s + %s + %s + + %s + + + %s + %s + %s + %s + %s + + + %d + +""" % \ + (HGCIA_VERSION, saxutils.escape(HGCIA_URL), + saxutils.escape(self.cia.user), src, author, rev, log, url, + self.fileelems(), timestamp) + + return msg + + +class hgcia(object): + """ CIA notification class """ + + deftemplate = '{desc}' + dstemplate = '{desc}\n-- \n{diffstat}' + + def __init__(self, ui, repo): + self.ui = ui + self.repo = repo + + self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc') + self.user = self.ui.config('cia', 'user') + self.project = self.ui.config('cia', 'project') + self.module = self.ui.config('cia', 'module') + self.diffstat = self.ui.configbool('cia', 'diffstat') + self.emailfrom = self.ui.config('email', 'from') + self.dryrun = self.ui.configbool('cia', 'test') + self.url = self.ui.config('web', 'baseurl') + + style = self.ui.config('cia', 'style') + template = self.ui.config('cia', 'template') + if not template: + template = self.diffstat and self.dstemplate or self.deftemplate + template = templater.parsestring(template, quoted=False) + t = cmdutil.changeset_templater(self.ui, self.repo, False, style, False) + t.use_template(template) + self.templater = t + + def sendrpc(self, msg): + srv = xmlrpclib.Server(self.ciaurl) + srv.hub.deliver(msg) + + def sendemail(self, address, data): + p = email.Parser.Parser() + msg = p.parsestr(data) + msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2") + msg['To'] = address + msg['From'] = self.emailfrom + msg['Subject'] = 'DeliverXML' + msg['Content-type'] = 'text/xml' + msgtext = msg.as_string(0) + + self.ui.status(_('hgcia: sending update to %s\n') % address) + mail.sendmail(self.ui, util.email(self.emailfrom), + [address], msgtext) + + +def hook(ui, repo, hooktype, node=None, url=None, **kwargs): + """ send CIA notification """ + def sendmsg(cia, ctx): + msg = ciamsg(cia, ctx).xml() + if cia.dryrun: + ui.write(msg) + elif cia.ciaurl.startswith('mailto:'): + if not cia.emailfrom: + raise util.Abort(_('email.from must be defined when ' + 'sending by email')) + cia.sendemail(cia.ciaurl[7:], msg) + else: + cia.sendrpc(msg) + + n = bin(node) + cia = hgcia(ui, repo) + if not cia.user: + ui.debug(_('cia: no user specified')) + return + if not cia.project: + ui.debug(_('cia: no project specified')) + return + if hooktype == 'changegroup': + start = repo.changelog.rev(n) + end = len(repo.changelog) + for rev in xrange(start, end): + n = repo.changelog.node(rev) + ctx = repo.changectx(n) + sendmsg(cia, ctx) + else: + ctx = repo.changectx(n) + sendmsg(cia, ctx) diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/inotify/__init__.py --- a/hgext/inotify/__init__.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/inotify/__init__.py Wed Dec 31 15:04:18 2008 +0100 @@ -39,7 +39,7 @@ cmdutil.service(opts, initfn=service.init, runfn=service.run) def reposetup(ui, repo): - if not repo.local(): + if not hasattr(repo, 'dirstate'): return # XXX: weakref until hg stops relying on __del__ @@ -52,6 +52,8 @@ def status(self, match, ignored, clean, unknown=True): files = match.files() + if '.' in files: + files = [] try: if not ignored and not self.inotifyserver: result = client.query(ui, repo, files, match, False, @@ -71,22 +73,24 @@ if result is not None: return result except (OSError, socket.error), err: + autostart = ui.configbool('inotify', 'autostart', True) + if err[0] == errno.ECONNREFUSED: ui.warn(_('(found dead inotify server socket; ' 'removing it)\n')) os.unlink(repo.join('inotify.sock')) - if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and \ - ui.configbool('inotify', 'autostart', True): + if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart: query = None ui.debug(_('(starting inotify server)\n')) try: - server.start(ui, repo) - query = client.query - except server.AlreadyStartedException, inst: - # another process may have started its own - # inotify server while this one was starting. - ui.debug(str(inst)) - query = client.query + try: + server.start(ui, repo) + query = client.query + except server.AlreadyStartedException, inst: + # another process may have started its own + # inotify server while this one was starting. + ui.debug(str(inst)) + query = client.query except Exception, inst: ui.warn(_('could not start inotify server: ' '%s\n') % inst) @@ -97,8 +101,9 @@ except socket.error, err: ui.warn(_('could not talk to new inotify ' 'server: %s\n') % err[-1]) - elif err[0] == errno.ENOENT: - ui.warn(_('(inotify server not running)\n')) + elif err[0] in (errno.ECONNREFUSED, errno.ENOENT): + # silently ignore normal errors if autostart is False + ui.debug(_('(inotify server not running)\n')) else: ui.warn(_('failed to contact inotify server: %s\n') % err[-1]) diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/inotify/client.py --- a/hgext/inotify/client.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/inotify/client.py Wed Dec 31 15:04:18 2008 +0100 @@ -7,7 +7,6 @@ # of the GNU General Public License, incorporated herein by reference. from mercurial.i18n import _ -from mercurial import ui import common import os, socket, struct diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/inotify/server.py --- a/hgext/inotify/server.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/inotify/server.py Wed Dec 31 15:04:18 2008 +0100 @@ -7,7 +7,7 @@ # of the GNU General Public License, incorporated herein by reference. from mercurial.i18n import _ -from mercurial import osutil, ui, util +from mercurial import osutil, util import common import errno, os, select, socket, stat, struct, sys, tempfile, time @@ -15,7 +15,6 @@ import linux as inotify from linux import watcher except ImportError: - print >> sys.stderr, '*** native support is required for this extension' raise class AlreadyStartedException(Exception): pass @@ -709,6 +708,26 @@ timeobj.handle_timeout() def start(ui, repo): + def closefds(ignore): + # (from python bug #1177468) + # close all inherited file descriptors + # Python 2.4.1 and later use /dev/urandom to seed the random module's RNG + # a file descriptor is kept internally as os._urandomfd (created on demand + # the first time os.urandom() is called), and should not be closed + try: + os.urandom(4) + urandom_fd = getattr(os, '_urandomfd', None) + except AttributeError: + urandom_fd = None + ignore.append(urandom_fd) + for fd in range(3, 256): + if fd in ignore: + continue + try: + os.close(fd) + except OSError: + pass + m = Master(ui, repo) sys.stdout.flush() sys.stderr.flush() @@ -717,6 +736,7 @@ if pid: return pid + closefds([m.server.fileno(), m.watcher.fileno()]) os.setsid() fd = os.open('/dev/null', os.O_RDONLY) diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/keyword.py --- a/hgext/keyword.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/keyword.py Wed Dec 31 15:04:18 2008 +0100 @@ -139,19 +139,13 @@ self.ct = cmdutil.changeset_templater(self.ui, self.repo, False, '', False) - def getnode(self, path, fnode): - '''Derives changenode from file path and filenode.''' - # used by kwfilelog.read and kwexpand - c = self.repo.filectx(path, fileid=fnode) - return c.node() - - def substitute(self, data, path, node, subfunc): + def substitute(self, data, path, ctx, subfunc): '''Replaces keywords in data with expanded template.''' def kwsub(mobj): kw = mobj.group(1) self.ct.use_template(self.templates[kw]) self.ui.pushbuffer() - self.ct.show(changenode=node, root=self.repo.root, file=path) + self.ct.show(ctx, root=self.repo.root, file=path) ekw = templatefilters.firstline(self.ui.popbuffer()) return '$%s: %s $' % (kw, ekw) return subfunc(kwsub, data) @@ -159,8 +153,8 @@ def expand(self, path, node, data): '''Returns data with keywords expanded.''' if not self.restrict and self.matcher(path) and not util.binary(data): - changenode = self.getnode(path, node) - return self.substitute(data, path, changenode, self.re_kw.sub) + ctx = self.repo.filectx(path, fileid=node).changectx() + return self.substitute(data, path, ctx, self.re_kw.sub) return data def iskwfile(self, path, flagfunc): @@ -171,14 +165,12 @@ def overwrite(self, node, expand, files): '''Overwrites selected files expanding/shrinking keywords.''' + ctx = self.repo[node] + mf = ctx.manifest() if node is not None: # commit - ctx = self.repo[node] - mf = ctx.manifest() files = [f for f in ctx.files() if f in mf] notify = self.ui.debug else: # kwexpand/kwshrink - ctx = self.repo['.'] - mf = ctx.manifest() notify = self.ui.note candidates = [f for f in files if self.iskwfile(f, ctx.flags)] if candidates: @@ -190,8 +182,9 @@ if util.binary(data): continue if expand: - changenode = node or self.getnode(f, mf[f]) - data, found = self.substitute(data, f, changenode, + if node is None: + ctx = self.repo.filectx(f, fileid=mf[f]).changectx() + data, found = self.substitute(data, f, ctx, self.re_kw.subn) else: found = self.re_kw.search(data) @@ -355,8 +348,8 @@ ui.note(_('unhooked all commit hooks\n')) ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg)) repo.commit(text=msg) - format = ui.verbose and ' in %s' % path or '' - demostatus('%s keywords expanded%s' % (kwstatus, format)) + fmt = ui.verbose and ' in %s' % path or '' + demostatus('%s keywords expanded%s' % (kwstatus, fmt)) ui.write(repo.wread(fn)) ui.debug(_('\nremoving temporary repo %s\n') % tmpdir) shutil.rmtree(tmpdir, ignore_errors=True) @@ -389,9 +382,9 @@ if opts.get('all') or opts.get('ignore'): kwfstats += (('I', [f for f in files if f not in kwfiles]),) for char, filenames in kwfstats: - format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n' + fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n' for f in filenames: - ui.write(format % repo.pathto(f, cwd)) + ui.write(fmt % repo.pathto(f, cwd)) def shrink(ui, repo, *pats, **opts): '''revert expanded keywords in working directory @@ -432,14 +425,10 @@ keyword substitutions. Monkeypatches patch and webcommands.''' - try: - if (not repo.local() or not kwtools['inc'] - or kwtools['hgcmd'] in nokwcommands.split() - or '.hg' in util.splitpath(repo.root) - or repo._url.startswith('bundle:')): - return - except AttributeError: - pass + if (not hasattr(repo, 'dirstate') or not kwtools['inc'] + or kwtools['hgcmd'] in nokwcommands.split() + or '.hg' in util.splitpath(repo.root)): + return kwtools['templater'] = kwt = kwtemplater(ui, repo) @@ -494,10 +483,10 @@ del wlock, lock # monkeypatches - def kwpatchfile_init(orig, self, ui, fname, missing=False): + def kwpatchfile_init(orig, self, ui, fname, opener, missing=False): '''Monkeypatch/wrap patch.patchfile.__init__ to avoid rejects or conflicts due to expanded keywords in working dir.''' - orig(self, ui, fname, missing) + orig(self, ui, fname, opener, missing) # shrink keywords read from working dir self.lines = kwt.shrinklines(self.fname, self.lines) diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/mq.py --- a/hgext/mq.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/mq.py Wed Dec 31 15:04:18 2008 +0100 @@ -56,6 +56,67 @@ def __str__(self): return self.rev + ':' + self.name +class patchheader(object): + def __init__(self, message, comments, user, date, haspatch): + self.message = message + self.comments = comments + self.user = user + self.date = date + self.haspatch = haspatch + + def setuser(self, user): + if not self.setheader(['From: ', '# User '], user): + try: + patchheaderat = self.comments.index('# HG changeset patch') + self.comments.insert(patchheaderat + 1,'# User ' + user) + except ValueError: + self.comments = ['From: ' + user, ''] + self.comments + self.user = user + + def setdate(self, date): + if self.setheader(['# Date '], date): + self.date = date + + def setmessage(self, message): + if self.comments: + self._delmsg() + self.message = [message] + self.comments += self.message + + def setheader(self, prefixes, new): + '''Update all references to a field in the patch header. + If none found, add it email style.''' + res = False + for prefix in prefixes: + for i in xrange(len(self.comments)): + if self.comments[i].startswith(prefix): + self.comments[i] = prefix + new + res = True + break + return res + + def __str__(self): + if not self.comments: + return '' + return '\n'.join(self.comments) + '\n\n' + + def _delmsg(self): + '''Remove existing message, keeping the rest of the comments fields. + If comments contains 'subject: ', message will prepend + the field and a blank line.''' + if self.message: + subj = 'subject: ' + self.message[0].lower() + for i in xrange(len(self.comments)): + if subj == self.comments[i].lower(): + del self.comments[i] + self.message = self.message[2:] + break + ci = 0 + for mi in xrange(len(self.message)): + while self.message[mi] != self.comments[ci]: + ci += 1 + del self.comments[ci] + class queue: def __init__(self, ui, path, patchdir=None): self.basepath = path @@ -307,7 +368,7 @@ if format and format.startswith("tag") and subject: message.insert(0, "") message.insert(0, subject) - return (message, comments, user, date, diffstart > 1) + return patchheader(message, comments, user, date, diffstart > 1) def removeundo(self, repo): undo = repo.sjoin('undo') @@ -351,13 +412,13 @@ if n == None: raise util.Abort(_("repo commit failed")) try: - message, comments, user, date, patchfound = mergeq.readheaders(patch) + ph = mergeq.readheaders(patch) except: raise util.Abort(_("unable to read %s") % patch) patchf = self.opener(patch, "w") + comments = str(ph) if comments: - comments = "\n".join(comments) + '\n\n' patchf.write(comments) self.printdiff(repo, head, n, fp=patchf) patchf.close() @@ -477,12 +538,13 @@ pf = os.path.join(patchdir, patchname) try: - message, comments, user, date, patchfound = self.readheaders(patchname) + ph = self.readheaders(patchname) except: self.ui.warn(_("Unable to read %s\n") % patchname) err = 1 break + message = ph.message if not message: message = _("imported patch %s\n") % patchname else: @@ -512,7 +574,7 @@ files = patch.updatedir(self.ui, repo, files) match = cmdutil.matchfiles(repo, files or []) - n = repo.commit(files, message, user, date, match=match, + n = repo.commit(files, message, ph.user, ph.date, match=match, force=True) if n == None: @@ -522,7 +584,7 @@ self.applied.append(statusentry(revlog.hex(n), patchname)) if patcherr: - if not patchfound: + if not ph.haspatch: self.ui.warn(_("patch %s is empty\n") % patchname) err = 0 else: @@ -824,11 +886,15 @@ raise util.Abort(_("patch %s not in series") % patch) def push(self, repo, patch=None, force=False, list=False, - mergeq=None): + mergeq=None, all=False): wlock = repo.wlock() if repo.dirstate.parents()[0] != repo.changelog.tip(): self.ui.status(_("(working directory not at tip)\n")) + if not self.series: + self.ui.warn(_('no patches in series\n')) + return 0 + try: patch = self.lookup(patch) # Suppose our series file is: A B C and the current 'top' @@ -841,26 +907,36 @@ if info[0] < len(self.applied) - 1: raise util.Abort( _("cannot push to a previous patch: %s") % patch) - if info[0] < len(self.series) - 1: - self.ui.warn( - _('qpush: %s is already at the top\n') % patch) + self.ui.warn( + _('qpush: %s is already at the top\n') % patch) + return + pushable, reason = self.pushable(patch) + if not pushable: + if reason: + reason = _('guarded by %r') % reason else: - self.ui.warn(_('all patches are currently applied\n')) - return + reason = _('no matching guards') + self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason)) + return 1 + elif all: + patch = self.series[-1] + if self.isapplied(patch): + self.ui.warn(_('all patches are currently applied\n')) + return 0 # Following the above example, starting at 'top' of B: # qpush should be performed (pushes C), but a subsequent # qpush without an argument is an error (nothing to # apply). This allows a loop of "...while hg qpush..." to # work as it detects an error when done - if self.series_end() == len(self.series): + start = self.series_end() + if start == len(self.series): self.ui.warn(_('patch series already fully applied\n')) return 1 if not force: self.check_localchanges(repo) - self.applied_dirty = 1; - start = self.series_end() + self.applied_dirty = 1 if start > 0: self.check_toppatch(repo) if not patch: @@ -1001,6 +1077,8 @@ if len(self.applied) == 0: self.ui.write(_("No patches applied\n")) return 1 + msg = opts.get('msg', '').rstrip() + newuser = opts.get('user') newdate = opts.get('date') if newdate: newdate = '%d %d' % util.parsedate(newdate) @@ -1013,9 +1091,9 @@ raise util.Abort(_("cannot refresh a revision with children")) cparents = repo.changelog.parents(top) patchparent = self.qparents(repo, top) - message, comments, user, date, patchfound = self.readheaders(patchfn) + ph = self.readheaders(patchfn) - patchf = self.opener(patchfn, 'r+') + patchf = self.opener(patchfn, 'r') # if the patch was a git patch, refresh it as a git patch for line in patchf: @@ -1023,59 +1101,21 @@ self.diffopts().git = True break - msg = opts.get('msg', '').rstrip() - if msg and comments: - # Remove existing message, keeping the rest of the comments - # fields. - # If comments contains 'subject: ', message will prepend - # the field and a blank line. - if message: - subj = 'subject: ' + message[0].lower() - for i in xrange(len(comments)): - if subj == comments[i].lower(): - del comments[i] - message = message[2:] - break - ci = 0 - for mi in xrange(len(message)): - while message[mi] != comments[ci]: - ci += 1 - del comments[ci] + if msg: + ph.setmessage(msg) + if newuser: + ph.setuser(newuser) + if newdate: + ph.setdate(newdate) - def setheaderfield(comments, prefixes, new): - # Update all references to a field in the patch header. - # If none found, add it email style. - res = False - for prefix in prefixes: - for i in xrange(len(comments)): - if comments[i].startswith(prefix): - comments[i] = prefix + new - res = True - break - return res - - newuser = opts.get('user') - if newuser: - if not setheaderfield(comments, ['From: ', '# User '], newuser): - try: - patchheaderat = comments.index('# HG changeset patch') - comments.insert(patchheaderat + 1,'# User ' + newuser) - except ValueError: - comments = ['From: ' + newuser, ''] + comments - user = newuser - - if newdate: - if setheaderfield(comments, ['# Date '], newdate): - date = newdate - - if msg: - comments.append(msg) + # only commit new patch when write is complete + patchf = self.opener(patchfn, 'w', atomictemp=True) patchf.seek(0) patchf.truncate() + comments = str(ph) if comments: - comments = "\n".join(comments) + '\n\n' patchf.write(comments) if opts.get('git'): @@ -1148,69 +1188,84 @@ changes=c, opts=self.diffopts()) for chunk in chunks: patchf.write(chunk) - patchf.close() - repo.dirstate.setparents(*cparents) - copies = {} - for dst in a: - src = repo.dirstate.copied(dst) - if src is not None: - copies.setdefault(src, []).append(dst) - repo.dirstate.add(dst) - # remember the copies between patchparent and tip - # this may be slow, so don't do it if we're not tracking copies - if self.diffopts().git: - for dst in aaa: - f = repo.file(dst) - src = f.renamed(man[dst]) - if src: - copies.setdefault(src[0], []).extend(copies.get(dst, [])) - if dst in a: - copies[src[0]].append(dst) - # we can't copy a file created by the patch itself - if dst in copies: - del copies[dst] - for src, dsts in copies.iteritems(): - for dst in dsts: - repo.dirstate.copy(src, dst) - for f in r: - repo.dirstate.remove(f) - # if the patch excludes a modified file, mark that - # file with mtime=0 so status can see it. - mm = [] - for i in xrange(len(m)-1, -1, -1): - if not matchfn(m[i]): - mm.append(m[i]) - del m[i] - for f in m: - repo.dirstate.normal(f) - for f in mm: - repo.dirstate.normallookup(f) - for f in forget: - repo.dirstate.forget(f) + try: + copies = {} + for dst in a: + src = repo.dirstate.copied(dst) + # during qfold, the source file for copies may + # be removed. Treat this as a simple add. + if src is not None and src in repo.dirstate: + copies.setdefault(src, []).append(dst) + repo.dirstate.add(dst) + # remember the copies between patchparent and tip + # this may be slow, so don't do it if we're not tracking copies + if self.diffopts().git: + for dst in aaa: + f = repo.file(dst) + src = f.renamed(man[dst]) + if src: + copies.setdefault(src[0], []).extend(copies.get(dst, [])) + if dst in a: + copies[src[0]].append(dst) + # we can't copy a file created by the patch itself + if dst in copies: + del copies[dst] + for src, dsts in copies.iteritems(): + for dst in dsts: + repo.dirstate.copy(src, dst) + for f in r: + repo.dirstate.remove(f) + # if the patch excludes a modified file, mark that + # file with mtime=0 so status can see it. + mm = [] + for i in xrange(len(m)-1, -1, -1): + if not matchfn(m[i]): + mm.append(m[i]) + del m[i] + for f in m: + repo.dirstate.normal(f) + for f in mm: + repo.dirstate.normallookup(f) + for f in forget: + repo.dirstate.forget(f) - if not msg: - if not message: - message = "[mq]: %s\n" % patchfn + if not msg: + if not ph.message: + message = "[mq]: %s\n" % patchfn + else: + message = "\n".join(ph.message) else: - message = "\n".join(message) - else: - message = msg + message = msg + + user = ph.user or changes[1] - if not user: - user = changes[1] + # assumes strip can roll itself back if interrupted + repo.dirstate.setparents(*cparents) + self.applied.pop() + self.applied_dirty = 1 + self.strip(repo, top, update=False, + backup='strip') + except: + repo.dirstate.invalidate() + raise - self.applied.pop() - self.applied_dirty = 1 - self.strip(repo, top, update=False, - backup='strip') - n = repo.commit(match.files(), message, user, date, match=match, - force=1) - self.applied.append(statusentry(revlog.hex(n), patchfn)) - self.removeundo(repo) + try: + # might be nice to attempt to roll back strip after this + patchf.rename() + n = repo.commit(match.files(), message, user, ph.date, + match=match, force=1) + self.applied.append(statusentry(revlog.hex(n), patchfn)) + except: + ctx = repo[cparents[0]] + repo.dirstate.rebuild(ctx.node(), ctx.manifest()) + self.save_dirty() + self.ui.warn(_('refresh interrupted while patch was popped! ' + '(revert --all, qpush to recover)\n')) + raise else: self.printdiff(repo, patchparent, fp=patchf) - patchf.close() + patchf.rename() added = repo.status()[1] for a in added: f = repo.wjoin(a) @@ -1228,6 +1283,7 @@ self.push(repo, force=True) finally: del wlock + self.removeundo(repo) def init(self, repo, create=False): if not create and os.path.isdir(self.path): @@ -1259,7 +1315,8 @@ summary=False): def displayname(patchname): if summary: - msg = self.readheaders(patchname)[0] + ph = self.readheaders(patchname) + msg = ph.message msg = msg and ': ' + msg[0] or ': ' else: msg = '' @@ -1528,7 +1585,7 @@ text = sys.stdin.read() else: text = url.open(self.ui, filename).read() - except IOError: + except (OSError, IOError): raise util.Abort(_("unable to read %s") % filename) if not patchname: patchname = normname(os.path.basename(filename)) @@ -1607,7 +1664,7 @@ An existing changeset may be placed under mq control with --rev (e.g. qimport --rev tip -n patch will place tip under mq control). With --git, patches imported with --rev will use the git diff - format. See the gitdiffs help topic for information on why this is + format. See the diffs help topic for information on why this is important for preserving rename/copy information and permission changes. """ q = repo.mq @@ -1774,9 +1831,10 @@ -e, -m or -l set the patch header as well as the commit message. If none is specified, the header is empty and the commit message is '[mq]: PATCH'. - Use the --git option to keep the patch in the git extended diff format. - Read the gitdiffs help topic for more information on why this is - important for preserving permission changes and copy/rename information. + Use the --git option to keep the patch in the git extended diff + format. Read the diffs help topic for more information on why this + is important for preserving permission changes and copy/rename + information. """ msg = cmdutil.logmessage(opts) def getmsg(): return ui.edit(msg, ui.username()) @@ -1803,7 +1861,7 @@ hg add/remove/copy/rename work as usual, though you might want to use git-style patches (--git or [diff] git=1) to track copies and renames. - See the gitdiffs help topic for more information on the git diff format. + See the diffs help topic for more information on the git diff format. """ q = repo.mq message = cmdutil.logmessage(opts) @@ -1814,8 +1872,8 @@ if message: raise util.Abort(_('option "-e" incompatible with "-m" or "-l"')) patch = q.applied[-1].name - (message, comment, user, date, hasdiff) = q.readheaders(patch) - message = ui.edit('\n'.join(message), user or ui.username()) + ph = q.readheaders(patch) + message = ui.edit('\n'.join(ph.message), ph.user or ui.username()) setupheaderopts(ui, opts) ret = q.refresh(repo, pats, msg=message, **opts) q.save_dirty() @@ -1873,7 +1931,9 @@ for p in patches: if not message: - messages.append(q.readheaders(p)[0]) + ph = q.readheaders(p) + if ph.message: + messages.append(ph.message) pf = q.join(p) (patchsuccess, files, fuzz) = q.patch(repo, pf) if not patchsuccess: @@ -1881,7 +1941,8 @@ patch.updatedir(ui, repo, files) if not message: - message, comments, user = q.readheaders(parent)[0:3] + ph = q.readheaders(parent) + message, user = ph.message, ph.user for msg in messages: message.append('* * *') message.extend(msg) @@ -1964,9 +2025,9 @@ ui.write('No patches applied\n') return 1 patch = q.lookup('qtip') - message = repo.mq.readheaders(patch)[0] + ph = repo.mq.readheaders(patch) - ui.write('\n'.join(message) + '\n') + ui.write('\n'.join(ph.message) + '\n') def lastsavename(path): (directory, base) = os.path.split(path) @@ -2000,11 +2061,6 @@ q = repo.mq mergeq = None - if opts['all']: - if not q.series: - ui.warn(_('no patches in series\n')) - return 0 - patch = q.series[-1] if opts['merge']: if opts['name']: newpath = repo.join(opts['name']) @@ -2016,7 +2072,7 @@ mergeq = queue(ui, repo.join(""), newpath) ui.warn(_("merging with queue at: %s\n") % mergeq.path) ret = q.push(repo, patch, force=opts['force'], list=opts['list'], - mergeq=mergeq) + mergeq=mergeq, all=opts.get('all')) return ret def pop(ui, repo, patch=None, **opts): diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/notify.py --- a/hgext/notify.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/notify.py Wed Dec 31 15:04:18 2008 +0100 @@ -105,6 +105,7 @@ self.stripcount = int(self.ui.config('notify', 'strip', 0)) self.root = self.strip(self.repo.root) self.domain = self.ui.config('notify', 'domain') + self.test = self.ui.configbool('notify', 'test', True) self.charsets = mail._charsets(self.ui) self.subs = self.subscribers() @@ -157,7 +158,8 @@ for user in users.split(','): subs[self.fixmail(user)] = 1 subs = util.sort(subs) - return [mail.addressencode(self.ui, s, self.charsets) for s in subs] + return [mail.addressencode(self.ui, s, self.charsets, self.test) + for s in subs] def url(self, path=None): return self.ui.config('web', 'baseurl') + (path or self.root) @@ -165,7 +167,7 @@ def node(self, node): '''format one changeset.''' - self.t.show(changenode=node, changes=self.repo.changelog.read(node), + self.t.show(self.repo[node], changes=self.repo.changelog.read(node), baseurl=self.ui.config('web', 'baseurl'), root=self.repo.root, webroot=self.root) @@ -186,7 +188,7 @@ # create fresh mime message from msg body text = msg.get_payload() # for notification prefer readability over data precision - msg = mail.mimeencode(self.ui, text, self.charsets) + msg = mail.mimeencode(self.ui, text, self.charsets, self.test) def fix_subject(subject): '''try to make subject line exist and be useful.''' @@ -201,7 +203,8 @@ maxsubject = int(self.ui.config('notify', 'maxsubject', 67)) if maxsubject and len(subject) > maxsubject: subject = subject[:maxsubject-3] + '...' - msg['Subject'] = mail.headencode(self.ui, subject, self.charsets) + msg['Subject'] = mail.headencode(self.ui, subject, + self.charsets, self.test) def fix_sender(sender): '''try to make message have proper sender.''' @@ -210,7 +213,8 @@ sender = self.ui.config('email', 'from') or self.ui.username() if '@' not in sender or '@localhost' in sender: sender = self.fixmail(sender) - msg['From'] = mail.addressencode(self.ui, sender, self.charsets) + msg['From'] = mail.addressencode(self.ui, sender, + self.charsets, self.test) msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2") fix_subject(subject) @@ -224,7 +228,7 @@ msg['To'] = ', '.join(self.subs) msgtext = msg.as_string(0) - if self.ui.configbool('notify', 'test', True): + if self.test: self.ui.write(msgtext) if not msgtext.endswith('\n'): self.ui.write('\n') diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/patchbomb.py --- a/hgext/patchbomb.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/patchbomb.py Wed Dec 31 15:04:18 2008 +0100 @@ -9,8 +9,7 @@ The remainder of the changeset description. - [Optional] If the diffstat program is installed, the result of - running diffstat on the patch. + [Optional] The result of running diffstat on the patch. The patch itself, as generated by "hg export". @@ -68,6 +67,115 @@ from mercurial.i18n import _ from mercurial.node import bin +class exportee: + def __init__(self, container): + self.lines = [] + self.container = container + self.name = 'email' + + def write(self, data): + self.lines.append(data) + + def close(self): + self.container.append(''.join(self.lines).split('\n')) + self.lines = [] + +def prompt(ui, prompt, default=None, rest=': ', empty_ok=False): + if not ui.interactive: + return default + if default: + prompt += ' [%s]' % default + prompt += rest + while True: + r = ui.prompt(prompt, default=default) + if r: + return r + if default is not None: + return default + if empty_ok: + return r + ui.warn(_('Please enter a valid value.\n')) + +def cdiffstat(ui, summary, patchlines): + s = patch.diffstat(patchlines) + if summary: + ui.write(summary, '\n') + ui.write(s, '\n') + ans = prompt(ui, _('Does the diffstat above look okay? '), 'y') + if not ans.lower().startswith('y'): + raise util.Abort(_('diffstat rejected')) + return s + +def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None): + + desc = [] + node = None + body = '' + + for line in patch: + if line.startswith('#'): + if line.startswith('# Node ID'): + node = line.split()[-1] + continue + if line.startswith('diff -r') or line.startswith('diff --git'): + break + desc.append(line) + + if not patchname and not node: + raise ValueError + + if opts.get('attach'): + body = ('\n'.join(desc[1:]).strip() or + 'Patch subject is complete summary.') + body += '\n\n\n' + + if opts.get('plain'): + while patch and patch[0].startswith('# '): + patch.pop(0) + if patch: + patch.pop(0) + while patch and not patch[0].strip(): + patch.pop(0) + + if opts.get('diffstat'): + body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n' + + if opts.get('attach') or opts.get('inline'): + msg = email.MIMEMultipart.MIMEMultipart() + if body: + msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test'))) + p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test')) + binnode = bin(node) + # if node is mq patch, it will have patch file name as tag + if not patchname: + patchtags = [t for t in repo.nodetags(binnode) + if t.endswith('.patch') or t.endswith('.diff')] + if patchtags: + patchname = patchtags[0] + elif total > 1: + patchname = cmdutil.make_filename(repo, '%b-%n.patch', + binnode, seqno=idx, total=total) + else: + patchname = cmdutil.make_filename(repo, '%b.patch', binnode) + disposition = 'inline' + if opts.get('attach'): + disposition = 'attachment' + p['Content-Disposition'] = disposition + '; filename=' + patchname + msg.attach(p) + else: + body += '\n'.join(patch) + msg = mail.mimetextpatch(body, display=opts.get('test')) + + subj = desc[0].strip().rstrip('. ') + if total == 1 and not opts.get('intro'): + subj = '[PATCH] ' + (opts.get('subject') or subj) + else: + tlen = len(str(total)) + subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj) + msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) + msg['X-Mercurial-Node'] = node + return msg, subj + def patchbomb(ui, repo, *revs, **opts): '''send changesets by email @@ -113,105 +221,6 @@ _charsets = mail._charsets(ui) - def prompt(prompt, default = None, rest = ': ', empty_ok = False): - if not ui.interactive: - return default - if default: - prompt += ' [%s]' % default - prompt += rest - while True: - r = ui.prompt(prompt, default=default) - if r: - return r - if default is not None: - return default - if empty_ok: - return r - ui.warn(_('Please enter a valid value.\n')) - - def confirm(s, denial): - if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'): - raise util.Abort(denial) - - def cdiffstat(summary, patchlines): - s = patch.diffstat(patchlines) - if s: - if summary: - ui.write(summary, '\n') - ui.write(s, '\n') - confirm(_('Does the diffstat above look okay'), - _('diffstat rejected')) - elif s is None: - ui.warn(_('No diffstat information available.\n')) - s = '' - return s - - def makepatch(patch, idx, total): - desc = [] - node = None - body = '' - for line in patch: - if line.startswith('#'): - if line.startswith('# Node ID'): - node = line.split()[-1] - continue - if line.startswith('diff -r') or line.startswith('diff --git'): - break - desc.append(line) - if not node: - raise ValueError - - if opts.get('attach'): - body = ('\n'.join(desc[1:]).strip() or - 'Patch subject is complete summary.') - body += '\n\n\n' - - if opts.get('plain'): - while patch and patch[0].startswith('# '): - patch.pop(0) - if patch: - patch.pop(0) - while patch and not patch[0].strip(): - patch.pop(0) - if opts.get('diffstat'): - body += cdiffstat('\n'.join(desc), patch) + '\n\n' - if opts.get('attach') or opts.get('inline'): - msg = email.MIMEMultipart.MIMEMultipart() - if body: - msg.attach(mail.mimeencode(ui, body, _charsets, - opts.get('test'))) - p = mail.mimetextpatch('\n'.join(patch), 'x-patch', - opts.get('test')) - binnode = bin(node) - # if node is mq patch, it will have patch file name as tag - patchname = [t for t in repo.nodetags(binnode) - if t.endswith('.patch') or t.endswith('.diff')] - if patchname: - patchname = patchname[0] - elif total > 1: - patchname = cmdutil.make_filename(repo, '%b-%n.patch', - binnode, idx, total) - else: - patchname = cmdutil.make_filename(repo, '%b.patch', binnode) - disposition = 'inline' - if opts.get('attach'): - disposition = 'attachment' - p['Content-Disposition'] = disposition + '; filename=' + patchname - msg.attach(p) - else: - body += '\n'.join(patch) - msg = mail.mimetextpatch(body, display=opts.get('test')) - - subj = desc[0].strip().rstrip('. ') - if total == 1: - subj = '[PATCH] ' + (opts.get('subject') or subj) - else: - tlen = len(str(total)) - subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj) - msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test')) - msg['X-Mercurial-Node'] = node - return msg, subj - def outgoing(dest, revs): '''Return the revisions present locally but not in dest''' dest = ui.expandpath(dest or 'default-push', dest or 'default') @@ -243,7 +252,8 @@ mail.validateconfig(ui) if not (revs or opts.get('rev') - or opts.get('outgoing') or opts.get('bundle')): + or opts.get('outgoing') or opts.get('bundle') + or opts.get('patches')): raise util.Abort(_('specify at least one changeset with -r or -o')) cmdutil.setremoteconfig(ui, opts) @@ -285,49 +295,34 @@ body = ui.edit(body, sender) return body - def getexportmsgs(): - patches = [] - - class exportee: - def __init__(self, container): - self.lines = [] - self.container = container - self.name = 'email' - - def write(self, data): - self.lines.append(data) - - def close(self): - self.container.append(''.join(self.lines).split('\n')) - self.lines = [] - - commands.export(ui, repo, *revs, **{'output': exportee(patches), - 'switch_parent': False, - 'text': None, - 'git': opts.get('git')}) - + def getpatchmsgs(patches, patchnames=None): jumbo = [] msgs = [] ui.write(_('This patch series consists of %d patches.\n\n') % len(patches)) + name = None for p, i in zip(patches, xrange(len(patches))): jumbo.extend(p) - msgs.append(makepatch(p, i + 1, len(patches))) + if patchnames: + name = patchnames[i] + msg = makepatch(ui, repo, p, opts, _charsets, i + 1, + len(patches), name) + msgs.append(msg) - if len(patches) > 1: + if len(patches) > 1 or opts.get('intro'): tlen = len(str(len(patches))) subj = '[PATCH %0*d of %d] %s' % ( tlen, 0, len(patches), opts.get('subject') or - prompt('Subject:', + prompt(ui, 'Subject:', rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches)))) body = '' if opts.get('diffstat'): - d = cdiffstat(_('Final summary:\n'), jumbo) + d = cdiffstat(ui, _('Final summary:\n'), jumbo) if d: body = '\n' + d @@ -341,7 +336,7 @@ def getbundlemsgs(bundle): subj = (opts.get('subject') - or prompt('Subject:', default='A bundle for your repository')) + or prompt(ui, 'Subject:', 'A bundle for your repository')) body = getdescription('', sender) msg = email.MIMEMultipart.MIMEMultipart() @@ -358,17 +353,25 @@ sender = (opts.get('from') or ui.config('email', 'from') or ui.config('patchbomb', 'from') or - prompt('From', ui.username())) + prompt(ui, 'From', ui.username())) - if opts.get('bundle'): + patches = opts.get('patches') + if patches: + msgs = getpatchmsgs(patches, opts.get('patchnames')) + elif opts.get('bundle'): msgs = getbundlemsgs(getbundle(dest)) else: - msgs = getexportmsgs() + patches = [] + commands.export(ui, repo, *revs, **{'output': exportee(patches), + 'switch_parent': False, + 'text': None, + 'git': opts.get('git')}) + msgs = getpatchmsgs(patches) def getaddrs(opt, prpt, default = None): addrs = opts.get(opt) or (ui.config('email', opt) or ui.config('patchbomb', opt) or - prompt(prpt, default = default)).split(',') + prompt(ui, prpt, default)).split(',') return [mail.addressencode(ui, a.strip(), _charsets, opts.get('test')) for a in addrs if a.strip()] @@ -394,6 +397,7 @@ m['Message-Id'] = genmsgid('patchbomb') if parent: m['In-Reply-To'] = parent + m['References'] = parent else: parent = m['Message-Id'] m['Date'] = util.datestr(start_time, "%a, %d %b %Y %H:%M:%S %1%2") @@ -441,34 +445,40 @@ generator.flatten(m, 0) sendmail(sender, to + bcc + cc, fp.getvalue()) -cmdtable = { - "email": - (patchbomb, - [('a', 'attach', None, _('send patches as attachments')), +emailopts = [ + ('a', 'attach', None, _('send patches as attachments')), ('i', 'inline', None, _('send patches as inline attachments')), ('', 'bcc', [], _('email addresses of blind copy recipients')), ('c', 'cc', [], _('email addresses of copy recipients')), ('d', 'diffstat', None, _('add diffstat output to messages')), ('', 'date', '', _('use the given date as the sending date')), ('', 'desc', '', _('use the given file as the series description')), - ('g', 'git', None, _('use git extended diff format')), ('f', 'from', '', _('email address of sender')), - ('', 'plain', None, _('omit hg patch header')), ('n', 'test', None, _('print messages that would be sent')), ('m', 'mbox', '', _('write messages to mbox file instead of sending them')), + ('s', 'subject', '', + _('subject of first message (intro or single patch)')), + ('t', 'to', [], _('email addresses of recipients')), + ] + + +cmdtable = { + "email": + (patchbomb, + [('g', 'git', None, _('use git extended diff format')), + ('', 'plain', None, _('omit hg patch header')), ('o', 'outgoing', None, _('send changes not found in the target repository')), ('b', 'bundle', None, _('send changes not in target as a binary bundle')), ('r', 'rev', [], _('a revision to send')), - ('s', 'subject', '', - _('subject of first message (intro or single patch)')), - ('t', 'to', [], _('email addresses of recipients')), ('', 'force', None, _('run even when remote repository is unrelated (with -b)')), ('', 'base', [], _('a base changeset to specify instead of a destination (with -b)')), - ] + commands.remoteopts, + ('', 'intro', None, + _('send an introduction email for a single patch')), + ] + emailopts + commands.remoteopts, _('hg email [OPTION]... [DEST]...')) } diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/rebase.py --- a/hgext/rebase.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/rebase.py Wed Dec 31 15:04:18 2008 +0100 @@ -64,6 +64,14 @@ contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) + extrafn = opts.get('extrafn') + if opts.get('keepbranches', None): + if extrafn: + raise dispatch.ParseError('rebase', + _('cannot use both keepbranches and extrafn')) + def extrafn(ctx, extra): + extra['branch'] = ctx.branch() + if contf or abortf: if contf and abortf: raise dispatch.ParseError('rebase', @@ -101,14 +109,14 @@ storestatus(repo, originalwd, target, state, collapsef, external) rebasenode(repo, rev, target, state, skipped, targetancestors, - collapsef) + collapsef, extrafn) ui.note(_('rebase merging completed\n')) if collapsef: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) concludenode(repo, rev, p1, external, state, collapsef, - last=True, skipped=skipped) + last=True, skipped=skipped, extrafn=extrafn) if 'qtip' in repo.tags(): updatemq(repo, state, skipped, **opts) @@ -131,7 +139,8 @@ finally: del lock, wlock -def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={}): +def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={}, + extrafn=None): """Skip commit if collapsing has been required and rev is not the last revision, commit otherwise """ @@ -155,18 +164,22 @@ else: commitmsg = repo[rev].description() # Commit might fail if unresolved files exist + extra = {'rebase_source': repo[rev].hex()} + if extrafn: + extrafn(repo[rev], extra) newrev = repo.commit(m+a+r, text=commitmsg, user=repo[rev].user(), date=repo[rev].date(), - extra={'rebase_source': repo[rev].hex()}) + extra=extra) return newrev except util.Abort: # Invalidate the previous setparents repo.dirstate.invalidate() raise -def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse): +def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse, + extrafn): 'Rebase a single revision' repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev])) @@ -195,7 +208,8 @@ repo.ui.debug(_('resuming interrupted rebase\n')) - newrev = concludenode(repo, rev, p1, p2, state, collapse) + newrev = concludenode(repo, rev, p1, p2, state, collapse, + extrafn=extrafn) # Update the state if newrev is not None: @@ -409,6 +423,7 @@ (rebase, [ ('', 'keep', False, _('keep original revisions')), + ('', 'keepbranches', False, _('keep original branches')), ('s', 'source', '', _('rebase from a given revision')), ('b', 'base', '', _('rebase from the base of a given revision')), ('d', 'dest', '', _('rebase onto a given revision')), diff -r cab1cf26ca58 -r a9221c7f51a4 hgext/transplant.py --- a/hgext/transplant.py Wed Dec 31 14:29:51 2008 +0100 +++ b/hgext/transplant.py Wed Dec 31 15:04:18 2008 +0100 @@ -398,7 +398,7 @@ transplants = [] merges = [] for node in nodes: - displayer.show(changenode=node) + displayer.show(repo[node]) action = None while not action: action = ui.prompt(_('apply changeset? [ynmpcq?]:')) @@ -461,13 +461,16 @@ def getremotechanges(repo, url): sourcerepo = ui.expandpath(url) source = hg.repository(ui, sourcerepo) - incoming = repo.findincoming(source, force=True) + common, incoming, rheads = repo.findcommonincoming(source, force=True) if not incoming: return (source, None, None) bundle = None if not source.local(): - cg = source.changegroup(incoming, 'incoming') + if source.capable('changegroupsubset'): + cg = source.changegroupsubset(incoming, rheads, 'incoming') + else: + cg = source.changegroup(incoming, 'incoming') bundle = changegroup.writebundle(cg, None, 'HG10UN') source = bundlerepo.bundlerepository(ui, repo.root, bundle) diff -r cab1cf26ca58 -r a9221c7f51a4 hgwebdir.cgi --- a/hgwebdir.cgi Wed Dec 31 14:29:51 2008 +0100 +++ b/hgwebdir.cgi Wed Dec 31 15:04:18 2008 +0100 @@ -28,12 +28,32 @@ # repos, collections of repos in a directory tree, or both. # # [paths] -# virtual/path = /real/path -# virtual/path = /real/path +# virtual/path1 = /real/path1 +# virtual/path2 = /real/path2 +# virtual/root = /real/root/* +# / = /real/root2/* +# virtual/root2 = /real/root2/** # # [collections] # /prefix/to/strip/off = /root/of/tree/full/of/repos # +# paths example: +# +# * First two lines mount one repository into one virtual path, like +# '/real/path1' into 'virtual/path1'. +# +# * The third entry mounts every mercurial repository found in '/real/root' +# in 'virtual/root'. This format is preferred over the [collections] one, +# since using absolute paths as configuration keys is not supported on every +# platform (especially on Windows). +# +# * The fourth entry is a special case mounting all repositories in +# /'real/root2' in the root of the virtual directory. +# +# * The fifth entry recursively finds all repositories under the real root, +# and mounts them using their relative path (to given real root) under the +# virtual root. +# # collections example: say directory tree /foo contains repos /foo/bar, # /foo/quux/baz. Give this config section: # [collections] diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/bundlerepo.py Wed Dec 31 15:04:18 2008 +0100 @@ -13,7 +13,7 @@ from node import hex, nullid, short from i18n import _ import changegroup, util, os, struct, bz2, zlib, tempfile, shutil, mdiff -import repo, localrepo, changelog, manifest, filelog, revlog +import repo, localrepo, changelog, manifest, filelog, revlog, context class bundlerevlog(revlog.revlog): def __init__(self, opener, indexfile, bundlefile, @@ -213,19 +213,20 @@ self.changelog = bundlechangelog(self.sopener, self.bundlefile) self.manstart = self.bundlefile.tell() return self.changelog - if name == 'manifest': + elif name == 'manifest': self.bundlefile.seek(self.manstart) self.manifest = bundlemanifest(self.sopener, self.bundlefile, self.changelog.rev) self.filestart = self.bundlefile.tell() return self.manifest - if name == 'manstart': + elif name == 'manstart': self.changelog return self.manstart - if name == 'filestart': + elif name == 'filestart': self.manifest return self.filestart - return localrepo.localrepository.__getattr__(self, name) + else: + raise AttributeError(name) def url(self): return self._url @@ -267,6 +268,9 @@ def cancopy(self): return False + def getcwd(self): + return os.getcwd() # always outside the repo + def instance(ui, path, create): if create: raise util.Abort(_('cannot create new bundle repository')) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/changelog.py --- a/mercurial/changelog.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/changelog.py Wed Dec 31 15:04:18 2008 +0100 @@ -179,7 +179,7 @@ user = user.strip() if "\n" in user: - raise RevlogError(_("username %s contains a newline") % `user`) + raise RevlogError(_("username %s contains a newline") % repr(user)) user, desc = util.fromlocal(user), util.fromlocal(desc) if date: diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/cmdutil.py --- a/mercurial/cmdutil.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/cmdutil.py Wed Dec 31 15:04:18 2008 +0100 @@ -214,9 +214,12 @@ def make_file(repo, pat, node=None, total=None, seqno=None, revwidth=None, mode='wb', pathname=None): + + writable = 'w' in mode or 'a' in mode + if not pat or pat == '-': - return 'w' in mode and sys.stdout or sys.stdin - if hasattr(pat, 'write') and 'w' in mode: + return writable and sys.stdout or sys.stdin + if hasattr(pat, 'write') and writable: return pat if hasattr(pat, 'read') and 'r' in mode: return pat @@ -593,26 +596,24 @@ return 1 return 0 - def show(self, rev=0, changenode=None, copies=(), **props): + def show(self, ctx, copies=(), **props): if self.buffered: self.ui.pushbuffer() - self._show(rev, changenode, copies, props) - self.hunk[rev] = self.ui.popbuffer() + self._show(ctx, copies, props) + self.hunk[ctx.rev()] = self.ui.popbuffer() else: - self._show(rev, changenode, copies, props) + self._show(ctx, copies, props) - def _show(self, rev, changenode, copies, props): + def _show(self, ctx, copies, props): '''show a single changeset or file revision''' - log = self.repo.changelog - if changenode is None: - changenode = log.node(rev) - elif not rev: - rev = log.rev(changenode) + changenode = ctx.node() + rev = ctx.rev() if self.ui.quiet: self.ui.write("%d:%s\n" % (rev, short(changenode))) return + log = self.repo.changelog changes = log.read(changenode) date = util.datestr(changes[2]) extra = changes[5] @@ -713,14 +714,12 @@ '''set template string to use''' self.t.cache['changeset'] = t - def _show(self, rev, changenode, copies, props): + def _show(self, ctx, copies, props): '''show a single changeset or file revision''' + changenode = ctx.node() + rev = ctx.rev() + log = self.repo.changelog - if changenode is None: - changenode = log.node(rev) - elif not rev: - rev = log.rev(changenode) - changes = log.read(changenode) def showlist(name, values, plural=None, **args): @@ -1014,7 +1013,7 @@ revs = [] for j in xrange(i - window, i + 1): n = filelog.node(j) - revs.append((filelog.linkrev(n), + revs.append((filelog.linkrev(j), follow and filelog.renamed(n))) revs.reverse() for rev in revs: @@ -1034,6 +1033,8 @@ if node is None: # A zero count may be a directory or deleted file, so # try to find matching entries on the slow path. + if follow: + raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_) slowpath = True break else: diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/commands.py --- a/mercurial/commands.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/commands.py Wed Dec 31 15:04:18 2008 +0100 @@ -9,7 +9,7 @@ from repo import RepoError, NoCapability from i18n import _, gettext import os, re, sys -import hg, util, revlog, bundlerepo, extensions, copies +import hg, util, revlog, bundlerepo, extensions, copies, context import difflib, patch, time, help, mdiff, tempfile, url import version import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect @@ -156,7 +156,7 @@ ctx = repo[opts.get('rev')] if not ctx: - raise util.Abort(_('repository has no revisions')) + raise util.Abort(_('no working directory: please specify a revision')) node = ctx.node() dest = cmdutil.make_filename(repo, dest, node) if os.path.realpath(dest) == repo.root: @@ -288,13 +288,13 @@ if len(nodes) == 1: # narrowed it down to a single revision ui.write(_("The first %s revision is:\n") % transition) - displayer.show(changenode=nodes[0]) + displayer.show(repo[nodes[0]]) else: # multiple possible revisions ui.write(_("Due to skipped revisions, the first " "%s revision could be any of:\n") % transition) for n in nodes: - displayer.show(changenode=n) + displayer.show(repo[n]) def check_state(state, interactive=True): if not state['good'] or not state['bad']: @@ -688,7 +688,10 @@ ui.write("%s\n" % "\n".join(options)) return - ui.write("%s\n" % "\n".join(util.sort(cmdutil.findpossible(cmd, table)))) + cmdlist = cmdutil.findpossible(cmd, table) + if ui.verbose: + cmdlist = [' '.join(c[0]) for c in cmdlist.values()] + ui.write("%s\n" % "\n".join(util.sort(cmdlist))) def debugfsinfo(ui, path = "."): file('.debugfsinfo', 'w').write('') @@ -829,7 +832,7 @@ except: pp = [nullid, nullid] ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % ( - i, r.start(i), r.length(i), r.base(i), r.linkrev(node), + i, r.start(i), r.length(i), r.base(i), r.linkrev(i), short(node), short(pp[0]), short(pp[1]))) def debugindexdot(ui, file_): @@ -1005,7 +1008,7 @@ probably with undesirable results. Use the --git option to generate diffs in the git extended diff - format. Read the gitdiffs help topic for more information. + format. Read the diffs help topic for more information. """ node1, node2 = cmdutil.revpair(repo, opts.get('rev')) @@ -1042,7 +1045,7 @@ probably with undesirable results. Use the --git option to generate diffs in the git extended diff - format. Read the gitdiffs help topic for more information. + format. Read the diffs help topic for more information. With the --switch-parent option, the diff will be against the second parent. It can be useful to review a merge. @@ -1270,7 +1273,7 @@ return 1 displayer = cmdutil.show_changeset(ui, repo, opts) for n in heads: - displayer.show(changenode=n) + displayer.show(repo[n]) def help_(ui, name=None, with_version=False): """show help for a given topic or a help overview @@ -1310,7 +1313,13 @@ return # synopsis - ui.write("%s\n" % i[2]) + if len(i) > 2: + if i[2].startswith('hg'): + ui.write("%s\n" % i[2]) + else: + ui.write('hg %s %s\n' % (aliases[0], i[2])) + else: + ui.write('hg %s\n' % aliases[0]) # aliases if not ui.quiet and len(aliases) > 1: @@ -1422,7 +1431,7 @@ if name and name != 'shortlist': i = None - for f in (helpcmd, helptopic, helpext): + for f in (helptopic, helpcmd, helpext): try: f(name) i = None @@ -1575,6 +1584,9 @@ recorded in the patch. This may happen due to character set problems or other deficiencies in the text patch format. + With --similarity, hg will attempt to discover renames and copies + in the patch in the same way as 'addremove'. + To read a patch from standard input, use patch name "-". See 'hg help dates' for a list of formats valid for -d/--date. """ @@ -1584,6 +1596,13 @@ if date: opts['date'] = util.parsedate(date) + try: + sim = float(opts.get('similarity') or 0) + except ValueError: + raise util.Abort(_('similarity must be a number')) + if sim < 0 or sim > 100: + raise util.Abort(_('similarity must be between 0 and 100')) + if opts.get('exact') or not opts.get('force'): cmdutil.bail_if_changed(repo) @@ -1647,7 +1666,7 @@ fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root, files=files) finally: - files = patch.updatedir(ui, repo, files) + files = patch.updatedir(ui, repo, files, similarity=sim/100.) if not opts.get('no_commit'): n = repo.commit(files, message, opts.get('user') or user, opts.get('date') or date) @@ -1684,7 +1703,8 @@ ui.status(_('comparing with %s\n') % url.hidepassword(source)) if revs: revs = [other.lookup(rev) for rev in revs] - incoming = repo.findincoming(other, heads=revs, force=opts["force"]) + common, incoming, rheads = repo.findcommonincoming(other, heads=revs, + force=opts["force"]) if not incoming: try: os.unlink(opts["bundle"]) @@ -1698,6 +1718,10 @@ fname = opts["bundle"] if fname or not other.local(): # create a bundle (uncompressed if other repo is not local) + + if revs is None and other.capable('changegroupsubset'): + revs = rheads + if revs is None: cg = other.changegroup(incoming, "incoming") else: @@ -1723,7 +1747,7 @@ if opts.get('no_merges') and len(parents) == 2: continue count += 1 - displayer.show(changenode=n) + displayer.show(other[n]) finally: if hasattr(other, 'close'): other.close() @@ -1833,7 +1857,7 @@ fl = repo.file(fn) for i in fl: node = fl.node(i) - lr = fl.linkrev(node) + lr = fl.linkrev(i) renamed = fl.renamed(node) rcache[fn][lr] = renamed if renamed: @@ -1861,7 +1885,6 @@ displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn) for st, rev, fns in changeiter: if st == 'add': - changenode = repo.changelog.node(rev) parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev] if opts.get('no_merges') and len(parents) == 2: @@ -1907,7 +1930,7 @@ rename = getrenamed(fn, rev) if rename: copies.append((fn, rename[0])) - displayer.show(rev, changenode, copies=copies) + displayer.show(context.changectx(repo, rev), copies=copies) elif st == 'iter': if count == limit: break if displayer.flush(rev): @@ -2020,7 +2043,7 @@ if opts.get('no_merges') and len(parents) == 2: continue count += 1 - displayer.show(changenode=n) + displayer.show(repo[n]) def parents(ui, repo, file_=None, **opts): """show the parents of the working dir or revision @@ -2053,14 +2076,14 @@ if not filenodes: raise util.Abort(_("'%s' not found in manifest!") % file_) fl = repo.file(file_) - p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes] + p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes] else: p = [cp.node() for cp in ctx.parents()] displayer = cmdutil.show_changeset(ui, repo, opts) for n in p: if n != nullid: - displayer.show(changenode=n) + displayer.show(repo[n]) def paths(ui, repo, search=None): """show definition of symbolic path names @@ -2086,7 +2109,7 @@ if modheads == 0: return if optupdate: - if modheads <= 1 or checkout: + if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout: return hg.update(repo, checkout) else: ui.status(_("not updating, since new heads added\n")) @@ -2307,32 +2330,40 @@ del wlock def resolve(ui, repo, *pats, **opts): - """resolve file merges from a branch merge or update - - This command will attempt to resolve unresolved merges from the - last update or merge command. This will use the local file - revision preserved at the last update or merge to cleanly retry - the file merge attempt. With no file or options specified, this - command will attempt to resolve all unresolved files. + """retry file merges from a merge or update + + This command will cleanly retry unresolved file merges using file + revisions preserved from the last update or merge. To attempt to + resolve all unresolved files, use the -a switch. + + This command will also allow listing resolved files and manually + marking and unmarking files as resolved. The codes used to show the status of files are: U = unresolved R = resolved """ - if len([x for x in opts if opts[x]]) > 1: + all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()] + + if (show and (mark or unmark)) or (mark and unmark): raise util.Abort(_("too many options specified")) + if pats and all: + raise util.Abort(_("can't specify --all and patterns")) + if not (all or pats or show or mark or unmark): + raise util.Abort(_('no files or directories specified; ' + 'use --all to remerge all files')) ms = merge_.mergestate(repo) m = cmdutil.match(repo, pats, opts) for f in ms: if m(f): - if opts.get("list"): + if show: ui.write("%s %s\n" % (ms[f].upper(), f)) - elif opts.get("mark"): + elif mark: ms.mark(f, "r") - elif opts.get("unmark"): + elif unmark: ms.mark(f, "u") else: wctx = repo[None] @@ -2836,7 +2867,7 @@ that repository becomes the current tip. The "tip" tag is special and cannot be renamed or assigned to a different changeset. """ - cmdutil.show_changeset(ui, repo, opts).show(len(repo) - 1) + cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1]) def unbundle(ui, repo, fname1, *fnames, **opts): """apply one or more changegroup files @@ -2998,14 +3029,16 @@ ('U', 'unified', '', _('number of lines of context to show')) ] +similarityopts = [ + ('s', 'similarity', '', + _('guess renamed files by similarity (0<=s<=100)')) +] + table = { - "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')), + "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')), "addremove": - (addremove, - [('s', 'similarity', '', - _('guess renamed files by similarity (0<=s<=100)')), - ] + walkopts + dryrunopts, - _('hg addremove [OPTION]... [FILE]...')), + (addremove, similarityopts + walkopts + dryrunopts, + _('[OPTION]... [FILE]...')), "^annotate|blame": (annotate, [('r', 'rev', '', _('annotate the specified revision')), @@ -3018,7 +3051,7 @@ ('l', 'line-number', None, _('show line number at the first appearance')) ] + walkopts, - _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')), + _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')), "archive": (archive, [('', 'no-decode', None, _('do not pass files through decoders')), @@ -3026,7 +3059,7 @@ ('r', 'rev', '', _('revision to distribute')), ('t', 'type', '', _('type of distribution to create')), ] + walkopts, - _('hg archive [OPTION]... DEST')), + _('[OPTION]... DEST')), "backout": (backout, [('', 'merge', None, @@ -3034,7 +3067,7 @@ ('', 'parent', '', _('parent to choose when backing out merge')), ('r', 'rev', '', _('revision to backout')), ] + walkopts + commitopts + commitopts2, - _('hg backout [OPTION]... [-r] REV')), + _('[OPTION]... [-r] REV')), "bisect": (bisect, [('r', 'reset', False, _('reset bisect state')), @@ -3043,18 +3076,18 @@ ('s', 'skip', False, _('skip testing changeset')), ('c', 'command', '', _('Use command to check changeset state')), ('U', 'noupdate', False, _('do not update to target'))], - _("hg bisect [-gbsr] [-c CMD] [REV]")), + _("[-gbsr] [-c CMD] [REV]")), "branch": (branch, [('f', 'force', None, _('set branch name even if it shadows an existing branch')), ('C', 'clean', None, _('reset branch name to parent branch name'))], - _('hg branch [-fC] [NAME]')), + _('[-fC] [NAME]')), "branches": (branches, [('a', 'active', False, _('show only branches that have unmerged heads'))], - _('hg branches [-a]')), + _('[-a]')), "bundle": (bundle, [('f', 'force', None, @@ -3066,14 +3099,14 @@ ('a', 'all', None, _('bundle all changesets in the repository')), ('t', 'type', 'bzip2', _('bundle compression type to use')), ] + remoteopts, - _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')), + _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')), "cat": (cat, [('o', 'output', '', _('print output to file with formatted name')), ('r', 'rev', '', _('print the given revision')), ('', 'decode', None, _('apply any matching decode filter')), ] + walkopts, - _('hg cat [OPTION]... FILE...')), + _('[OPTION]... FILE...')), "^clone": (clone, [('U', 'noupdate', None, @@ -3084,70 +3117,67 @@ ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')), ] + remoteopts, - _('hg clone [OPTION]... SOURCE [DEST]')), + _('[OPTION]... SOURCE [DEST]')), "^commit|ci": (commit, [('A', 'addremove', None, _('mark new/missing files as added/removed before committing')), ] + walkopts + commitopts + commitopts2, - _('hg commit [OPTION]... [FILE]...')), + _('[OPTION]... [FILE]...')), "copy|cp": (copy, [('A', 'after', None, _('record a copy that has already occurred')), ('f', 'force', None, _('forcibly copy over an existing managed file')), ] + walkopts + dryrunopts, - _('hg copy [OPTION]... [SOURCE]... DEST')), - "debugancestor": (debugancestor, [], - _('hg debugancestor [INDEX] REV1 REV2')), - "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')), + _('[OPTION]... [SOURCE]... DEST')), + "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')), + "debugcheckstate": (debugcheckstate, []), "debugcomplete": (debugcomplete, [('o', 'options', None, _('show the command options'))], - _('hg debugcomplete [-o] CMD')), + _('[-o] CMD')), "debugdate": (debugdate, [('e', 'extended', None, _('try extended date formats'))], - _('hg debugdate [-e] DATE [RANGE]')), - "debugdata": (debugdata, [], _('hg debugdata FILE REV')), - "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')), - "debugindex": (debugindex, [], _('hg debugindex FILE')), - "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')), - "debuginstall": (debuginstall, [], _('hg debuginstall')), + _('[-e] DATE [RANGE]')), + "debugdata": (debugdata, [], _('FILE REV')), + "debugfsinfo": (debugfsinfo, [], _('[PATH]')), + "debugindex": (debugindex, [], _('FILE')), + "debugindexdot": (debugindexdot, [], _('FILE')), + "debuginstall": (debuginstall, []), "debugrawcommit|rawcommit": (rawcommit, [('p', 'parent', [], _('parent')), ('F', 'files', '', _('file list')) ] + commitopts + commitopts2, - _('hg debugrawcommit [OPTION]... [FILE]...')), + _('[OPTION]... [FILE]...')), "debugrebuildstate": (debugrebuildstate, [('r', 'rev', '', _('revision to rebuild to'))], - _('hg debugrebuildstate [-r REV] [REV]')), + _('[-r REV] [REV]')), "debugrename": (debugrename, [('r', 'rev', '', _('revision to debug'))], - _('hg debugrename [-r REV] FILE')), + _('[-r REV] FILE')), "debugsetparents": - (debugsetparents, - [], - _('hg debugsetparents REV1 [REV2]')), + (debugsetparents, [], _('REV1 [REV2]')), "debugstate": (debugstate, [('', 'nodates', None, _('do not display the saved mtime'))], - _('hg debugstate [OPTION]...')), - "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')), + _('[OPTION]...')), + "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')), "^diff": (diff, [('r', 'rev', [], _('revision')) ] + diffopts + diffopts2 + walkopts, - _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')), + _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')), "^export": (export, [('o', 'output', '', _('print output to file with formatted name')), ('', 'switch-parent', None, _('diff against the second parent')) ] + diffopts, - _('hg export [OPTION]... [-o OUTFILESPEC] REV...')), + _('[OPTION]... [-o OUTFILESPEC] REV...')), "grep": (grep, [('0', 'print0', None, _('end fields with NUL')), @@ -3162,13 +3192,13 @@ ('u', 'user', None, _('list the author (long with -v)')), ('d', 'date', None, _('list the date (short with -q)')), ] + walkopts, - _('hg grep [OPTION]... PATTERN [FILE]...')), + _('[OPTION]... PATTERN [FILE]...')), "heads": (heads, [('r', 'rev', '', _('show only heads which are descendants of rev')), ] + templateopts, - _('hg heads [-r REV] [REV]...')), - "help": (help_, [], _('hg help [TOPIC]')), + _('[-r REV] [REV]...')), + "help": (help_, [], _('[TOPIC]')), "identify|id": (identify, [('r', 'rev', '', _('identify the specified rev')), @@ -3176,7 +3206,7 @@ ('i', 'id', None, _('show global revision id')), ('b', 'branch', None, _('show branch')), ('t', 'tags', None, _('show tags'))], - _('hg identify [-nibt] [-r REV] [SOURCE]')), + _('[-nibt] [-r REV] [SOURCE]')), "import|patch": (import_, [('p', 'strip', 1, @@ -3190,8 +3220,8 @@ _('apply patch to the nodes from which it was generated')), ('', 'import-branch', None, _('Use any branch information in patch (implied by --exact)'))] + - commitopts + commitopts2, - _('hg import [OPTION]... PATCH...')), + commitopts + commitopts2 + similarityopts, + _('[OPTION]... PATCH...')), "incoming|in": (incoming, [('f', 'force', None, @@ -3201,12 +3231,12 @@ ('r', 'rev', [], _('a specific revision up to which you would like to pull')), ] + logopts + remoteopts, - _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...' + _('[-p] [-n] [-M] [-f] [-r REV]...' ' [--bundle FILENAME] [SOURCE]')), "^init": (init, remoteopts, - _('hg init [-e CMD] [--remotecmd CMD] [DEST]')), + _('[-e CMD] [--remotecmd CMD] [DEST]')), "locate": (locate, [('r', 'rev', '', _('search the repository as it stood at rev')), @@ -3215,7 +3245,7 @@ ('f', 'fullpath', None, _('print complete paths from the filesystem root')), ] + walkopts, - _('hg locate [OPTION]... [PATTERN]...')), + _('[OPTION]... [PATTERN]...')), "^log|history": (log, [('f', 'follow', None, @@ -3233,17 +3263,17 @@ _('show only changesets within the given named branch')), ('P', 'prune', [], _('do not display revision or any of its ancestors')), ] + logopts + walkopts, - _('hg log [OPTION]... [FILE]')), + _('[OPTION]... [FILE]')), "manifest": (manifest, [('r', 'rev', '', _('revision to display'))], - _('hg manifest [-r REV]')), + _('[-r REV]')), "^merge": (merge, [('f', 'force', None, _('force a merge with outstanding changes')), ('r', 'rev', '', _('revision to merge')), ], - _('hg merge [-f] [[-r] REV]')), + _('[-f] [[-r] REV]')), "outgoing|out": (outgoing, [('f', 'force', None, @@ -3252,13 +3282,13 @@ _('a specific revision up to which you would like to push')), ('n', 'newest-first', None, _('show newest record first')), ] + logopts + remoteopts, - _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')), + _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')), "^parents": (parents, [('r', 'rev', '', _('show parents from the specified rev')), ] + templateopts, _('hg parents [-r REV] [FILE]')), - "paths": (paths, [], _('hg paths [NAME]')), + "paths": (paths, [], _('[NAME]')), "^pull": (pull, [('u', 'update', None, @@ -3268,35 +3298,36 @@ ('r', 'rev', [], _('a specific revision up to which you would like to pull')), ] + remoteopts, - _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')), + _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')), "^push": (push, [('f', 'force', None, _('force push')), ('r', 'rev', [], _('a specific revision up to which you would like to push')), ] + remoteopts, - _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')), - "recover": (recover, [], _('hg recover')), + _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')), + "recover": (recover, []), "^remove|rm": (remove, [('A', 'after', None, _('record delete for missing files')), ('f', 'force', None, _('remove (and delete) file even if added or modified')), ] + walkopts, - _('hg remove [OPTION]... FILE...')), + _('[OPTION]... FILE...')), "rename|mv": (rename, [('A', 'after', None, _('record a rename that has already occurred')), ('f', 'force', None, _('forcibly copy over an existing managed file')), ] + walkopts + dryrunopts, - _('hg rename [OPTION]... SOURCE... DEST')), + _('[OPTION]... SOURCE... DEST')), "resolve": (resolve, - [('l', 'list', None, _('list state of files needing merge')), + [('a', 'all', None, _('remerge all unresolved files')), + ('l', 'list', None, _('list state of files needing merge')), ('m', 'mark', None, _('mark files as resolved')), ('u', 'unmark', None, _('unmark files as resolved'))], - _('hg resolve [OPTION]... [FILE]...')), + _('[OPTION]... [FILE]...')), "revert": (revert, [('a', 'all', None, _('revert all changes when no arguments given')), @@ -3304,9 +3335,9 @@ ('r', 'rev', '', _('revision to revert to')), ('', 'no-backup', None, _('do not save backup copies of files')), ] + walkopts + dryrunopts, - _('hg revert [OPTION]... [-r REV] [NAME]...')), - "rollback": (rollback, [], _('hg rollback')), - "root": (root, [], _('hg root')), + _('[OPTION]... [-r REV] [NAME]...')), + "rollback": (rollback, []), + "root": (root, []), "^serve": (serve, [('A', 'accesslog', '', _('name of access log file to write to')), @@ -3326,11 +3357,11 @@ ('', 'style', '', _('template style to use')), ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')), ('', 'certificate', '', _('SSL certificate file'))], - _('hg serve [OPTION]...')), + _('[OPTION]...')), "showconfig|debugconfig": (showconfig, [('u', 'untrusted', None, _('show untrusted configuration options'))], - _('hg showconfig [-u] [NAME]...')), + _('[-u] [NAME]...')), "^status|st": (status, [('A', 'all', None, _('show status of all files')), @@ -3347,7 +3378,7 @@ _('end filenames with NUL, for use with xargs')), ('', 'rev', [], _('show difference from revision')), ] + walkopts, - _('hg status [OPTION]... [FILE]...')), + _('[OPTION]... [FILE]...')), "tag": (tag, [('f', 'force', None, _('replace existing tag')), @@ -3357,26 +3388,26 @@ # -l/--local is already there, commitopts cannot be used ('m', 'message', '', _('use as commit message')), ] + commitopts2, - _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')), - "tags": (tags, [], _('hg tags')), + _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')), + "tags": (tags, []), "tip": (tip, [('p', 'patch', None, _('show patch')), ] + templateopts, - _('hg tip [-p]')), + _('[-p]')), "unbundle": (unbundle, [('u', 'update', None, _('update to new tip if changesets were unbundled'))], - _('hg unbundle [-u] FILE...')), + _('[-u] FILE...')), "^update|up|checkout|co": (update, [('C', 'clean', None, _('overwrite locally modified files (no backup)')), ('d', 'date', '', _('tipmost revision matching date')), ('r', 'rev', '', _('revision'))], - _('hg update [-C] [-d DATE] [[-r] REV]')), - "verify": (verify, [], _('hg verify')), - "version": (version_, [], _('hg version')), + _('[-C] [-d DATE] [[-r] REV]')), + "verify": (verify, []), + "version": (version_, []), } norepo = ("clone init version help debugcomplete debugdata" diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/context.py --- a/mercurial/context.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/context.py Wed Dec 31 15:04:18 2008 +0100 @@ -9,6 +9,15 @@ from i18n import _ import ancestor, bdiff, revlog, util, os, errno +class propertycache(object): + def __init__(self, func): + self.func = func + self.name = func.__name__ + def __get__(self, obj, type=None): + result = self.func(obj) + setattr(obj, self.name, result) + return result + class changectx(object): """A changecontext object makes access to data related to a particular changeset convenient.""" @@ -17,8 +26,12 @@ if changeid == '': changeid = '.' self._repo = repo - self._node = self._repo.lookup(changeid) - self._rev = self._repo.changelog.rev(self._node) + if isinstance(changeid, (long, int)): + self._rev = changeid + self._node = self._repo.changelog.node(changeid) + else: + self._node = self._repo.lookup(changeid) + self._rev = self._repo.changelog.rev(self._node) def __str__(self): return short(self.node()) @@ -47,25 +60,24 @@ def __nonzero__(self): return self._rev != nullrev - def __getattr__(self, name): - if name == '_changeset': - self._changeset = self._repo.changelog.read(self.node()) - return self._changeset - elif name == '_manifest': - self._manifest = self._repo.manifest.read(self._changeset[0]) - return self._manifest - elif name == '_manifestdelta': - md = self._repo.manifest.readdelta(self._changeset[0]) - self._manifestdelta = md - return self._manifestdelta - elif name == '_parents': - p = self._repo.changelog.parents(self._node) - if p[1] == nullid: - p = p[:-1] - self._parents = [changectx(self._repo, x) for x in p] - return self._parents - else: - raise AttributeError(name) + def _changeset(self): + return self._repo.changelog.read(self.node()) + _changeset = propertycache(_changeset) + + def _manifest(self): + return self._repo.manifest.read(self._changeset[0]) + _manifest = propertycache(_manifest) + + def _manifestdelta(self): + return self._repo.manifest.readdelta(self._changeset[0]) + _manifestdelta = propertycache(_manifestdelta) + + def _parents(self): + p = self._repo.changelog.parentrevs(self._rev) + if p[1] == nullrev: + p = p[:-1] + return [changectx(self._repo, x) for x in p] + _parents = propertycache(_parents) def __contains__(self, key): return key in self._manifest @@ -189,33 +201,35 @@ if fileid is not None: self._fileid = fileid - def __getattr__(self, name): - if name == '_changectx': - self._changectx = changectx(self._repo, self._changeid) - return self._changectx - elif name == '_filelog': - self._filelog = self._repo.file(self._path) - return self._filelog - elif name == '_changeid': - if '_changectx' in self.__dict__: - self._changeid = self._changectx.rev() - else: - self._changeid = self._filelog.linkrev(self._filenode) - return self._changeid - elif name == '_filenode': - if '_fileid' in self.__dict__: - self._filenode = self._filelog.lookup(self._fileid) - else: - self._filenode = self._changectx.filenode(self._path) - return self._filenode - elif name == '_filerev': - self._filerev = self._filelog.rev(self._filenode) - return self._filerev - elif name == '_repopath': - self._repopath = self._path - return self._repopath + def _changectx(self): + return changectx(self._repo, self._changeid) + _changectx = propertycache(_changectx) + + def _filelog(self): + return self._repo.file(self._path) + _filelog = propertycache(_filelog) + + def _changeid(self): + if '_changectx' in self.__dict__: + return self._changectx.rev() else: - raise AttributeError(name) + return self._filelog.linkrev(self._filerev) + _changeid = propertycache(_changeid) + + def _filenode(self): + if '_fileid' in self.__dict__: + return self._filelog.lookup(self._fileid) + else: + return self._changectx.filenode(self._path) + _filenode = propertycache(_filenode) + + def _filerev(self): + return self._filelog.rev(self._filenode) + _filerev = propertycache(_filerev) + + def _repopath(self): + return self._path + _repopath = propertycache(_repopath) def __nonzero__(self): try: @@ -263,9 +277,9 @@ return self._changectx.rev() if '_changeid' in self.__dict__: return self._changectx.rev() - return self._filelog.linkrev(self._filenode) + return self._filelog.linkrev(self._filerev) - def linkrev(self): return self._filelog.linkrev(self._filenode) + def linkrev(self): return self._filelog.linkrev(self._filerev) def node(self): return self._changectx.node() def user(self): return self._changectx.user() def date(self): return self._changectx.date() @@ -501,29 +515,7 @@ def __contains__(self, key): return self._dirstate[key] not in "?r" - def __getattr__(self, name): - if name == '_status': - self._status = self._repo.status(unknown=True) - return self._status - elif name == '_user': - self._user = self._repo.ui.username() - return self._user - elif name == '_date': - self._date = util.makedate() - return self._date - if name == '_manifest': - self._buildmanifest() - return self._manifest - elif name == '_parents': - p = self._repo.dirstate.parents() - if p[1] == nullid: - p = p[:-1] - self._parents = [changectx(self._repo, x) for x in p] - return self._parents - else: - raise AttributeError(name) - - def _buildmanifest(self): + def _manifest(self): """generate a manifest corresponding to the working directory""" man = self._parents[0].manifest().copy() @@ -543,7 +535,28 @@ if f in man: del man[f] - self._manifest = man + return man + _manifest = propertycache(_manifest) + + def _status(self): + return self._repo.status(unknown=True) + _status = propertycache(_status) + + def _user(self): + return self._repo.ui.username() + _user = propertycache(_user) + + def _date(self): + return util.makedate() + _date = propertycache(_date) + + def _parents(self): + p = self._repo.dirstate.parents() + if p[1] == nullid: + p = p[:-1] + self._parents = [changectx(self._repo, x) for x in p] + return self._parents + _parents = propertycache(_parents) def manifest(self): return self._manifest @@ -618,19 +631,17 @@ if workingctx: self._changectx = workingctx - def __getattr__(self, name): - if name == '_changectx': - self._changectx = workingctx(self._repo) - return self._changectx - elif name == '_repopath': - self._repopath = (self._repo.dirstate.copied(self._path) - or self._path) - return self._repopath - elif name == '_filelog': - self._filelog = self._repo.file(self._repopath) - return self._filelog - else: - raise AttributeError(name) + def _changectx(self): + return workingctx(self._repo) + _changectx = propertycache(_changectx) + + def _repopath(self): + return self._repo.dirstate.copied(self._path) or self._path + _repopath = propertycache(_repopath) + + def _filelog(self): + return self._repo.file(self._repopath) + _filelog = propertycache(_filelog) def __nonzero__(self): return True @@ -647,7 +658,7 @@ def rev(self): if '_changectx' in self.__dict__: return self._changectx.rev() - return self._filelog.linkrev(self._filenode) + return self._filelog.linkrev(self._filerev) def data(self): return self._repo.wread(self._path) def renamed(self): @@ -723,7 +734,7 @@ parents = [(p or nullid) for p in parents] p1, p2 = parents self._parents = [changectx(self._repo, p) for p in (p1, p2)] - files = util.sort(list(files)) + files = util.sort(util.unique(files)) self._status = [files, [], [], [], []] self._filectxfn = filectxfn diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/dispatch.py --- a/mercurial/dispatch.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/dispatch.py Wed Dec 31 15:04:18 2008 +0100 @@ -7,7 +7,7 @@ from i18n import _ from repo import RepoError -import os, sys, atexit, signal, pdb, traceback, socket, errno, shlex, time +import os, sys, atexit, signal, pdb, socket, errno, shlex, time import util, commands, hg, lock, fancyopts, revlog, version, extensions, hook import cmdutil import ui as _ui @@ -90,7 +90,7 @@ else: raise except socket.error, inst: - ui.warn(_("abort: %s\n") % inst[-1]) + ui.warn(_("abort: %s\n") % inst.args[-1]) except IOError, inst: if hasattr(inst, "code"): ui.warn(_("abort: %s\n") % inst) @@ -100,7 +100,7 @@ except: # it might be anything, for example a string reason = inst.reason ui.warn(_("abort: error: %s\n") % reason) - elif hasattr(inst, "args") and inst[0] == errno.EPIPE: + elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE: if ui.debugflag: ui.warn(_("broken pipe\n")) elif getattr(inst, "strerror", None): @@ -116,13 +116,13 @@ else: ui.warn(_("abort: %s\n") % inst.strerror) except util.UnexpectedOutput, inst: - ui.warn(_("abort: %s") % inst[0]) - if not isinstance(inst[1], basestring): - ui.warn(" %r\n" % (inst[1],)) - elif not inst[1]: + ui.warn(_("abort: %s") % inst.args[0]) + if not isinstance(inst.args[1], basestring): + ui.warn(" %r\n" % (inst.args[1],)) + elif not inst.args[1]: ui.warn(_(" empty string\n")) else: - ui.warn("\n%r\n" % util.ellipsis(inst[1])) + ui.warn("\n%r\n" % util.ellipsis(inst.args[1])) except ImportError, inst: m = str(inst).split()[-1] ui.warn(_("abort: could not import module %s!\n") % m) @@ -356,9 +356,9 @@ raise RepoError(_("There is no Mercurial repository here" " (.hg not found)")) raise - d = lambda: func(ui, repo, *args, **cmdoptions) - else: - d = lambda: func(ui, *args, **cmdoptions) + args.insert(0, repo) + + d = lambda: util.checksignature(func)(ui, *args, **cmdoptions) # run pre-hook, and abort if it fails ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs)) @@ -374,11 +374,7 @@ def checkargs(): try: return cmdfunc() - except TypeError: - # was this an argument error? - tb = traceback.extract_tb(sys.exc_info()[2]) - if len(tb) != 2: # no - raise + except util.SignatureError: raise ParseError(cmd, _("invalid arguments")) if options['profile']: diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/extensions.py --- a/mercurial/extensions.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/extensions.py Wed Dec 31 15:04:18 2008 +0100 @@ -96,9 +96,11 @@ origfn = entry[0] def wrap(*args, **kwargs): - return wrapper(origfn, *args, **kwargs) + return util.checksignature(wrapper)( + util.checksignature(origfn), *args, **kwargs) wrap.__doc__ = getattr(origfn, '__doc__') + wrap.__module__ = getattr(origfn, '__module__') newentry = list(entry) newentry[0] = wrap diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/filemerge.py --- a/mercurial/filemerge.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/filemerge.py Wed Dec 31 15:04:18 2008 +0100 @@ -32,8 +32,11 @@ tmsg = tool if pat: tmsg += " specified for " + pat - if pat and not _findtool(ui, tool): # skip search if not matching - ui.warn(_("couldn't find merge tool %s\n") % tmsg) + if not _findtool(ui, tool): + if pat: # explicitly requested tool deserves a warning + ui.warn(_("couldn't find merge tool %s\n") % tmsg) + else: # configured but non-existing tools are more silent + ui.note(_("couldn't find merge tool %s\n") % tmsg) elif symlink and not _toolbool(ui, tool, "symlink"): ui.warn(_("tool %s can't handle symlinks\n") % tmsg) elif binary and not _toolbool(ui, tool, "binary"): @@ -71,8 +74,8 @@ tools.insert(0, (None, uimerge)) # highest priority tools.append((None, "hgmerge")) # the old default, if found for p,t in tools: - toolpath = _findtool(ui, t) - if toolpath and check(t, None, symlink, binary): + if check(t, None, symlink, binary): + toolpath = _findtool(ui, t) return (t, '"' + toolpath + '"') # internal merge as last resort return (not (symlink or binary) and "internal:merge" or None, None) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/help.py --- a/mercurial/help.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/help.py Wed Dec 31 15:04:18 2008 +0100 @@ -216,33 +216,36 @@ gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2. ''')), - (['gitdiffs'], _('Using git Diffs'), + (['diffs'], _('Diff Formats'), _(r''' - In several places, Mercurial supports two separate variations on - the unified diff format: normal diffs, as are de facto standardized - by GNU's patch utility, and git diffs, invented for the git VCS. + Mercurial's default format for showing changes between two versions + of a file is compatible with the unified format of GNU diff, which + can be used by GNU patch and many other standard tools. - The git diff format is an addition of some information to the normal - diff format, which allows diff to convey changes in file permissions - as well as the creation, deletion, renaming and copying of files, as - well as diffs for binary files (unsupported by standard diff), - operations which are very useful to modern version control systems - such as Mercurial, in trying to faithfully replay your changes. + While this standard format is often enough, it does not encode the + following information: + + - executable status + - copy or rename information + - changes in binary files + - creation or deletion of empty files - In building Mercurial, we made a choice to support the git diff - format, but we haven't made it the default. This is because for a - long time, the format for unified diffs we usually use has been - defined by GNU patch, and it doesn't (yet) support git's extensions - to the diff format. This means that, when extracting diffs from a - Mercurial repository (through the diff command, for example), you - must be careful about things like file copies and renames (file - creation and deletion are mostly handled fine by the traditional - diff format, with some rare edge cases for which the git extensions - can be used). Mercurial's internal operations (like push and pull) - are not affected by these differences, because they use a different, - binary format for communicating changes. + Mercurial also supports the extended diff format from the git VCS + which addresses these limitations. The git diff format is not + produced by default because there are very few tools which + understand this format. - To use git diffs, use the --git option for relevant commands, or - enable them in a hgrc, setting 'git = True' in the [diff] section. + This means that when generating diffs from a Mercurial repository + (e.g. with "hg export"), you should be careful about things like + file copies and renames or other things mentioned above, because + when applying a standard diff to a different repository, this extra + information is lost. Mercurial's internal operations (like push and + pull) are not affected by this, because they use an internal binary + format for communicating changes. + + To make Mercurial produce the git extended diff format, use the + --git option available for many commands, or set 'git = True' in the + [diff] section of your hgrc. You do not need to set this option when + importing diffs in this format or using them in the mq extension. ''')), ) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/hg.py --- a/mercurial/hg.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/hg.py Wed Dec 31 15:04:18 2008 +0100 @@ -252,8 +252,6 @@ note = ", ".join([_("%d files %s") % s for s in stats]) repo.ui.status("%s\n" % note) -def _update(repo, node): return update(repo, node) - def update(repo, node): """update the working directory to node, merging linear changes""" stats = _merge.update(repo, node, False, False, None) @@ -262,6 +260,9 @@ repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n")) return stats[3] > 0 +# naming conflict in clone() +_update = update + def clean(repo, node, show_stats=True): """forcibly switch the working directory to node, clobbering changes""" stats = _merge.update(repo, node, False, True, None) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/hgweb/hgweb_mod.py Wed Dec 31 15:04:18 2008 +0100 @@ -161,11 +161,13 @@ # process the web interface request try: - tmpl = self.templater(req) ctype = tmpl('mimetype', encoding=self.encoding) ctype = templater.stringify(ctype) + # check allow_read / deny_read config options + self.check_perm(req, None) + if cmd == '': req.form['cmd'] = [tmpl.cache['default']] cmd = req.form['cmd'][0] @@ -180,20 +182,20 @@ content = getattr(webcommands, cmd)(self, req, tmpl) req.respond(HTTP_OK, ctype) - return ''.join(content), + return content except revlog.LookupError, err: req.respond(HTTP_NOT_FOUND, ctype) msg = str(err) if 'manifest' not in msg: msg = 'revision not found: %s' % err.name - return ''.join(tmpl('error', error=msg)), + return tmpl('error', error=msg) except (RepoError, revlog.RevlogError), inst: req.respond(HTTP_SERVER_ERROR, ctype) - return ''.join(tmpl('error', error=str(inst))), + return tmpl('error', error=str(inst)) except ErrorResponse, inst: req.respond(inst.code, ctype) - return ''.join(tmpl('error', error=inst.message)), + return tmpl('error', error=inst.message) def templater(self, req): @@ -226,23 +228,16 @@ def motd(**map): yield self.config("web", "motd", "") - def sessionvars(**map): - fields = [] - if 'style' in req.form: - style = req.form['style'][0] - if style != self.config('web', 'style', ''): - fields.append(('style', style)) - - separator = req.url[-1] == '?' and ';' or '?' - for name, value in fields: - yield dict(name=name, value=value, separator=separator) - separator = ';' - # figure out which style to use - style = self.config("web", "style", "") + vars = {} + style = self.config("web", "style", "paper") if 'style' in req.form: style = req.form['style'][0] + vars['style'] = style + + start = req.url[-1] == '?' and '&' or '?' + sessionvars = webutil.sessionvars(vars, start) mapfile = style_map(self.templatepath, style) if not self.reponame: @@ -278,11 +273,24 @@ def check_perm(self, req, op): '''Check permission for operation based on request data (including - authentication info. Return true if op allowed, else false.''' + authentication info). Return if op allowed, else raise an ErrorResponse + exception.''' + + user = req.env.get('REMOTE_USER') + + deny_read = self.configlist('web', 'deny_read') + if deny_read and (not user or deny_read == ['*'] or user in deny_read): + raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized') + + allow_read = self.configlist('web', 'allow_read') + result = (not allow_read) or (allow_read == ['*']) or (user in allow_read) + if not result: + raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized') if op == 'pull' and not self.allowpull: raise ErrorResponse(HTTP_OK, '') - elif op == 'pull': + # op is None when checking allow/deny_read permissions for a web-browser request + elif op == 'pull' or op is None: return # enforce that you can only push using POST requests @@ -296,8 +304,6 @@ if self.configbool('web', 'push_ssl', True) and scheme != 'https': raise ErrorResponse(HTTP_OK, 'ssl required') - user = req.env.get('REMOTE_USER') - deny = self.configlist('web', 'deny_push') if deny and (not user or deny == ['*'] or user in deny): raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized') diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/hgweb/hgwebdir_mod.py Wed Dec 31 15:04:18 2008 +0100 @@ -25,7 +25,7 @@ self.parentui = parentui or ui.ui(report_untrusted=False, interactive = False) self.motd = None - self.style = None + self.style = 'paper' self.stripecount = None self.repos_sorted = ('name', False) self._baseurl = None @@ -51,7 +51,26 @@ if cp.has_option('web', 'baseurl'): self._baseurl = cp.get('web', 'baseurl') if cp.has_section('paths'): - self.repos.extend(cleannames(cp.items('paths'))) + paths = cleannames(cp.items('paths')) + for prefix, root in paths: + roothead, roottail = os.path.split(root) + # "foo = /bar/*" makes every subrepo of /bar/ to be + # mounted as foo/subrepo + # and "foo = /bar/**" does even recurse inside the + # subdirectories, remember to use it without working dir. + try: + recurse = {'*': False, '**': True}[roottail] + except KeyError: + self.repos.append((prefix, root)) + continue + roothead = os.path.normpath(roothead) + for path in util.walkrepos(roothead, followsym=True, + recurse=recurse): + path = os.path.normpath(path) + name = util.pconvert(path[len(roothead):]).strip('/') + if prefix: + name = prefix + '/' + name + self.repos.append((name, path)) if cp.has_section('collections'): for prefix, root in cp.items('collections'): for path in util.walkrepos(root, followsym=True): @@ -72,6 +91,28 @@ req = wsgirequest(env, respond) return self.run_wsgi(req) + def read_allowed(self, ui, req): + """Check allow_read and deny_read config options of a repo's ui object + to determine user permissions. By default, with neither option set (or + both empty), allow all users to read the repo. There are two ways a + user can be denied read access: (1) deny_read is not empty, and the + user is unauthenticated or deny_read contains user (or *), and (2) + allow_read is not empty and the user is not in allow_read. Return True + if user is allowed to read the repo, else return False.""" + + user = req.env.get('REMOTE_USER') + + deny_read = ui.configlist('web', 'deny_read', default=None, untrusted=True) + if deny_read and (not user or deny_read == ['*'] or user in deny_read): + return False + + allow_read = ui.configlist('web', 'allow_read', default=None, untrusted=True) + # by default, allow reading if no allow_read option has been set + if (not allow_read) or (allow_read == ['*']) or (user in allow_read): + return True + + return False + def run_wsgi(self, req): try: @@ -94,7 +135,7 @@ # top-level index elif not virtual: req.respond(HTTP_OK, ctype) - return ''.join(self.makeindex(req, tmpl)), + return self.makeindex(req, tmpl) # nested indexes and hgwebs @@ -116,7 +157,7 @@ subdir = virtual + '/' if [r for r in repos if r.startswith(subdir)]: req.respond(HTTP_OK, ctype) - return ''.join(self.makeindex(req, tmpl, subdir)), + return self.makeindex(req, tmpl, subdir) up = virtual.rfind('/') if up < 0: @@ -125,11 +166,11 @@ # prefixes not found req.respond(HTTP_NOT_FOUND, ctype) - return ''.join(tmpl("notfound", repo=virtual)), + return tmpl("notfound", repo=virtual) except ErrorResponse, err: req.respond(err.code, ctype) - return ''.join(tmpl('error', error=err.message or '')), + return tmpl('error', error=err.message or '') finally: tmpl = None @@ -175,6 +216,9 @@ if u.configbool("web", "hidden", untrusted=True): continue + if not self.read_allowed(u, req): + continue + parts = [name] if 'PATH_INFO' in req.env: parts.insert(0, req.env['PATH_INFO'].rstrip('/')) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/hgweb/webcommands.py Wed Dec 31 15:04:18 2008 +0100 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os, mimetypes, re, cgi +import os, mimetypes, re, cgi, copy import webutil from mercurial import revlog, archival, templatefilters from mercurial.node import short, hex, nullid @@ -227,6 +227,7 @@ def changeset(web, req, tmpl): ctx = webutil.changectx(web.repo, req) showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node()) + showbranch = webutil.nodebranchnodefault(ctx) parents = ctx.parents() files = [] @@ -246,6 +247,7 @@ parent=webutil.siblings(parents), child=webutil.siblings(ctx.children()), changesettag=showtags, + changesetbranch=showbranch, author=ctx.user(), desc=ctx.description(), date=ctx.date(), @@ -526,13 +528,13 @@ if not numrevs: # file doesn't exist at all raise rev = webutil.changectx(web.repo, req).rev() - first = fl.linkrev(fl.node(0)) + first = fl.linkrev(0) if rev < first: # current rev is from before file existed raise frev = numrevs - 1 - while fl.linkrev(fl.node(frev)) > rev: + while fl.linkrev(frev) > rev: frev -= 1 - fctx = web.repo.filectx(f, fl.linkrev(fl.node(frev))) + fctx = web.repo.filectx(f, fl.linkrev(frev)) count = fctx.filerev() + 1 pagelen = web.maxshortchanges @@ -555,7 +557,11 @@ "rename": webutil.renamelink(fctx), "parent": webutil.siblings(fctx.parents()), "child": webutil.siblings(fctx.children()), - "desc": ctx.description()}) + "desc": ctx.description(), + "tags": webutil.nodetagsdict(web.repo, ctx.node()), + "branch": webutil.nodebranchnodefault(ctx), + "inbranch": webutil.nodeinbranch(web.repo, ctx), + "branches": webutil.nodebranchdict(web.repo, ctx)}) if limit > 0: l = l[:limit] @@ -619,21 +625,27 @@ rev = webutil.changectx(web.repo, req).rev() bg_height = 39 + revcount = 25 + if 'revcount' in req.form: + revcount = int(req.form.get('revcount', [revcount])[0]) + tmpl.defaults['sessionvars']['revcount'] = revcount + + lessvars = copy.copy(tmpl.defaults['sessionvars']) + lessvars['revcount'] = revcount / 2 + morevars = copy.copy(tmpl.defaults['sessionvars']) + morevars['revcount'] = revcount * 2 + max_rev = len(web.repo) - 1 - revcount = min(max_rev, int(req.form.get('revcount', [25])[0])) + revcount = min(max_rev, revcount) revnode = web.repo.changelog.node(rev) revnode_hex = hex(revnode) uprev = min(max_rev, rev + revcount) downrev = max(0, rev - revcount) - lessrev = max(0, rev - revcount / 2) - - maxchanges = web.maxshortchanges or web.maxchanges count = len(web.repo) - changenav = webutil.revnavgen(rev, maxchanges, count, web.repo.changectx) + changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx) tree = list(graphmod.graph(web.repo, rev, downrev)) canvasheight = (len(tree) + 1) * bg_height - 27; - data = [] for i, (ctx, vtx, edges) in enumerate(tree): node = short(ctx.node()) @@ -646,7 +658,6 @@ data.append((node, vtx, edges, desc, user, age, branch, ctx.tags())) return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev, - lessrev=lessrev, revcountmore=revcount and 2 * revcount or 1, - revcountless=revcount / 2, downrev=downrev, - canvasheight=canvasheight, bg_height=bg_height, - jsdata=data, node=revnode_hex, changenav=changenav) + lessvars=lessvars, morevars=morevars, downrev=downrev, + canvasheight=canvasheight, jsdata=data, bg_height=bg_height, + node=revnode_hex, changenav=changenav) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/hgweb/webutil.py Wed Dec 31 15:04:18 2008 +0100 @@ -6,7 +6,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os +import os, copy from mercurial import match, patch from mercurial.node import hex, nullid from mercurial.repo import RepoError @@ -126,7 +126,7 @@ ctx = repo[changeid] except RepoError: man = repo.manifest - ctx = repo[man.linkrev(man.lookup(changeid))] + ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))] return ctx @@ -196,3 +196,19 @@ block.append(chunk) yield tmpl('diffblock', parity=parity.next(), lines=prettyprintlines(''.join(block))) + +class sessionvars(object): + def __init__(self, vars, start='?'): + self.start = start + self.vars = vars + def __getitem__(self, key): + return self.vars[key] + def __setitem__(self, key, value): + self.vars[key] = value + def __copy__(self): + return sessionvars(copy.copy(self.vars), self.start) + def __iter__(self): + separator = self.start + for key, value in self.vars.iteritems(): + yield {'name': key, 'value': str(value), 'separator': separator} + separator = '&' diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/hgweb/wsgicgi.py --- a/mercurial/hgweb/wsgicgi.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/hgweb/wsgicgi.py Wed Dec 31 15:04:18 2008 +0100 @@ -17,6 +17,9 @@ environ = dict(os.environ.items()) environ.setdefault('PATH_INFO', '') + if '.cgi' in environ['PATH_INFO']: + environ['PATH_INFO'] = environ['PATH_INFO'].split('.cgi', 1)[1] + environ['wsgi.input'] = sys.stdin environ['wsgi.errors'] = sys.stderr environ['wsgi.version'] = (1, 0) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/hook.py --- a/mercurial/hook.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/hook.py Wed Dec 31 15:04:18 2008 +0100 @@ -96,19 +96,20 @@ oldstdout = os.dup(sys.__stdout__.fileno()) os.dup2(sys.__stderr__.fileno(), sys.__stdout__.fileno()) - for hname, cmd in util.sort(ui.configitems('hooks')): - if hname.split('.')[0] != name or not cmd: - continue - if callable(cmd): - r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r - elif cmd.startswith('python:'): - r = _pythonhook(ui, repo, name, hname, cmd[7:].strip(), - args, throw) or r - else: - r = _exthook(ui, repo, hname, cmd, args, throw) or r - - if _redirect: - os.dup2(oldstdout, sys.__stdout__.fileno()) - os.close(oldstdout) + try: + for hname, cmd in util.sort(ui.configitems('hooks')): + if hname.split('.')[0] != name or not cmd: + continue + if callable(cmd): + r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r + elif cmd.startswith('python:'): + r = _pythonhook(ui, repo, name, hname, cmd[7:].strip(), + args, throw) or r + else: + r = _exthook(ui, repo, hname, cmd, args, throw) or r + finally: + if _redirect: + os.dup2(oldstdout, sys.__stdout__.fileno()) + os.close(oldstdout) return r diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/httprepo.py --- a/mercurial/httprepo.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/httprepo.py Wed Dec 31 15:04:18 2008 +0100 @@ -148,13 +148,16 @@ raise util.UnexpectedOutput(_("unexpected response:"), d) def between(self, pairs): - n = " ".join(["-".join(map(hex, p)) for p in pairs]) - d = self.do_read("between", pairs=n) - try: - p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ] - return p - except: - raise util.UnexpectedOutput(_("unexpected response:"), d) + batch = 8 # avoid giant requests + r = [] + for i in xrange(0, len(pairs), batch): + n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]]) + d = self.do_read("between", pairs=n) + try: + r += [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ] + except: + raise util.UnexpectedOutput(_("unexpected response:"), d) + return r def changegroup(self, nodes, kind): n = " ".join(map(hex, nodes)) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/localrepo.py --- a/mercurial/localrepo.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/localrepo.py Wed Dec 31 15:04:18 2008 +0100 @@ -437,10 +437,14 @@ partial[b] = c.node() def lookup(self, key): - if key == '.': + if isinstance(key, int): + return self.changelog.node(key) + elif key == '.': return self.dirstate.parents()[0] elif key == 'null': return nullid + elif key == 'tip': + return self.changelog.tip() n = self.changelog._match(key) if n: return n @@ -978,7 +982,7 @@ else: ctx2 = self[node2] - working = ctx2 == self[None] + working = ctx2.rev() is None parentworking = working and ctx1 == self['.'] match = match or match_.always(self.root, self.getcwd()) listignored, listclean, listunknown = ignored, clean, unknown @@ -1262,6 +1266,22 @@ (and so we know that the rest of the nodes are missing in remote, see outgoing) """ + return self.findcommonincoming(remote, base, heads, force)[1] + + def findcommonincoming(self, remote, base=None, heads=None, force=False): + """Return a tuple (common, missing roots, heads) used to identify + missing nodes from remote. + + If base dict is specified, assume that these nodes and their parents + exist on the remote side and that no child of a node of base exists + in both remote and self. + Furthermore base will be updated to include the nodes that exists + in self and remote but no children exists in self and remote. + If a list of heads is specified, return only nodes which are heads + or ancestors of these heads. + + All the ancestors of base are in self and in remote. + """ m = self.changelog.nodemap search = [] fetch = {} @@ -1276,8 +1296,8 @@ if self.changelog.tip() == nullid: base[nullid] = 1 if heads != [nullid]: - return [nullid] - return [] + return [nullid], [nullid], list(heads) + return [nullid], [], [] # assume we're closer to the tip than the root # and start by examining the heads @@ -1290,8 +1310,9 @@ else: base[h] = 1 + heads = unknown if not unknown: - return [] + return base.keys(), [], [] req = dict.fromkeys(unknown) reqcnt = 0 @@ -1386,7 +1407,7 @@ self.ui.debug(_("%d total queries\n") % reqcnt) - return fetch.keys() + return base.keys(), fetch.keys(), heads def findoutgoing(self, remote, base=None, heads=None, force=False): """Return list of nodes that are roots of subsets not in remote @@ -1439,7 +1460,8 @@ def pull(self, remote, heads=None, force=False): lock = self.lock() try: - fetch = self.findincoming(remote, heads=heads, force=force) + common, fetch, rheads = self.findcommonincoming(remote, heads=heads, + force=force) if fetch == [nullid]: self.ui.status(_("requesting all changes\n")) @@ -1447,10 +1469,13 @@ self.ui.status(_("no changes found\n")) return 0 + if heads is None and remote.capable('changegroupsubset'): + heads = rheads + if heads is None: cg = remote.changegroup(fetch, 'pull') else: - if 'changegroupsubset' not in remote.capabilities: + if not remote.capable('changegroupsubset'): raise util.Abort(_("Partial pull cannot be done because other repository doesn't support changegroupsubset.")) cg = remote.changegroupsubset(fetch, heads, 'pull') return self.addchangegroup(cg, 'pull', remote.url()) @@ -1471,11 +1496,11 @@ return self.push_addchangegroup(remote, force, revs) def prepush(self, remote, force, revs): - base = {} + common = {} remote_heads = remote.heads() - inc = self.findincoming(remote, base, remote_heads, force=force) + inc = self.findincoming(remote, common, remote_heads, force=force) - update, updated_heads = self.findoutgoing(remote, base, remote_heads) + update, updated_heads = self.findoutgoing(remote, common, remote_heads) if revs is not None: msng_cl, bases, heads = self.changelog.nodesbetween(update, revs) else: @@ -1521,7 +1546,8 @@ if revs is None: - cg = self.changegroup(update, 'push') + # use the fast path, no race possible on push + cg = self._changegroup(common.keys(), 'push') else: cg = self.changegroupsubset(update, revs, 'push') return cg, remote_heads @@ -1703,7 +1729,7 @@ # If a 'missing' manifest thinks it belongs to a changenode # the recipient is assumed to have, obviously the recipient # must have that manifest. - linknode = cl.node(mnfst.linkrev(n)) + linknode = cl.node(mnfst.linkrev(mnfst.rev(n))) if linknode in has_cl_set: has_mnfst_set[n] = 1 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set) @@ -1769,7 +1795,7 @@ # assume the recipient must have, then the recipient must have # that filenode. for n in msngset: - clnode = cl.node(filerevlog.linkrev(n)) + clnode = cl.node(filerevlog.linkrev(filerevlog.rev(n))) if clnode in has_cl_set: hasset[n] = 1 prune_parents(filerevlog, hasset, msngset) @@ -1892,9 +1918,8 @@ def gennodelst(log): for r in log: - n = log.node(r) - if log.linkrev(n) in revset: - yield n + if log.linkrev(r) in revset: + yield log.node(r) def changed_file_collector(changedfileset): def collect_changed_files(clnode): @@ -1905,7 +1930,7 @@ def lookuprevlink_func(revlog): def lookuprevlink(n): - return cl.node(revlog.linkrev(n)) + return cl.node(revlog.linkrev(revlog.rev(n))) return lookuprevlink def gengroup(): diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/manifest.py --- a/mercurial/manifest.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/manifest.py Wed Dec 31 15:04:18 2008 +0100 @@ -35,7 +35,8 @@ return mfdict def readdelta(self, node): - return self.parse(mdiff.patchtext(self.delta(node))) + r = self.rev(node) + return self.parse(mdiff.patchtext(self.revdiff(r - 1, r))) def read(self, node): if node == nullid: return manifestdict() # don't upset local cache @@ -62,6 +63,8 @@ while i < lenm and m[i] != c: i += 1 return i + if not s: + return (lo, lo) lenm = len(m) if not hi: hi = lenm diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/mdiff.py --- a/mercurial/mdiff.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/mdiff.py Wed Dec 31 15:04:18 2008 +0100 @@ -160,7 +160,7 @@ if opts.showfunc: # walk backwards from the start of the context # to find a line starting with an alphanumeric char. - for x in xrange(astart, -1, -1): + for x in xrange(astart - 1, -1, -1): t = l1[x].rstrip() if funcre.match(t): func = ' ' + t[:40] diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/patch.py --- a/mercurial/patch.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/patch.py Wed Dec 31 15:04:18 2008 +0100 @@ -9,7 +9,7 @@ from i18n import _ from node import hex, nullid, short import base85, cmdutil, mdiff, util, revlog, diffhelpers, copies -import cStringIO, email.Parser, os, re, errno +import cStringIO, email.Parser, os, re, errno, math import sys, tempfile, zlib gitre = re.compile('diff --git a/(.*) b/(.*)') @@ -22,17 +22,20 @@ # helper functions -def copyfile(src, dst, basedir=None): - if not basedir: - basedir = os.getcwd() - - abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)] +def copyfile(src, dst, basedir): + abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]] if os.path.exists(absdst): raise util.Abort(_("cannot create %s: destination already exists") % dst) - if not os.path.isdir(basedir): - os.makedirs(basedir) + dstdir = os.path.dirname(absdst) + if dstdir and not os.path.isdir(dstdir): + try: + os.makedirs(dstdir) + except IOError: + raise util.Abort( + _("cannot create %s: unable to create destination directory") + % dst) util.copyfile(abssrc, absdst) @@ -207,6 +210,8 @@ gp.path = line[8:].rstrip() elif line.startswith('deleted file'): gp.op = 'DELETE' + # is the deleted file a symlink? + gp.setmode(int(line.rstrip()[-6:], 8)) elif line.startswith('new file mode '): gp.op = 'ADD' gp.setmode(int(line.rstrip()[-6:], 8)) @@ -228,27 +233,22 @@ contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') class patchfile: - def __init__(self, ui, fname, missing=False): + def __init__(self, ui, fname, opener, missing=False): self.fname = fname + self.opener = opener self.ui = ui self.lines = [] self.exists = False self.missing = missing if not missing: try: - fp = file(fname, 'rb') - self.lines = fp.readlines() + self.lines = self.readlines(fname) self.exists = True except IOError: pass else: self.ui.warn(_("unable to find '%s' for patching\n") % self.fname) - if not self.exists: - dirname = os.path.dirname(fname) - if dirname and not os.path.isdir(dirname): - os.makedirs(dirname) - self.hash = {} self.dirty = 0 self.offset = 0 @@ -257,6 +257,23 @@ self.printfile(False) self.hunks = 0 + def readlines(self, fname): + fp = self.opener(fname, 'r') + try: + return fp.readlines() + finally: + fp.close() + + def writelines(self, fname, lines): + fp = self.opener(fname, 'w') + try: + fp.writelines(lines) + finally: + fp.close() + + def unlink(self, fname): + os.unlink(fname) + def printfile(self, warn): if self.fileprinted: return @@ -307,35 +324,24 @@ self.ui.warn( _("%d out of %d hunks FAILED -- saving rejects to file %s\n") % (len(self.rej), self.hunks, fname)) - try: os.unlink(fname) - except: - pass - fp = file(fname, 'wb') - base = os.path.basename(self.fname) - fp.write("--- %s\n+++ %s\n" % (base, base)) - for x in self.rej: - for l in x.hunk: - fp.write(l) - if l[-1] != '\n': - fp.write("\n\ No newline at end of file\n") + + def rejlines(): + base = os.path.basename(self.fname) + yield "--- %s\n+++ %s\n" % (base, base) + for x in self.rej: + for l in x.hunk: + yield l + if l[-1] != '\n': + yield "\n\ No newline at end of file\n" + + self.writelines(fname, rejlines()) def write(self, dest=None): - if self.dirty: - if not dest: - dest = self.fname - st = None - try: - st = os.lstat(dest) - except OSError, inst: - if inst.errno != errno.ENOENT: - raise - if st and st.st_nlink > 1: - os.unlink(dest) - fp = file(dest, 'wb') - if st and st.st_nlink > 1: - os.chmod(dest, st.st_mode) - fp.writelines(self.lines) - fp.close() + if not self.dirty: + return + if not dest: + dest = self.fname + self.writelines(dest, self.lines) def close(self): self.write() @@ -360,9 +366,9 @@ self.rej.append(h) return -1 - if isinstance(h, binhunk): + if isinstance(h, githunk): if h.rmfile(): - os.unlink(self.fname) + self.unlink(self.fname) else: self.lines[:] = h.new() self.offset += len(h.new()) @@ -379,7 +385,7 @@ orig_start = start if diffhelpers.testhunk(old, self.lines, start) == 0: if h.rmfile(): - os.unlink(self.fname) + self.unlink(self.fname) else: self.lines[start : start + h.lena] = h.new() self.offset += h.lenb - h.lena @@ -650,12 +656,12 @@ def new(self, fuzz=0, toponly=False): return self.fuzzit(self.b, fuzz, toponly) -class binhunk: - 'A binary patch file. Only understands literals so far.' +class githunk(object): + """A git hunk""" def __init__(self, gitpatch): self.gitpatch = gitpatch self.text = None - self.hunk = ['GIT binary patch\n'] + self.hunk = [] def createfile(self): return self.gitpatch.op in ('ADD', 'RENAME', 'COPY') @@ -669,6 +675,12 @@ def new(self): return [self.text] +class binhunk(githunk): + 'A binary patch file. Only understands literals so far.' + def __init__(self, gitpatch): + super(binhunk, self).__init__(gitpatch) + self.hunk = ['GIT binary patch\n'] + def extract(self, lr): line = lr.readline() self.hunk.append(line) @@ -696,6 +708,18 @@ len(text), size) self.text = text +class symlinkhunk(githunk): + """A git symlink hunk""" + def __init__(self, gitpatch, hunk): + super(symlinkhunk, self).__init__(gitpatch) + self.hunk = hunk + + def complete(self): + return True + + def fix_newline(self): + return + def parsefilename(str): # --- filename \t|space stuff s = str[4:].rstrip('\r\n') @@ -770,9 +794,7 @@ def readline(self): if self.buf: - l = self.buf[0] - del self.buf[0] - return l + return self.buf.pop(0) return self.fp.readline() def __iter__(self): @@ -855,6 +877,10 @@ create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD' remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE' current_hunk = hunk(x, hunknum + 1, lr, context, create, remove) + if remove: + gpatch = changed.get(afile[2:]) + if gpatch and gpatch.mode[0]: + current_hunk = symlinkhunk(gpatch, current_hunk) except PatchError, err: ui.debug(err) current_hunk = None @@ -938,6 +964,7 @@ err = 0 current_file = None gitpatches = None + opener = util.opener(os.getcwd()) def closefile(): if not current_file: @@ -960,11 +987,11 @@ afile, bfile, first_hunk = values try: if sourcefile: - current_file = patchfile(ui, sourcefile) + current_file = patchfile(ui, sourcefile, opener) else: current_file, missing = selectfile(afile, bfile, first_hunk, strip, reverse) - current_file = patchfile(ui, current_file, missing) + current_file = patchfile(ui, current_file, opener, missing) except PatchError, err: ui.warn(str(err) + '\n') current_file, current_hunk = None, None @@ -975,9 +1002,7 @@ cwd = os.getcwd() for gp in gitpatches: if gp.op in ('COPY', 'RENAME'): - src, dst = [util.canonpath(cwd, cwd, x) - for x in [gp.oldpath, gp.path]] - copyfile(src, dst) + copyfile(gp.oldpath, gp.path, cwd) changed[gp.path] = gp else: raise util.Abort(_('unsupported parser state: %s') % state) @@ -1002,7 +1027,7 @@ ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'), context=get('unified', getter=ui.config)) -def updatedir(ui, repo, patches): +def updatedir(ui, repo, patches, similarity=0): '''Update dirstate after patch application according to metadata''' if not patches: return @@ -1026,7 +1051,7 @@ for src, dst in copies: repo.copy(src, dst) removes = removes.keys() - if removes: + if (not similarity) and removes: repo.remove(util.sort(removes), True) for f in patches: gp = patches[f] @@ -1037,9 +1062,9 @@ if gp.op == 'ADD' and not os.path.exists(dst): flags = (isexec and 'x' or '') + (islink and 'l' or '') repo.wwrite(gp.path, '', flags) - else: + elif gp.op != 'DELETE': util.set_flags(dst, islink, isexec) - cmdutil.addremove(repo, cfiles) + cmdutil.addremove(repo, cfiles, similarity=similarity) files = patches.keys() files.extend([r for r in removes if r not in files]) return util.sort(files) @@ -1294,7 +1319,8 @@ if not fp: fp = cmdutil.make_file(repo, template, node, total=total, - seqno=seqno, revwidth=revwidth) + seqno=seqno, revwidth=revwidth, + mode='ab') if fp != sys.stdout and hasattr(fp, 'name'): repo.ui.note("%s\n" % fp.name) @@ -1318,13 +1344,57 @@ for seqno, rev in enumerate(revs): single(rev, seqno+1, fp) -def diffstat(patchlines): - if not util.find_exe('diffstat'): - return - output = util.filter('\n'.join(patchlines), - 'diffstat -p1 -w79 2>%s' % util.nulldev) - stat = [l.lstrip() for l in output.splitlines(True)] - last = stat.pop() - stat.insert(0, last) - stat = ''.join(stat) - return stat +def diffstatdata(lines): + filename = None + for line in lines: + if line.startswith('diff'): + if filename: + yield (filename, adds, removes) + # set numbers to 0 anyway when starting new file + adds = 0 + removes = 0 + if line.startswith('diff --git'): + filename = gitre.search(line).group(1) + else: + # format: "diff -r ... -r ... file name" + filename = line.split(None, 5)[-1] + elif line.startswith('+') and not line.startswith('+++'): + adds += 1 + elif line.startswith('-') and not line.startswith('---'): + removes += 1 + yield (filename, adds, removes) + +def diffstat(lines): + output = [] + stats = list(diffstatdata(lines)) + width = util.termwidth() - 2 + + maxtotal, maxname = 0, 0 + totaladds, totalremoves = 0, 0 + for filename, adds, removes in stats: + totaladds += adds + totalremoves += removes + maxname = max(maxname, len(filename)) + maxtotal = max(maxtotal, adds+removes) + + countwidth = len(str(maxtotal)) + graphwidth = width - countwidth - maxname + if graphwidth < 10: + graphwidth = 10 + + factor = int(math.ceil(float(maxtotal) / graphwidth)) + + for filename, adds, removes in stats: + # If diffstat runs out of room it doesn't print anything, which + # isn't very useful, so always print at least one + or - if there + # were at least some changes + pluses = '+' * max(adds/factor, int(bool(adds))) + minuses = '-' * max(removes/factor, int(bool(removes))) + output.append(' %-*s | %*.d %s%s\n' % (maxname, filename, countwidth, + adds+removes, pluses, minuses)) + + if stats: + output.append(' %d files changed, %d insertions(+), %d deletions(-)\n' % + (len(stats), totaladds, totalremoves)) + + return ''.join(output) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/repair.py --- a/mercurial/repair.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/repair.py Wed Dec 31 15:04:18 2008 +0100 @@ -41,8 +41,7 @@ startrev = count = len(revlog) # find the truncation point of the revlog for i in xrange(0, count): - node = revlog.node(i) - lrev = revlog.linkrev(node) + lrev = revlog.linkrev(i) if lrev >= link: startrev = i + 1 break @@ -51,7 +50,7 @@ # (we have to manually save these guys) for i in xrange(startrev, count): node = revlog.node(i) - lrev = revlog.linkrev(node) + lrev = revlog.linkrev(i) if lrev < link: extra.append((node, cl.node(lrev))) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/revlog.py --- a/mercurial/revlog.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/revlog.py Wed Dec 31 15:04:18 2008 +0100 @@ -500,11 +500,12 @@ raise LookupError(node, self.indexfile, _('no node')) def node(self, rev): return self.index[rev][7] - def linkrev(self, node): - return self.index[self.rev(node)][4] + def linkrev(self, rev): + return self.index[rev][4] def parents(self, node): - d = self.index[self.rev(node)][5:7] - return (self.node(d[0]), self.node(d[1])) + i = self.index + d = i[self.rev(node)] + return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline def parentrevs(self, rev): return self.index[rev][5:7] def start(self, rev): @@ -872,16 +873,16 @@ if len(id) < 40: try: # hex(node)[:...] - bin_id = bin(id[:len(id) & ~1]) # grab an even number of digits - node = None - for n in self.nodemap: - if n.startswith(bin_id) and hex(n).startswith(id): - if node is not None: - raise LookupError(id, self.indexfile, - _('ambiguous identifier')) - node = n - if node is not None: - return node + l = len(id) / 2 # grab an even number of digits + bin_id = bin(id[:l*2]) + nl = [n for n in self.nodemap if n[:l] == bin_id] + nl = [n for n in nl if hex(n).startswith(id)] + if len(nl) > 0: + if len(nl) == 1: + return nl[0] + raise LookupError(id, self.indexfile, + _('ambiguous identifier')) + return None except TypeError: pass @@ -941,11 +942,6 @@ return decompress(c) - def delta(self, node): - """return or calculate a delta between a node and its predecessor""" - r = self.rev(node) - return self.revdiff(r - 1, r) - def revdiff(self, rev1, rev2): """return or calculate a delta between two revisions""" if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2): diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/store.py --- a/mercurial/store.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/store.py Wed Dec 31 15:04:18 2008 +0100 @@ -61,6 +61,9 @@ # encode third letter ('aux' -> 'au~78') ec = "~%02x" % ord(n[2]) n = n[0:2] + ec + n[3:] + if n[-1] in '. ': + # encode last period or space ('foo...' -> 'foo..~2e') + n = n[:-1] + "~%02x" % ord(n[-1]) res.append(n) return '/'.join(res) @@ -111,6 +114,9 @@ sdirs = [] for p in parts[:-1]: d = p[:DIR_PREFIX_LEN] + if d[-1] in '. ': + # Windows can't access dirs ending in period or space + d = d[:-1] + '_' t = '/'.join(sdirs) + '/' + d if len(t) > _MAX_SHORTENED_DIRS_LEN: break diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/templater.py --- a/mercurial/templater.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/templater.py Wed Dec 31 15:04:18 2008 +0100 @@ -44,7 +44,8 @@ template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))" r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]") - def __init__(self, mapfile, filters={}, defaults={}, cache={}): + def __init__(self, mapfile, filters={}, defaults={}, cache={}, + minchunk=1024, maxchunk=65536): '''set up template engine. mapfile is name of file to read map definitions from. filters is dict of functions. each transforms a value into another. @@ -55,6 +56,7 @@ self.base = (mapfile and os.path.dirname(mapfile)) or '' self.filters = filters self.defaults = defaults + self.minchunk, self.maxchunk = minchunk, maxchunk if not mapfile: return @@ -130,6 +132,13 @@ yield v def __call__(self, t, **map): + stream = self.expand(t, **map) + if self.minchunk: + stream = util.increasingchunks(stream, min=self.minchunk, + max=self.maxchunk) + return stream + + def expand(self, t, **map): '''Perform expansion. t is name of map element to expand. map contains added elements for use during expansion. Is a generator.''' tmpl = self._template(t) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/transaction.py --- a/mercurial/transaction.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/transaction.py Wed Dec 31 15:04:18 2008 +0100 @@ -12,7 +12,7 @@ # of the GNU General Public License, incorporated herein by reference. from i18n import _ -import os +import os, errno class transaction(object): def __init__(self, report, opener, journal, after=None, createmode=None): @@ -102,7 +102,11 @@ if o: opener(f, "a").truncate(int(o)) else: - fn = opener(f).name - os.unlink(fn) + try: + fn = opener(f).name + os.unlink(fn) + except OSError, inst: + if inst.errno != errno.ENOENT: + raise os.unlink(file) diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/ui.py --- a/mercurial/ui.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/ui.py Wed Dec 31 15:04:18 2008 +0100 @@ -350,7 +350,7 @@ if not user: raise util.Abort(_("Please specify a username.")) if "\n" in user: - raise util.Abort(_("username %s contains a newline\n") % `user`) + raise util.Abort(_("username %s contains a newline\n") % repr(user)) return user def shortuser(self, user): @@ -407,7 +407,8 @@ import readline # force demandimport to really load the module readline.read_history_file - except ImportError: + # windows sometimes raises something other than ImportError + except Exception: pass line = raw_input(prompt) # When stdin is in binary mode on Windows, it can cause @@ -421,7 +422,9 @@ If not interactive -- the default is returned """ - if not self.interactive: return default + if not self.interactive: + self.note(msg, ' ', default, "\n") + return default while True: try: r = self._readline(msg + ' ') diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/util.py --- a/mercurial/util.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/util.py Wed Dec 31 15:04:18 2008 +0100 @@ -13,7 +13,7 @@ """ from i18n import _ -import cStringIO, errno, getpass, re, shutil, sys, tempfile +import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil import imp @@ -81,6 +81,8 @@ popen3 = os.popen3 +_encodingfixup = {'646': 'ascii', 'ANSI_X3.4-1968': 'ascii'} + try: _encoding = os.environ.get("HGENCODING") if sys.platform == 'darwin' and not _encoding: @@ -91,6 +93,7 @@ _encoding = locale.getlocale()[1] if not _encoding: _encoding = locale.getpreferredencoding() or 'ascii' + _encoding = _encodingfixup.get(_encoding, _encoding) except locale.Error: _encoding = 'ascii' _encodingmode = os.environ.get("HGENCODINGMODE", "strict") @@ -290,6 +293,37 @@ l.sort() return l +def increasingchunks(source, min=1024, max=65536): + '''return no less than min bytes per chunk while data remains, + doubling min after each chunk until it reaches max''' + def log2(x): + if not x: + return 0 + i = 0 + while x: + x >>= 1 + i += 1 + return i - 1 + + buf = [] + blen = 0 + for chunk in source: + buf.append(chunk) + blen += len(chunk) + if blen >= min: + if min < max: + min = min << 1 + nmin = 1 << log2(blen) + if nmin > min: + min = nmin + if min > max: + min = max + yield ''.join(buf) + blen = 0 + buf = [] + if buf: + yield ''.join(buf) + class Abort(Exception): """Raised if a command needs to print an error and exit.""" @@ -576,7 +610,7 @@ if inc: dummy, inckinds, dummy = normalizepats(inc, 'glob') incmatch = matchfn(inckinds, '(?:/|$)') - excmatch = lambda fn: False + excmatch = never if exc: dummy, exckinds, dummy = normalizepats(exc, 'glob') excmatch = matchfn(exckinds, '(?:/|$)') @@ -671,6 +705,21 @@ if cwd is not None and oldcwd != cwd: os.chdir(oldcwd) +class SignatureError(Exception): + pass + +def checksignature(func): + '''wrap a function with code to check for calling errors''' + def check(*args, **kwargs): + try: + return func(*args, **kwargs) + except TypeError: + if len(traceback.extract_tb(sys.exc_info()[2])) == 1: + raise SignatureError + raise + + return check + # os.path.lexists is not available on python2.3 def lexists(filename): "test whether a file with this name exists. does not follow symlinks" @@ -1827,7 +1876,7 @@ else: return "%s..." % (text[:maxlength-3]) -def walkrepos(path, followsym=False, seen_dirs=None): +def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): '''yield every hg repository under path, recursively.''' def errhandler(err): if err.filename == path: @@ -1852,11 +1901,15 @@ _add_dir_if_not_there(seen_dirs, path) for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler): if '.hg' in dirs: - dirs.remove('.hg') # don't recurse inside the .hg directory yield root # found a repository qroot = os.path.join(root, '.hg', 'patches') if os.path.isdir(os.path.join(qroot, '.hg')): yield qroot # we have a patch queue repo here + if recurse: + # avoid recursing inside the .hg directory + dirs.remove('.hg') + else: + dirs[:] = [] # don't descend further elif followsym: newdirs = [] for d in dirs: @@ -1932,3 +1985,24 @@ def uirepr(s): # Avoid double backslash in Windows path repr() return repr(s).replace('\\\\', '\\') + +def termwidth(): + if 'COLUMNS' in os.environ: + try: + return int(os.environ['COLUMNS']) + except ValueError: + pass + try: + import termios, array, fcntl + for dev in (sys.stdout, sys.stdin): + try: + fd = dev.fileno() + if not os.isatty(fd): + continue + arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8) + return array.array('h', arri)[1] + except ValueError: + pass + except ImportError: + pass + return 80 diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/util_win32.py --- a/mercurial/util_win32.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/util_win32.py Wed Dec 31 15:04:18 2008 +0100 @@ -19,7 +19,7 @@ import util from win32com.shell import shell,shellcon -class WinError: +class WinError(Exception): winerror_map = { winerror.ERROR_ACCESS_DENIED: errno.EACCES, winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES, @@ -245,7 +245,7 @@ def user_rcpath_win32(): '''return os-specific hgrc search path to the user dir''' userdir = os.path.expanduser('~') - if sys.getwindowsversion() != 2 and userdir == '~': + if sys.getwindowsversion()[3] != 2 and userdir == '~': # We are on win < nt: fetch the APPDATA directory location and use # the parent directory as the user home dir. appdir = shell.SHGetPathFromIDList( @@ -292,7 +292,7 @@ raise WinIOError(err, name) def __iter__(self): - for line in self.read().splitlines(True): + for line in self.readlines(): yield line def read(self, count=-1): @@ -311,6 +311,11 @@ except pywintypes.error, err: raise WinIOError(err) + def readlines(self, sizehint=None): + # splitlines() splits on single '\r' while readlines() + # does not. cStringIO has a well behaving readlines() and is fast. + return cStringIO.StringIO(self.read()).readlines() + def write(self, data): try: if 'a' in self.mode: diff -r cab1cf26ca58 -r a9221c7f51a4 mercurial/verify.py --- a/mercurial/verify.py Wed Dec 31 14:29:51 2008 +0100 +++ b/mercurial/verify.py Wed Dec 31 15:04:18 2008 +0100 @@ -70,7 +70,7 @@ warn(_("warning: `%s' uses revlog format 0") % name) def checkentry(obj, i, node, seen, linkrevs, f): - lr = obj.linkrev(node) + lr = obj.linkrev(obj.rev(node)) if lr < 0 or (havecl and lr not in linkrevs): t = "unexpected" if lr < 0 or lr >= len(cl): @@ -135,7 +135,7 @@ elif f != "/dev/null": fns = filenodes.setdefault(f, {}) if fn not in fns: - fns[fn] = n + fns[fn] = i except Exception, inst: exc(lr, _("reading manifest delta %s") % short(n), inst) @@ -155,7 +155,8 @@ for f in util.sort(filenodes): if f not in filelinkrevs: try: - lr = min([repo.file(f).linkrev(n) for n in filenodes[f]]) + fl = repo.file(f) + lr = min([fl.linkrev(fl.rev(n)) for n in filenodes[f]]) except: lr = None err(lr, _("in manifest but not in changeset"), f) diff -r cab1cf26ca58 -r a9221c7f51a4 templates/changelog.tmpl --- a/templates/changelog.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -#header# -#repo|escape#: changelog - - - - - -
-shortlog -graph -tags -files -#archives%archiveentry# -rss -atom -
- -

changelog for #repo|escape#

- -
-{sessionvars%hiddenformentry} -

- - -navigate: #changenav%naventry# -

-
- -#entries%changelogentry# - -
-{sessionvars%hiddenformentry} -

- - -navigate: #changenav%naventry# -

-
- -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/changelogentry.tmpl --- a/templates/changelogentry.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ - - - - - - - - - - #parent%changelogparent# - #child%changelogchild# - #changelogtag# - - - - - - - - - - - - -
#date|age# ago:#desc|strip|firstline|escape#
changeset #rev#:#node|short#
author:#author|obfuscate#
date:#date|date#
files:#files#
diff -r cab1cf26ca58 -r a9221c7f51a4 templates/changeset.tmpl --- a/templates/changeset.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -#header# -#repo|escape#: changeset #node|short# - - - -
-changelog -shortlog -graph -tags -files -raw -#archives%archiveentry# -
- -

changeset: #desc|strip|escape|firstline#

- - - - - - -#parent%changesetparent# -#child%changesetchild# -#changesettag# - - - - - - - - - - - - - - - - -
changeset #rev#:#node|short#
author:#author|obfuscate#
date:#date|date# (#date|age# ago)
files:#files#
description:#desc|strip|escape|addbreaks#
- -
-#diff# -
- -#footer# - - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/changeset.tmpl --- a/templates/coal/changeset.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -{header} -{repo|escape}: {node|short} - - -
- - -
- -

{repo|escape}

-

changeset {rev}:{node|short} {changesettag}

- - - -
{desc|strip|escape|addbreaks}
- - - - - - - - - - - - - - - - - - - - - -
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%changesetparent}
children{child%changesetchild}
files{files}
- -
- - - - - -
linediff
-{diff} -
- -
-
-{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/error.tmpl --- a/templates/coal/error.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -{header} -{repo|escape}: error - - - -
- - -
- -

{repo|escape}

-

error

- - - -
-

-An error occurred while processing your request: -

-

-{error|escape} -

-
-
-
- -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/fileannotate.tmpl --- a/templates/coal/fileannotate.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -{header} -{repo|escape}: {file|escape} annotate - - - -
- - -
-

{repo|escape}

-

annotate {file|escape} @ {rev}:{node|short}

- - - -
{desc|strip|escape|addbreaks}
- - - - - - - - - - - - - - - - - - -{changesettag} -
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%filerevparent}
children{child%filerevchild}
- -
- -
- - - - - - -{annotate%annotateline} -
revlinesource
-
-
-
- -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/filediff.tmpl --- a/templates/coal/filediff.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -{header} -{repo|escape}: {file|escape} diff - - - -
- - -
-

{repo|escape}

-

diff {file|escape} @ {rev}:{node|short}

- - - -
{desc|strip|escape|addbreaks}
- - - - - - - - - - - - - - - - - - -{changesettag} -
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%filerevparent}
children{child%filerevchild}
- -
- - - - - -
linediff
- -{diff} -
-
-
- -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/filelog.tmpl --- a/templates/coal/filelog.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -{header} -{repo|escape}: {file|escape} history - - - - - -
- - -
-

{repo|escape}

-

log {file|escape}

- - - - - - - - - - - -{entries%filelogentry} -
ageauthordescription
- -
-
- -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/filelogentry.tmpl --- a/templates/coal/filelogentry.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ - - {date|age} - {author|person} - {desc|strip|firstline|escape} - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/filerevision.tmpl --- a/templates/coal/filerevision.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -{header} -{repo|escape}: {node|short} {file|escape} - - - -
- - -
-

{repo|escape}

-

view {file|escape} @ {rev}:{node|short}

- - - -
{desc|strip|escape|addbreaks}
- - - - - - - - - - - - - - - - - - -{changesettag} -
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%filerevparent}
children{child%filerevchild}
- -
- - - - - -{text%fileline} -
linesource
-
-
-
- -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/footer.tmpl --- a/templates/coal/footer.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -{motd} - - - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/graph.tmpl --- a/templates/coal/graph.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -{header} -{repo|escape}: revision graph - - - - - - -
- - -
-

{repo|escape}

-

graph

- - - - - - - -
-
    - -
      -
      - - - - - - -
      -
      - -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/index.tmpl --- a/templates/coal/index.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -{header} -Mercurial repositories index - - - -
      - -
      -

      Mercurial Repositories

      - - - - - - - - - - {entries%indexentry} -
      NameDescriptionContactLast change 
      -
      -
      -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/manifest.tmpl --- a/templates/coal/manifest.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -{header} -{repo|escape}: {node|short} {path|escape} - - - -
      - - -
      -

      {repo|escape}

      -

      directory {path|escape} @ {rev}:{node|short} {tags%changelogtag}

      - - - - - - - - - - - - - - -{dentries%direntry} -{fentries%fileentry} -
      namesizepermissions
      [up]drwxr-xr-x
      -
      -
      -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/map --- a/templates/coal/map Wed Dec 31 14:29:51 2008 +0100 +++ b/templates/coal/map Wed Dec 31 15:04:18 2008 +0100 @@ -2,13 +2,13 @@ mimetype = 'text/html; charset={encoding}' header = header.tmpl -footer = footer.tmpl -search = search.tmpl +footer = ../paper/footer.tmpl +search = ../paper/search.tmpl -changelog = shortlog.tmpl -shortlog = shortlog.tmpl -shortlogentry = shortlogentry.tmpl -graph = graph.tmpl +changelog = ../paper/shortlog.tmpl +shortlog = ../paper/shortlog.tmpl +shortlogentry = ../paper/shortlogentry.tmpl +graph = ../paper/graph.tmpl naventry = '{label|escape} ' navshortentry = '{label|escape} ' @@ -18,28 +18,28 @@ filenodelink = '{file|escape} ' filenolink = '{file|escape} ' fileellipses = '...' -changelogentry = shortlogentry.tmpl -searchentry = shortlogentry.tmpl -changeset = changeset.tmpl -manifest = manifest.tmpl +changelogentry = ../paper/shortlogentry.tmpl +searchentry = ../paper/shortlogentry.tmpl +changeset = ../paper/changeset.tmpl +manifest = ../paper/manifest.tmpl direntry = ' {basename|escape}/ {emptydirs|escape}drwxr-xr-x' fileentry = ' {basename|escape}{size}{permissions|permissions}' -filerevision = filerevision.tmpl -fileannotate = fileannotate.tmpl -filediff = filediff.tmpl -filelog = filelog.tmpl -fileline = '{linenumber}{line|escape}' -filelogentry = filelogentry.tmpl +filerevision = ../paper/filerevision.tmpl +fileannotate = ../paper/fileannotate.tmpl +filediff = ../paper/filediff.tmpl +filelog = ../paper/filelog.tmpl +fileline = '
      {linenumber} {line|escape}
      ' +filelogentry = ../paper/filelogentry.tmpl -annotateline = '{author|user}@{rev}{linenumber}{line|escape}' +annotateline = '{author|user}@{rev}{linenumber} {line|escape}' -diffblock = '{lines}
      ' -difflineplus = '{linenumber}{line|escape}' -difflineminus = '{linenumber}{line|escape}' -difflineat = '{linenumber}{line|escape}' -diffline = '{linenumber}{line|escape}' +diffblock = '
      {lines}
      ' +difflineplus = '{linenumber} {line|escape}' +difflineminus = '{linenumber} {line|escape}' +difflineat = '{linenumber} {line|escape}' +diffline = '{linenumber} {line|escape}' changelogparent = 'parent {rev}:{node|short}' @@ -54,19 +54,21 @@ changesetchild = '{node|short}' changelogchild = 'child{node|short}' fileannotatechild = 'child:{node|short}' -tags = tags.tmpl +tags = ../paper/tags.tmpl tagentry = '{tag|escape}{node|short}' changelogtag = '{name|escape} ' changesettag = '{tag|escape} ' +changelogbranchhead = '{name|escape} ' +changelogbranchname = '{name|escape} ' filediffparent = 'parent {rev}:{node|short}' filelogparent = 'parent {rev}:{node|short}' filediffchild = 'child {rev}:{node|short}' filelogchild = 'child {rev}:{node|short}' indexentry = '{name|escape}{description}{contact|obfuscate}{lastchange|age} ago{archives%indexarchiveentry}\n' indexarchiveentry = ' ↓{type|escape}' -index = index.tmpl +index = ../paper/index.tmpl archiveentry = '
    • {type|escape}
    • ' -notfound = notfound.tmpl -error = error.tmpl +notfound = ../paper/notfound.tmpl +error = ../paper/error.tmpl urlparameter = '{separator}{name}={value|urlescape}' hiddenformentry = '' diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/notfound.tmpl --- a/templates/coal/notfound.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -{header} -Mercurial repository not found - - - -

      Mercurial repository not found

      - -The specified repository "{repo|escape}" is unknown, sorry. - -Please go back to the main repository list page. - -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/search.tmpl --- a/templates/coal/search.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -{header} -{repo|escape}: searching for {query|escape} - - - -
      - - -
      -

      {repo|escape}

      -

      searching for '{query|escape}'

      - - - - - - - - - -{entries} -
      ageauthordescription
      - -
      -
      - -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/shortlog.tmpl --- a/templates/coal/shortlog.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -{header} -{repo|escape}: log - - - - - -
      - - -
      -

      {repo|escape}

      -

      log

      - - - - - - - - - - - -{entries%shortlogentry} -
      ageauthordescription
      - - -
      -
      - -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/shortlogentry.tmpl --- a/templates/coal/shortlogentry.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ - - {date|age} - {author|person} - {desc|strip|firstline|escape}{tags%changelogtag} - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/coal/tags.tmpl --- a/templates/coal/tags.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -{header} -{repo|escape}: tags - - - - - -
      - - -
      -

      {repo|escape}

      -

      tags

      - - - - - - - - -{entries%tagentry} -
      tagnode
      -
      -
      - -{footer} diff -r cab1cf26ca58 -r a9221c7f51a4 templates/error.tmpl --- a/templates/error.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -#header# -Mercurial Error - - - -

      Mercurial Error

      - -

      -An error occurred while processing your request: -

      -

      -#error|escape# -

      - -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/fileannotate.tmpl --- a/templates/fileannotate.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -#header# -#repo|escape#: #file|escape# annotate - - - - - -

      Annotate #file|escape#

      - - - - - -#parent%fileannotateparent# -#child%fileannotatechild# - - - - - - - - - - - - - - - -
      changeset #rev#:#node|short#
      author:#author|obfuscate#
      date:#date|date# (#date|age# ago)
      permissions:#permissions|permissions#
      description:{desc|strip|escape|addbreaks}
      - -
      - - -#annotate%annotateline# -
      - -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/filediff.tmpl --- a/templates/filediff.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -#header# -#repo|escape#: #file|escape# diff - - - - - -

      #file|escape#

      - - - - - - -#parent%filediffparent# -#child%filediffchild# -
      revision #rev#:#node|short#
      - -
      -#diff# -
      - -#footer# - - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/filelog.tmpl --- a/templates/filelog.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#header# -#repo|escape#: #file|escape# history - - - - - - - -

      #file|escape# revision history

      - -

      navigate: {nav%filenaventry}

      - -#entries%filelogentry# - -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/filelogentry.tmpl --- a/templates/filelogentry.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ - - - - - - - - - #rename%filelogrename# - - - - - - - - -
      #date|age# ago:#desc|strip|firstline|escape#
      revision #filerev#: - - #node|short# - (diff) - (annotate) -
      author:#author|obfuscate#
      date:#date|date#
      - - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/filerevision.tmpl --- a/templates/filerevision.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -#header# -#repo|escape#:#file|escape# - - - - - -

      #file|escape#

      - - - - - -#parent%filerevparent# -#child%filerevchild# - - - - - - - - - - - - - -
      changeset #rev#:#node|short#
      author:#author|obfuscate#
      date:#date|date# (#date|age# ago)
      permissions:#permissions|permissions#
      description:{desc|strip|escape|addbreaks}
      - -
      -#text%fileline#
      -
      - -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/footer.tmpl --- a/templates/footer.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -#motd# - - - - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/gitweb/graph.tmpl --- a/templates/gitweb/graph.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ b/templates/gitweb/graph.tmpl Wed Dec 31 15:04:18 2008 +0100 @@ -4,6 +4,7 @@ href="{url}atom-log" title="Atom feed for #repo|escape#"/> + @@ -25,8 +26,8 @@ tags | files
      -less -more +less +more | #changenav%navgraphentry#
      @@ -111,8 +112,8 @@ diff -r cab1cf26ca58 -r a9221c7f51a4 templates/graph.tmpl --- a/templates/graph.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -#header# -#repo|escape#: graph - - - - - - - - -

      graph

      - -
      -{sessionvars%hiddenformentry} -

      - - -navigate: #changenav%navgraphentry# -

      -
      - - - -
      -
        - -
          -
          - - - - -
          -{sessionvars%hiddenformentry} -

          - - -navigate: #changenav%navgraphentry# -

          -
          - -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/header.tmpl --- a/templates/header.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ - - - - - - diff -r cab1cf26ca58 -r a9221c7f51a4 templates/index.tmpl --- a/templates/index.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -#header# -Mercurial repositories index - - - -

          Mercurial Repositories

          - - - - - - - - - - #entries%indexentry# -
          NameDescriptionContactLast change 
          - -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/manifest.tmpl --- a/templates/manifest.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#header# -#repo|escape#: files for changeset #node|short# - - - -
          -changelog -shortlog -graph -tags -changeset -#archives%archiveentry# -
          - -

          files for changeset #node|short#: #path|escape#

          - - - - -#dentries%direntry# -#fentries%fileentry# -
          drwxr-xr-x  -   -   - [up] -
          -#footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/map --- a/templates/map Wed Dec 31 14:29:51 2008 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -default = 'shortlog' -mimetype = 'text/html; charset={encoding}' -header = header.tmpl -footer = footer.tmpl -search = search.tmpl -changelog = changelog.tmpl -shortlog = shortlog.tmpl -shortlogentry = shortlogentry.tmpl -graph = graph.tmpl -naventry = '{label|escape} ' -navshortentry = '{label|escape} ' -navgraphentry = '{label|escape} ' -filenaventry = '{label|escape} ' -filedifflink = '#file|escape# ' -filenodelink = '#file|escape# ' -filenolink = '{file|escape} ' -fileellipses = '...' -changelogentry = changelogentry.tmpl -searchentry = changelogentry.tmpl -changeset = changeset.tmpl -manifest = manifest.tmpl -direntry = 'drwxr-xr-x   #basename|escape#/ #emptydirs|urlescape#' -fileentry = '#permissions|permissions# #date|isodate# #size# #basename|escape#' -filerevision = filerevision.tmpl -fileannotate = fileannotate.tmpl -filediff = filediff.tmpl -filelog = filelog.tmpl -fileline = '
          #linenumber##line|escape#
          ' -filelogentry = filelogentry.tmpl -annotateline = '#author|user#@#rev##linenumber#
          #line|escape#
          ' -difflineplus = '#linenumber##line|escape#' -difflineminus = '#linenumber##line|escape#' -difflineat = '#linenumber##line|escape#' -diffline = '#linenumber##line|escape#' -changelogparent = 'parent #rev#:#node|short#' -changesetparent = 'parent #rev#:#node|short#' -filerevparent = 'parent:{rename%filerename}{node|short}' -filerename = '{file|escape}@' -filelogrename = 'base:#file|escape#@#node|short#' -fileannotateparent = 'parent:{rename%filerename}{node|short}' -changesetchild = 'child #rev#:#node|short#' -changelogchild = 'child #rev#:#node|short#' -filerevchild = 'child:#node|short#' -fileannotatechild = 'child:#node|short#' -tags = tags.tmpl -tagentry = '
        • #node# #tag|escape#
        • ' -diffblock = '
          #lines#
          ' -changelogtag = 'tag:#tag|escape#' -changesettag = 'tag:#tag|escape#' -filediffparent = 'parent #rev#:#node|short#' -filelogparent = 'parent #rev#:#node|short#' -filediffchild = 'child #rev#:#node|short#' -filelogchild = 'child #rev#:#node|short#' -indexentry = '#name|escape##description##contact|obfuscate##lastchange|age# agoRSS Atom #archives%archiveentry#' -index = index.tmpl -archiveentry = '#type|escape# ' -notfound = notfound.tmpl -error = error.tmpl -urlparameter = '#separator##name#=#value|urlescape#' -hiddenformentry = '' diff -r cab1cf26ca58 -r a9221c7f51a4 templates/monoblue/graph.tmpl --- a/templates/monoblue/graph.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ b/templates/monoblue/graph.tmpl Wed Dec 31 15:04:18 2008 +0100 @@ -59,7 +59,7 @@ var revlink = '
        • '; revlink += '_DESC'; - revlink += '_DATE ago, by _USER
        • '; + revlink += '_TAGS_DATE ago, by _USER'; graph.vertex = function(x, y, color, parity, cur) { @@ -79,6 +79,26 @@ item = item.replace(/_USER/, cur[4]); item = item.replace(/_DATE/, cur[5]); + var tagspan = ''; + if (cur[7].length || (cur[6][0] != 'default' || cur[6][1])) { + tagspan = ''; + if (cur[6][1]) { + tagspan += ''; + tagspan += cur[6][0] + ' '; + } else if (!cur[6][1] && cur[6][0] != 'default') { + tagspan += ''; + tagspan += cur[6][0] + ' '; + } + if (cur[7].length) { + for (var t in cur[7]) { + var tag = cur[7][t]; + tagspan += '' + tag + ' '; + } + } + tagspan += ''; + } + + item = item.replace(/_TAGS/, tagspan); return [bg, item]; } @@ -89,7 +109,9 @@
          -#changenav%navgraphentry# + less + more + | {changenav%navgraphentry}
          #footer# diff -r cab1cf26ca58 -r a9221c7f51a4 templates/monoblue/index.tmpl --- a/templates/monoblue/index.tmpl Wed Dec 31 14:29:51 2008 +0100 +++ b/templates/monoblue/index.tmpl Wed Dec 31 15:04:18 2008 +0100 @@ -1,26 +1,39 @@ #header# #repo|escape#: Mercurial repositories index - - - - - - - - - - - - - #entries%indexentry# -
          NameDescriptionContactLast change  
          -