summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:49:51 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:49:51 +0100
commita498da43c7fdb9f24b73680c02a4a3588cc62d9a (patch)
treedaf8119dae1749b5165b68033a1b23a7375ce9ce /contrib
downloadmercurial-tarball-a498da43c7fdb9f24b73680c02a4a3588cc62d9a.tar.gz
Tarball conversion
Diffstat (limited to 'contrib')
-rw-r--r--contrib/bash_completion593
-rwxr-xr-xcontrib/buildrpm110
-rw-r--r--contrib/casesmash.py34
-rwxr-xr-xcontrib/check-code.py450
-rwxr-xr-xcontrib/convert-repo27
-rwxr-xr-xcontrib/debugcmdserver.py47
-rw-r--r--contrib/debugshell.py21
-rwxr-xr-xcontrib/dumprevlog25
-rwxr-xr-xcontrib/hg-ssh86
-rw-r--r--contrib/hgfixes/__init__.py0
-rw-r--r--contrib/hgfixes/fix_bytes.py97
-rw-r--r--contrib/hgfixes/fix_bytesmod.py63
-rw-r--r--contrib/hgfixes/fix_leftover_imports.py108
-rwxr-xr-xcontrib/hgk4061
-rw-r--r--contrib/hgsh/Makefile13
-rw-r--r--contrib/hgsh/hgsh.c440
-rwxr-xr-xcontrib/hgweb.fcgi19
-rw-r--r--contrib/hgweb.wsgi18
-rw-r--r--contrib/logo-droplets.svg5
-rw-r--r--contrib/macosx/Readme.html37
-rw-r--r--contrib/macosx/Welcome.html20
-rw-r--r--contrib/macosx/macosx-build.txt11
-rw-r--r--contrib/memory.py36
-rw-r--r--contrib/mercurial.el1293
-rwxr-xr-xcontrib/mercurial.spec87
-rw-r--r--contrib/mergetools.hgrc123
-rw-r--r--contrib/mq.el417
-rw-r--r--contrib/perf.py252
-rwxr-xr-xcontrib/plan9/9diff42
-rw-r--r--contrib/plan9/README39
-rw-r--r--contrib/plan9/hgrc.d/9diff.rc7
-rw-r--r--contrib/plan9/hgrc.d/factotum.rc4
-rw-r--r--contrib/plan9/mkfile37
-rw-r--r--contrib/plan9/proto24
-rw-r--r--contrib/pylintrc313
-rw-r--r--contrib/python-hook-examples.py22
-rw-r--r--contrib/sample.hgrc133
-rw-r--r--contrib/setup3k.py373
-rw-r--r--contrib/shrink-revlog.py294
-rwxr-xr-xcontrib/simplemerge67
-rw-r--r--contrib/tcsh_completion50
-rwxr-xr-xcontrib/tcsh_completion_build.sh74
-rwxr-xr-xcontrib/tmplrewrite.py23
-rwxr-xr-xcontrib/undumprevlog37
-rw-r--r--contrib/vim/HGAnnotate.vim27
-rw-r--r--contrib/vim/hg-menu.vim93
-rw-r--r--contrib/vim/hgcommand.vim1703
-rw-r--r--contrib/vim/hgtest.vim41
-rw-r--r--contrib/vim/patchreview.txt97
-rw-r--r--contrib/vim/patchreview.vim868
-rw-r--r--contrib/win32/ReadMe.html162
-rw-r--r--contrib/win32/buildlocal.bat9
-rw-r--r--contrib/win32/hg.bat12
-rw-r--r--contrib/win32/hgwebdir_wsgi.py95
-rw-r--r--contrib/win32/mercurial.icobin0 -> 2238 bytes
-rw-r--r--contrib/win32/mercurial.ini97
-rw-r--r--contrib/win32/mercurial.iss148
-rw-r--r--contrib/win32/postinstall.txt9
-rw-r--r--contrib/win32/win32-build.txt133
-rw-r--r--contrib/wix/COPYING.rtfbin0 -> 1686 bytes
-rw-r--r--contrib/wix/README.txt31
-rw-r--r--contrib/wix/contrib.wxs44
-rw-r--r--contrib/wix/defines.wxi9
-rw-r--r--contrib/wix/dist.wxs31
-rw-r--r--contrib/wix/doc.wxs50
-rw-r--r--contrib/wix/guids.wxi51
-rw-r--r--contrib/wix/help.wxs34
-rw-r--r--contrib/wix/hg.cmd3
-rw-r--r--contrib/wix/i18n.wxs26
-rw-r--r--contrib/wix/locale.wxs34
-rw-r--r--contrib/wix/mercurial.wxs172
-rw-r--r--contrib/wix/templates.wxs222
-rw-r--r--contrib/xml.rnc41
-rw-r--r--contrib/zsh_completion1077
74 files changed, 15351 insertions, 0 deletions
diff --git a/contrib/bash_completion b/contrib/bash_completion
new file mode 100644
index 0000000..dfd149d
--- /dev/null
+++ b/contrib/bash_completion
@@ -0,0 +1,593 @@
+# bash completion for the Mercurial distributed SCM
+
+# Docs:
+#
+# If you source this file from your .bashrc, bash should be able to
+# complete a command line that uses hg with all the available commands
+# and options and sometimes even arguments.
+#
+# Mercurial allows you to define additional commands through extensions.
+# Bash should be able to automatically figure out the name of these new
+# commands and their options. See below for how to define _hg_opt_foo
+# and _hg_cmd_foo functions to fine-tune the completion for option and
+# non-option arguments, respectively.
+#
+#
+# Notes about completion for specific commands:
+#
+# - the completion function for the email command from the patchbomb
+# extension will try to call _hg_emails to get a list of e-mail
+# addresses. It's up to the user to define this function. For
+# example, put the addresses of the lists that you usually patchbomb
+# in ~/.patchbomb-to and the addresses that you usually use to send
+# the patchbombs in ~/.patchbomb-from and use something like this:
+#
+# _hg_emails()
+# {
+# if [ -r ~/.patchbomb-$1 ]; then
+# cat ~/.patchbomb-$1
+# fi
+# }
+#
+#
+# Writing completion functions for additional commands:
+#
+# If it exists, the function _hg_cmd_foo will be called without
+# arguments to generate the completion candidates for the hg command
+# "foo". If the command receives some arguments that aren't options
+# even though they start with a "-", you can define a function called
+# _hg_opt_foo to generate the completion candidates. If _hg_opt_foo
+# doesn't return 0, regular completion for options is attempted.
+#
+# In addition to the regular completion variables provided by bash,
+# the following variables are also set:
+# - $hg - the hg program being used (e.g. /usr/bin/hg)
+# - $cmd - the name of the hg command being completed
+# - $cmd_index - the index of $cmd in $COMP_WORDS
+# - $cur - the current argument being completed
+# - $prev - the argument before $cur
+# - $global_args - "|"-separated list of global options that accept
+# an argument (e.g. '--cwd|-R|--repository')
+# - $canonical - 1 if we canonicalized $cmd before calling the function
+# 0 otherwise
+#
+
+shopt -s extglob
+
+_hg_cmd()
+{
+ HGPLAIN=1 "$hg" "$@" 2>/dev/null
+}
+
+_hg_commands()
+{
+ local commands
+ commands="$(HGPLAINEXCEPT=alias _hg_cmd debugcomplete "$cur")" || commands=""
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$commands' -- "$cur"))
+}
+
+_hg_paths()
+{
+ local paths="$(_hg_cmd paths -q)"
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$paths' -- "$cur"))
+}
+
+_hg_repos()
+{
+ local i
+ for i in $(compgen -d -- "$cur"); do
+ test ! -d "$i"/.hg || COMPREPLY=(${COMPREPLY[@]:-} "$i")
+ done
+}
+
+_hg_status()
+{
+ local files="$(_hg_cmd status -n$1 .)"
+ local IFS=$'\n'
+ compopt -o filenames 2>/dev/null
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
+}
+
+_hg_tags()
+{
+ local tags="$(_hg_cmd tags -q)"
+ local IFS=$'\n'
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$tags' -- "$cur"))
+}
+
+_hg_branches()
+{
+ local branches="$(_hg_cmd branches -q)"
+ local IFS=$'\n'
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$branches' -- "$cur"))
+}
+
+_hg_bookmarks()
+{
+ local bookmarks="$(_hg_cmd bookmarks -q)"
+ local IFS=$'\n'
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$bookmarks' -- "$cur"))
+}
+
+_hg_labels()
+{
+ _hg_tags
+ _hg_branches
+ _hg_bookmarks
+}
+
+# this is "kind of" ugly...
+_hg_count_non_option()
+{
+ local i count=0
+ local filters="$1"
+
+ for ((i=1; $i<=$COMP_CWORD; i++)); do
+ if [[ "${COMP_WORDS[i]}" != -* ]]; then
+ if [[ ${COMP_WORDS[i-1]} == @($filters|$global_args) ]]; then
+ continue
+ fi
+ count=$(($count + 1))
+ fi
+ done
+
+ echo $(($count - 1))
+}
+
+_hg()
+{
+ local cur prev cmd cmd_index opts i
+ # global options that receive an argument
+ local global_args='--cwd|-R|--repository'
+ local hg="$1"
+ local canonical=0
+
+ COMPREPLY=()
+ cur="$2"
+ prev="$3"
+
+ # searching for the command
+ # (first non-option argument that doesn't follow a global option that
+ # receives an argument)
+ for ((i=1; $i<=$COMP_CWORD; i++)); do
+ if [[ ${COMP_WORDS[i]} != -* ]]; then
+ if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
+ cmd="${COMP_WORDS[i]}"
+ cmd_index=$i
+ break
+ fi
+ fi
+ done
+
+ if [[ "$cur" == -* ]]; then
+ if [ "$(type -t "_hg_opt_$cmd")" = function ] && "_hg_opt_$cmd"; then
+ return
+ fi
+
+ opts=$(_hg_cmd debugcomplete --options "$cmd")
+
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur"))
+ return
+ fi
+
+ # global options
+ case "$prev" in
+ -R|--repository)
+ _hg_paths
+ _hg_repos
+ return
+ ;;
+ --cwd)
+ # Stick with default bash completion
+ return
+ ;;
+ esac
+
+ if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then
+ _hg_commands
+ return
+ fi
+
+ # try to generate completion candidates for whatever command the user typed
+ local help
+ if _hg_command_specific; then
+ return
+ fi
+
+ # canonicalize the command name and try again
+ help=$(_hg_cmd help "$cmd")
+ if [ $? -ne 0 ]; then
+ # Probably either the command doesn't exist or it's ambiguous
+ return
+ fi
+ cmd=${help#hg }
+ cmd=${cmd%%[$' \n']*}
+ canonical=1
+ _hg_command_specific
+}
+
+_hg_command_specific()
+{
+ if [ "$(type -t "_hg_cmd_$cmd")" = function ]; then
+ "_hg_cmd_$cmd"
+ return 0
+ fi
+
+ if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" == --rev ]; then
+ if [ $canonical = 1 ]; then
+ _hg_labels
+ return 0
+ elif [[ status != "$cmd"* ]]; then
+ _hg_labels
+ return 0
+ else
+ return 1
+ fi
+ fi
+
+ case "$cmd" in
+ help)
+ _hg_commands
+ ;;
+ export)
+ if _hg_ext_mq_patchlist qapplied && [ "${COMPREPLY[*]}" ]; then
+ return 0
+ fi
+ _hg_labels
+ ;;
+ manifest|update)
+ _hg_labels
+ ;;
+ pull|push|outgoing|incoming)
+ _hg_paths
+ _hg_repos
+ ;;
+ paths)
+ _hg_paths
+ ;;
+ add)
+ _hg_status "u"
+ ;;
+ merge)
+ _hg_labels
+ ;;
+ commit|record)
+ _hg_status "mar"
+ ;;
+ remove)
+ _hg_status "d"
+ ;;
+ forget)
+ _hg_status "a"
+ ;;
+ diff)
+ _hg_status "mar"
+ ;;
+ revert)
+ _hg_status "mard"
+ ;;
+ clone)
+ local count=$(_hg_count_non_option)
+ if [ $count = 1 ]; then
+ _hg_paths
+ fi
+ _hg_repos
+ ;;
+ debugindex|debugindexdot)
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.i" -- "$cur"))
+ ;;
+ debugdata)
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.d" -- "$cur"))
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+
+ return 0
+}
+
+complete -o bashdefault -o default -F _hg hg \
+ || complete -o default -F _hg hg
+
+
+# Completion for commands provided by extensions
+
+# bookmarks
+_hg_bookmarks()
+{
+ local bookmarks="$(_hg_cmd bookmarks --quiet )"
+ local IFS=$'\n'
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$bookmarks' -- "$cur"))
+}
+
+_hg_cmd_bookmarks()
+{
+ if [[ "$prev" = @(-d|--delete|-m|--rename) ]]; then
+ _hg_bookmarks
+ return
+ fi
+}
+
+# mq
+_hg_ext_mq_patchlist()
+{
+ local patches
+ patches=$(_hg_cmd $1)
+ if [ $? -eq 0 ] && [ "$patches" ]; then
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$patches' -- "$cur"))
+ return 0
+ fi
+ return 1
+}
+
+_hg_ext_mq_queues()
+{
+ local root=$(_hg_cmd root)
+ local n
+ for n in $(cd "$root"/.hg && compgen -d -- "$cur"); do
+ # I think we're usually not interested in the regular "patches" queue
+ # so just filter it.
+ if [ "$n" != patches ] && [ -e "$root/.hg/$n/series" ]; then
+ COMPREPLY=(${COMPREPLY[@]:-} "$n")
+ fi
+ done
+}
+
+_hg_cmd_qpop()
+{
+ if [[ "$prev" = @(-n|--name) ]]; then
+ _hg_ext_mq_queues
+ return
+ fi
+ _hg_ext_mq_patchlist qapplied
+}
+
+_hg_cmd_qpush()
+{
+ if [[ "$prev" = @(-n|--name) ]]; then
+ _hg_ext_mq_queues
+ return
+ fi
+ _hg_ext_mq_patchlist qunapplied
+}
+
+_hg_cmd_qgoto()
+{
+ if [[ "$prev" = @(-n|--name) ]]; then
+ _hg_ext_mq_queues
+ return
+ fi
+ _hg_ext_mq_patchlist qseries
+}
+
+_hg_cmd_qdelete()
+{
+ local qcmd=qunapplied
+ if [[ "$prev" = @(-r|--rev) ]]; then
+ qcmd=qapplied
+ fi
+ _hg_ext_mq_patchlist $qcmd
+}
+
+_hg_cmd_qfinish()
+{
+ if [[ "$prev" = @(-a|--applied) ]]; then
+ return
+ fi
+ _hg_ext_mq_patchlist qapplied
+}
+
+_hg_cmd_qsave()
+{
+ if [[ "$prev" = @(-n|--name) ]]; then
+ _hg_ext_mq_queues
+ return
+ fi
+}
+
+_hg_cmd_strip()
+{
+ _hg_labels
+}
+
+_hg_cmd_qcommit()
+{
+ local root=$(_hg_cmd root)
+ # this is run in a sub-shell, so we can't use _hg_status
+ local files=$(cd "$root/.hg/patches" && _hg_cmd status -nmar)
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
+}
+
+_hg_cmd_qfold()
+{
+ _hg_ext_mq_patchlist qunapplied
+}
+
+_hg_cmd_qrename()
+{
+ _hg_ext_mq_patchlist qseries
+}
+
+_hg_cmd_qheader()
+{
+ _hg_ext_mq_patchlist qseries
+}
+
+_hg_cmd_qclone()
+{
+ local count=$(_hg_count_non_option)
+ if [ $count = 1 ]; then
+ _hg_paths
+ fi
+ _hg_repos
+}
+
+_hg_ext_mq_guards()
+{
+ _hg_cmd qselect --series | sed -e 's/^.//'
+}
+
+_hg_cmd_qselect()
+{
+ local guards=$(_hg_ext_mq_guards)
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$guards' -- "$cur"))
+}
+
+_hg_cmd_qguard()
+{
+ local prefix=''
+
+ if [[ "$cur" == +* ]]; then
+ prefix=+
+ elif [[ "$cur" == -* ]]; then
+ prefix=-
+ fi
+ local ncur=${cur#[-+]}
+
+ if ! [ "$prefix" ]; then
+ _hg_ext_mq_patchlist qseries
+ return
+ fi
+
+ local guards=$(_hg_ext_mq_guards)
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -P $prefix -W '$guards' -- "$ncur"))
+}
+
+_hg_opt_qguard()
+{
+ local i
+ for ((i=cmd_index+1; i<=COMP_CWORD; i++)); do
+ if [[ ${COMP_WORDS[i]} != -* ]]; then
+ if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
+ _hg_cmd_qguard
+ return 0
+ fi
+ elif [ "${COMP_WORDS[i]}" = -- ]; then
+ _hg_cmd_qguard
+ return 0
+ fi
+ done
+ return 1
+}
+
+_hg_cmd_qqueue()
+{
+ local q
+ local queues
+ local opts="--list --create --rename --delete --purge"
+
+ queues=$( _hg_cmd qqueue --quiet )
+
+ COMPREPLY=( $( compgen -W "${opts} ${queues}" "${cur}" ) )
+}
+
+
+# hbisect
+_hg_cmd_bisect()
+{
+ local i subcmd
+
+ # find the sub-command
+ for ((i=cmd_index+1; i<=COMP_CWORD; i++)); do
+ if [[ ${COMP_WORDS[i]} != -* ]]; then
+ if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
+ subcmd="${COMP_WORDS[i]}"
+ break
+ fi
+ fi
+ done
+
+ if [ -z "$subcmd" ] || [ $COMP_CWORD -eq $i ] || [ "$subcmd" = help ]; then
+ COMPREPLY=(${COMPREPLY[@]:-}
+ $(compgen -W 'bad good help init next reset' -- "$cur"))
+ return
+ fi
+
+ case "$subcmd" in
+ good|bad)
+ _hg_labels
+ ;;
+ esac
+
+ return
+}
+
+
+# patchbomb
+_hg_cmd_email()
+{
+ case "$prev" in
+ -c|--cc|-t|--to|-f|--from|--bcc)
+ # we need an e-mail address. let the user provide a function
+ # to get them
+ if [ "$(type -t _hg_emails)" = function ]; then
+ local arg=to
+ if [[ "$prev" == @(-f|--from) ]]; then
+ arg=from
+ fi
+ local addresses=$(_hg_emails $arg)
+ COMPREPLY=(${COMPREPLY[@]:-}
+ $(compgen -W '$addresses' -- "$cur"))
+ fi
+ return
+ ;;
+ -m|--mbox)
+ # fallback to standard filename completion
+ return
+ ;;
+ -s|--subject)
+ # free form string
+ return
+ ;;
+ esac
+
+ _hg_labels
+ return
+}
+
+
+# gpg
+_hg_cmd_sign()
+{
+ _hg_labels
+}
+
+
+# transplant
+_hg_cmd_transplant()
+{
+ case "$prev" in
+ -s|--source)
+ _hg_paths
+ _hg_repos
+ return
+ ;;
+ --filter)
+ # standard filename completion
+ return
+ ;;
+ esac
+
+ # all other transplant options values and command parameters are revisions
+ _hg_labels
+ return
+}
+
+# shelve
+_hg_shelves()
+{
+ local shelves="$(_hg_cmd unshelve -l .)"
+ local IFS=$'\n'
+ COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$shelves' -- "$cur"))
+}
+
+_hg_cmd_shelve()
+{
+ _hg_status "mard"
+}
+
+_hg_cmd_unshelve()
+{
+ _hg_shelves
+}
diff --git a/contrib/buildrpm b/contrib/buildrpm
new file mode 100755
index 0000000..6fedb03
--- /dev/null
+++ b/contrib/buildrpm
@@ -0,0 +1,110 @@
+#!/bin/sh
+#
+# Build a Mercurial RPM in place.
+#
+# Tested on
+# - Fedora 8 (with docutils 0.5)
+# - Fedora 11
+# - OpenSuse 11.2
+
+cd "`dirname $0`/.."
+HG="$PWD/hg"
+PYTHONPATH="$PWD/mercurial/pure"
+export PYTHONPATH
+
+specfile=contrib/mercurial.spec
+if [ ! -f $specfile ]; then
+ echo "Cannot find $specfile!" 1>&2
+ exit 1
+fi
+
+if [ ! -d .hg ]; then
+ echo 'You are not inside a Mercurial repository!' 1>&2
+ exit 1
+fi
+
+if $HG id -i | grep '+$' > /dev/null 2>&1; then
+ echo -n "Your local changes will NOT be in the RPM. Continue [y/n] ? "
+ read answer
+ if echo $answer | grep -iv '^y'; then
+ exit
+ fi
+fi
+
+rpmdir="$PWD/rpmbuild"
+
+rm -rf $rpmdir
+mkdir -p $rpmdir/SOURCES $rpmdir/SPECS $rpmdir/RPMS $rpmdir/SRPMS $rpmdir/BUILD
+
+# make setup.py build the version string
+python setup.py build_py -c -d .
+hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
+
+if echo $hgversion | grep -- '-' > /dev/null 2>&1; then
+ # nightly build case, version is like 1.3.1+250-20b91f91f9ca
+ version=`echo $hgversion | cut -d- -f1`
+ release=`echo $hgversion | cut -d- -f2 | sed -e 's/+.*//'`
+else
+ # official tag, version is like 1.3.1
+ version=`echo $hgversion | sed -e 's/+.*//'`
+ release='0'
+fi
+
+$HG archive -t tgz $rpmdir/SOURCES/mercurial-$version.tar.gz
+rpmspec=$rpmdir/SPECS/mercurial-$version.spec
+
+sed -e "s,^Version:.*,Version: $version," \
+ -e "s,^Release:.*,Release: $release," \
+ $specfile > $rpmspec
+
+echo >> $rpmspec
+echo "%changelog" >> $rpmspec
+
+if echo $version | grep '+' > /dev/null 2>&1; then
+ latesttag="`echo $version | sed -e 's/+.*//'`"
+ $HG log -r .:"$latesttag" -fM \
+ --template '{date|hgdate}\t{author}\t{desc|firstline}\n' | python -c '
+import sys, time
+
+def datestr(date, format):
+ return time.strftime(format, time.gmtime(float(date[0]) - date[1]))
+
+changelog = []
+for l in sys.stdin.readlines():
+ tok = l.split("\t")
+ hgdate = tuple(int(v) for v in tok[0].split())
+ changelog.append((datestr(hgdate, "%F"), tok[1], hgdate, tok[2]))
+prevtitle = ""
+for l in sorted(changelog, reverse=True):
+ title = "* %s %s" % (datestr(l[2], "%a %b %d %Y"), l[1])
+ if prevtitle != title:
+ prevtitle = title
+ print
+ print title
+ print "- %s" % l[3].strip()
+' >> $rpmspec
+
+else
+
+ $HG log \
+ --template '{date|hgdate}\t{author}\t{desc|firstline}\n' \
+ .hgtags | python -c '
+import sys, time
+
+def datestr(date, format):
+ return time.strftime(format, time.gmtime(float(date[0]) - date[1]))
+
+for l in sys.stdin.readlines():
+ tok = l.split("\t")
+ hgdate = tuple(int(v) for v in tok[0].split())
+ print "* %s %s\n- %s" % (datestr(hgdate, "%a %b %d %Y"), tok[1], tok[2])
+' >> $rpmspec
+
+fi
+
+rpmbuild --define "_topdir $rpmdir" -ba $rpmspec --clean
+if [ $? = 0 ]; then
+ echo
+ echo "Packages are in $rpmdir:"
+ ls -l $rpmdir/*RPMS/*
+fi
diff --git a/contrib/casesmash.py b/contrib/casesmash.py
new file mode 100644
index 0000000..071e014
--- /dev/null
+++ b/contrib/casesmash.py
@@ -0,0 +1,34 @@
+import sys, os, __builtin__
+from mercurial import util
+
+def lowerwrap(scope, funcname):
+ f = getattr(scope, funcname)
+ def wrap(fname, *args, **kwargs):
+ d, base = os.path.split(fname)
+ try:
+ files = os.listdir(d or '.')
+ except OSError, inst:
+ files = []
+ if base in files:
+ return f(fname, *args, **kwargs)
+ for fn in files:
+ if fn.lower() == base.lower():
+ return f(os.path.join(d, fn), *args, **kwargs)
+ return f(fname, *args, **kwargs)
+ scope.__dict__[funcname] = wrap
+
+def normcase(path):
+ return path.lower()
+
+os.path.normcase = normcase
+
+for f in 'file open'.split():
+ lowerwrap(__builtin__, f)
+
+for f in "chmod chown open lstat stat remove unlink".split():
+ lowerwrap(os, f)
+
+for f in "exists lexists".split():
+ lowerwrap(os.path, f)
+
+lowerwrap(util, 'posixfile')
diff --git a/contrib/check-code.py b/contrib/check-code.py
new file mode 100755
index 0000000..b8f714c
--- /dev/null
+++ b/contrib/check-code.py
@@ -0,0 +1,450 @@
+#!/usr/bin/env python
+#
+# check-code - a style and portability checker for Mercurial
+#
+# Copyright 2010 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import re, glob, os, sys
+import keyword
+import optparse
+
+def repquote(m):
+ t = re.sub(r"\w", "x", m.group('text'))
+ t = re.sub(r"[^\s\nx]", "o", t)
+ return m.group('quote') + t + m.group('quote')
+
+def reppython(m):
+ comment = m.group('comment')
+ if comment:
+ return "#" * len(comment)
+ return repquote(m)
+
+def repcomment(m):
+ return m.group(1) + "#" * len(m.group(2))
+
+def repccomment(m):
+ t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
+ return m.group(1) + t + "*/"
+
+def repcallspaces(m):
+ t = re.sub(r"\n\s+", "\n", m.group(2))
+ return m.group(1) + t
+
+def repinclude(m):
+ return m.group(1) + "<foo>"
+
+def rephere(m):
+ t = re.sub(r"\S", "x", m.group(2))
+ return m.group(1) + t
+
+
+testpats = [
+ [
+ (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
+ (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
+ (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
+ (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
+ (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
+ (r'echo -n', "don't use 'echo -n', use printf"),
+ (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
+ (r'head -c', "don't use 'head -c', use 'dd'"),
+ (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
+ (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
+ (r'printf.*\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
+ (r'printf.*\\x', "don't use printf \\x, use Python"),
+ (r'\$\(.*\)', "don't use $(expr), use `expr`"),
+ (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
+ (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
+ "use egrep for extended grep syntax"),
+ (r'/bin/', "don't use explicit paths for tools"),
+ (r'[^\n]\Z', "no trailing newline"),
+ (r'export.*=', "don't export and assign at once"),
+ (r'^source\b', "don't use 'source', use '.'"),
+ (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
+ (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
+ (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
+ (r'^stop\(\)', "don't use 'stop' as a shell function name"),
+ (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
+ (r'^alias\b.*=', "don't use alias, use a function"),
+ (r'if\s*!', "don't use '!' to negate exit status"),
+ (r'/dev/u?random', "don't use entropy, use /dev/zero"),
+ (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
+ (r'^( *)\t', "don't use tabs to indent"),
+ ],
+ # warnings
+ [
+ (r'^function', "don't use 'function', use old style"),
+ (r'^diff.*-\w*N', "don't use 'diff -N'"),
+ (r'\$PWD', "don't use $PWD, use `pwd`"),
+ (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
+ ]
+]
+
+testfilters = [
+ (r"( *)(#([^\n]*\S)?)", repcomment),
+ (r"<<(\S+)((.|\n)*?\n\1)", rephere),
+]
+
+uprefix = r"^ \$ "
+utestpats = [
+ [
+ (r'^(\S| $ ).*(\S[ \t]+|^[ \t]+)\n', "trailing whitespace on non-output"),
+ (uprefix + r'.*\|\s*sed[^|>\n]*\n',
+ "use regex test output patterns instead of sed"),
+ (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
+ (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
+ (uprefix + r'.*\|\| echo.*(fail|error)',
+ "explicit exit code checks unnecessary"),
+ (uprefix + r'set -e', "don't use set -e"),
+ (uprefix + r'\s', "don't indent commands, use > for continued lines"),
+ (r'^ saved backup bundle to \$TESTTMP.*\.hg$',
+ "use (glob) to match Windows paths too"),
+ ],
+ # warnings
+ []
+]
+
+for i in [0, 1]:
+ for p, m in testpats[i]:
+ if p.startswith(r'^'):
+ p = r"^ [$>] (%s)" % p[1:]
+ else:
+ p = r"^ [$>] .*(%s)" % p
+ utestpats[i].append((p, m))
+
+utestfilters = [
+ (r"( *)(#([^\n]*\S)?)", repcomment),
+]
+
+pypats = [
+ [
+ (r'^\s*def\s*\w+\s*\(.*,\s*\(',
+ "tuple parameter unpacking not available in Python 3+"),
+ (r'lambda\s*\(.*,.*\)',
+ "tuple parameter unpacking not available in Python 3+"),
+ (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
+ (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
+ (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
+ (r'^\s*\t', "don't use tabs"),
+ (r'\S;\s*\n', "semicolon"),
+ (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
+ (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
+ (r'\w,\w', "missing whitespace after ,"),
+ (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
+ (r'^\s+\w+=\w+[^,)\n]$', "missing whitespace in assignment"),
+ (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
+ r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Py2.4'),
+ (r'.{81}', "line too long"),
+ (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
+ (r'[^\n]\Z', "no trailing newline"),
+ (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
+# (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
+# "don't use underbars in identifiers"),
+ (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
+ "don't use camelcase in identifiers"),
+ (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
+ "linebreak after :"),
+ (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
+ (r'class\s[^( \n]+\(\):',
+ "class foo() not available in Python 2.4, use class foo(object)"),
+ (r'\b(%s)\(' % '|'.join(keyword.kwlist),
+ "Python keyword is not a function"),
+ (r',]', "unneeded trailing ',' in list"),
+# (r'class\s[A-Z][^\(]*\((?!Exception)',
+# "don't capitalize non-exception classes"),
+# (r'in range\(', "use xrange"),
+# (r'^\s*print\s+', "avoid using print in core and extensions"),
+ (r'[\x80-\xff]', "non-ASCII character literal"),
+ (r'("\')\.format\(', "str.format() not available in Python 2.4"),
+ (r'^\s*with\s+', "with not available in Python 2.4"),
+ (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
+ (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
+ (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
+ (r'(?<!def)\s+(any|all|format)\(',
+ "any/all/format not available in Python 2.4"),
+ (r'(?<!def)\s+(callable)\(',
+ "callable not available in Python 3, use getattr(f, '__call__', None)"),
+ (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
+ (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
+ "gratuitous whitespace after Python keyword"),
+ (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
+# (r'\s\s=', "gratuitous whitespace before ="),
+ (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
+ "missing whitespace around operator"),
+ (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
+ "missing whitespace around operator"),
+ (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
+ "missing whitespace around operator"),
+ (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
+ "wrong whitespace around ="),
+ (r'raise Exception', "don't raise generic exceptions"),
+ (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
+ (r' [=!]=\s+(True|False|None)',
+ "comparison with singleton, use 'is' or 'is not' instead"),
+ (r'^\s*(while|if) [01]:',
+ "use True/False for constant Boolean expression"),
+ (r'(?:(?<!def)\s+|\()hasattr',
+ 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
+ (r'opener\([^)]*\).read\(',
+ "use opener.read() instead"),
+ (r'BaseException', 'not in Py2.4, use Exception'),
+ (r'os\.path\.relpath', 'os.path.relpath is not in Py2.5'),
+ (r'opener\([^)]*\).write\(',
+ "use opener.write() instead"),
+ (r'[\s\(](open|file)\([^)]*\)\.read\(',
+ "use util.readfile() instead"),
+ (r'[\s\(](open|file)\([^)]*\)\.write\(',
+ "use util.readfile() instead"),
+ (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
+ "always assign an opened file to a variable, and close it afterwards"),
+ (r'[\s\(](open|file)\([^)]*\)\.',
+ "always assign an opened file to a variable, and close it afterwards"),
+ (r'(?i)descendent', "the proper spelling is descendAnt"),
+ (r'\.debug\(\_', "don't mark debug messages for translation"),
+ (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
+ (r'^\s*except\s*:', "warning: naked except clause", r'#.*re-raises'),
+ (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
+ ],
+ # warnings
+ [
+ (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
+ "warning: unwrapped ui message"),
+ ]
+]
+
+pyfilters = [
+ (r"""(?msx)(?P<comment>\#.*?$)|
+ ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
+ (?P<text>(([^\\]|\\.)*?))
+ (?P=quote))""", reppython),
+]
+
+cpats = [
+ [
+ (r'//', "don't use //-style comments"),
+ (r'^ ', "don't use spaces to indent"),
+ (r'\S\t', "don't use tabs except for indent"),
+ (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
+ (r'.{81}', "line too long"),
+ (r'(while|if|do|for)\(', "use space after while/if/do/for"),
+ (r'return\(', "return is not a function"),
+ (r' ;', "no space before ;"),
+ (r'\w+\* \w+', "use int *foo, not int* foo"),
+ (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
+ (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
+ (r'\w,\w', "missing whitespace after ,"),
+ (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
+ (r'^#\s+\w', "use #foo, not # foo"),
+ (r'[^\n]\Z', "no trailing newline"),
+ (r'^\s*#import\b', "use only #include in standard C code"),
+ ],
+ # warnings
+ []
+]
+
+cfilters = [
+ (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
+ (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
+ (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
+ (r'(\()([^)]+\))', repcallspaces),
+]
+
+inutilpats = [
+ [
+ (r'\bui\.', "don't use ui in util"),
+ ],
+ # warnings
+ []
+]
+
+inrevlogpats = [
+ [
+ (r'\brepo\.', "don't use repo in revlog"),
+ ],
+ # warnings
+ []
+]
+
+checks = [
+ ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
+ ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
+ ('c', r'.*\.c$', cfilters, cpats),
+ ('unified test', r'.*\.t$', utestfilters, utestpats),
+ ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
+ inrevlogpats),
+ ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
+ inutilpats),
+]
+
+class norepeatlogger(object):
+ def __init__(self):
+ self._lastseen = None
+
+ def log(self, fname, lineno, line, msg, blame):
+ """print error related a to given line of a given file.
+
+ The faulty line will also be printed but only once in the case
+ of multiple errors.
+
+ :fname: filename
+ :lineno: line number
+ :line: actual content of the line
+ :msg: error message
+ """
+ msgid = fname, lineno, line
+ if msgid != self._lastseen:
+ if blame:
+ print "%s:%d (%s):" % (fname, lineno, blame)
+ else:
+ print "%s:%d:" % (fname, lineno)
+ print " > %s" % line
+ self._lastseen = msgid
+ print " " + msg
+
+_defaultlogger = norepeatlogger()
+
+def getblame(f):
+ lines = []
+ for l in os.popen('hg annotate -un %s' % f):
+ start, line = l.split(':', 1)
+ user, rev = start.split()
+ lines.append((line[1:-1], user, rev))
+ return lines
+
+def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
+ blame=False, debug=False, lineno=True):
+ """checks style and portability of a given file
+
+ :f: filepath
+ :logfunc: function used to report error
+ logfunc(filename, linenumber, linecontent, errormessage)
+ :maxerr: number of error to display before arborting.
+ Set to false (default) to report all errors
+
+ return True if no error is found, False otherwise.
+ """
+ blamecache = None
+ result = True
+ for name, match, filters, pats in checks:
+ if debug:
+ print name, f
+ fc = 0
+ if not re.match(match, f):
+ if debug:
+ print "Skipping %s for %s it doesn't match %s" % (
+ name, match, f)
+ continue
+ fp = open(f)
+ pre = post = fp.read()
+ fp.close()
+ if "no-" + "check-code" in pre:
+ if debug:
+ print "Skipping %s for %s it has no- and check-code" % (
+ name, f)
+ break
+ for p, r in filters:
+ post = re.sub(p, r, post)
+ if warnings:
+ pats = pats[0] + pats[1]
+ else:
+ pats = pats[0]
+ # print post # uncomment to show filtered version
+
+ if debug:
+ print "Checking %s for %s" % (name, f)
+
+ prelines = None
+ errors = []
+ for pat in pats:
+ if len(pat) == 3:
+ p, msg, ignore = pat
+ else:
+ p, msg = pat
+ ignore = None
+
+ # fix-up regexes for multiline searches
+ po = p
+ # \s doesn't match \n
+ p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
+ # [^...] doesn't match newline
+ p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
+
+ #print po, '=>', p
+
+ pos = 0
+ n = 0
+ for m in re.finditer(p, post, re.MULTILINE):
+ if prelines is None:
+ prelines = pre.splitlines()
+ postlines = post.splitlines(True)
+
+ start = m.start()
+ while n < len(postlines):
+ step = len(postlines[n])
+ if pos + step > start:
+ break
+ pos += step
+ n += 1
+ l = prelines[n]
+
+ if "check-code" + "-ignore" in l:
+ if debug:
+ print "Skipping %s for %s:%s (check-code -ignore)" % (
+ name, f, n)
+ continue
+ elif ignore and re.search(ignore, l, re.MULTILINE):
+ continue
+ bd = ""
+ if blame:
+ bd = 'working directory'
+ if not blamecache:
+ blamecache = getblame(f)
+ if n < len(blamecache):
+ bl, bu, br = blamecache[n]
+ if bl == l:
+ bd = '%s@%s' % (bu, br)
+ errors.append((f, lineno and n + 1, l, msg, bd))
+ result = False
+
+ errors.sort()
+ for e in errors:
+ logfunc(*e)
+ fc += 1
+ if maxerr and fc >= maxerr:
+ print " (too many errors, giving up)"
+ break
+
+ return result
+
+if __name__ == "__main__":
+ parser = optparse.OptionParser("%prog [options] [files]")
+ parser.add_option("-w", "--warnings", action="store_true",
+ help="include warning-level checks")
+ parser.add_option("-p", "--per-file", type="int",
+ help="max warnings per file")
+ parser.add_option("-b", "--blame", action="store_true",
+ help="use annotate to generate blame info")
+ parser.add_option("", "--debug", action="store_true",
+ help="show debug information")
+ parser.add_option("", "--nolineno", action="store_false",
+ dest='lineno', help="don't show line numbers")
+
+ parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
+ lineno=True)
+ (options, args) = parser.parse_args()
+
+ if len(args) == 0:
+ check = glob.glob("*")
+ else:
+ check = args
+
+ ret = 0
+ for f in check:
+ if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
+ blame=options.blame, debug=options.debug,
+ lineno=options.lineno):
+ ret = 1
+ sys.exit(ret)
diff --git a/contrib/convert-repo b/contrib/convert-repo
new file mode 100755
index 0000000..4113f40
--- /dev/null
+++ b/contrib/convert-repo
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+#
+# Wrapper script around the convert.py hgext extension
+# for foreign SCM conversion to mercurial format.
+#
+
+import sys
+from mercurial import ui, fancyopts
+from hgext import convert
+
+# Options extracted from the cmdtable
+func, options, help = convert.cmdtable['convert']
+
+# An ui instance
+u = ui.ui()
+
+opts = {}
+args = []
+try:
+ args = list(fancyopts.fancyopts(sys.argv[1:], options, opts))
+ args += [None]*(3 - len(args))
+ src, dest, revmapfile = args
+except (fancyopts.getopt.GetoptError, ValueError), inst:
+ u.warn('Usage:\n%s\n' % help)
+ sys.exit(-1)
+
+convert.convert(u, src, dest, revmapfile, **opts)
diff --git a/contrib/debugcmdserver.py b/contrib/debugcmdserver.py
new file mode 100755
index 0000000..859ee9a
--- /dev/null
+++ b/contrib/debugcmdserver.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+#
+# Dumps output generated by Mercurial's command server in a formatted style to a
+# given file or stderr if '-' is specified. Output is also written in its raw
+# format to stdout.
+#
+# $ ./hg serve --cmds pipe | ./contrib/debugcmdserver.py -
+# o, 52 -> 'capabilities: getencoding runcommand\nencoding: UTF-8'
+
+import sys, struct
+
+if len(sys.argv) != 2:
+ print 'usage: debugcmdserver.py FILE'
+ sys.exit(1)
+
+outputfmt = '>cI'
+outputfmtsize = struct.calcsize(outputfmt)
+
+if sys.argv[1] == '-':
+ log = sys.stderr
+else:
+ log = open(sys.argv[1], 'a')
+
+def read(size):
+ data = sys.stdin.read(size)
+ if not data:
+ raise EOFError
+ sys.stdout.write(data)
+ sys.stdout.flush()
+ return data
+
+try:
+ while True:
+ header = read(outputfmtsize)
+ channel, length = struct.unpack(outputfmt, header)
+ log.write('%s, %-4d' % (channel, length))
+ if channel in 'IL':
+ log.write(' -> waiting for input\n')
+ else:
+ data = read(length)
+ log.write(' -> %r\n' % data)
+ log.flush()
+except EOFError:
+ pass
+finally:
+ if log != sys.stderr:
+ log.close()
diff --git a/contrib/debugshell.py b/contrib/debugshell.py
new file mode 100644
index 0000000..6ca4999
--- /dev/null
+++ b/contrib/debugshell.py
@@ -0,0 +1,21 @@
+# debugshell extension
+"""a python shell with repo, changelog & manifest objects"""
+
+import mercurial
+import code
+
+def debugshell(ui, repo, **opts):
+ objects = {
+ 'mercurial': mercurial,
+ 'repo': repo,
+ 'cl': repo.changelog,
+ 'mf': repo.manifest,
+ }
+ bannermsg = "loaded repo : %s\n" \
+ "using source: %s" % (repo.root,
+ mercurial.__path__[0])
+ code.interact(bannermsg, local=objects)
+
+cmdtable = {
+ "debugshell|dbsh": (debugshell, [])
+}
diff --git a/contrib/dumprevlog b/contrib/dumprevlog
new file mode 100755
index 0000000..8bb9c4e
--- /dev/null
+++ b/contrib/dumprevlog
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# Dump revlogs as raw data stream
+# $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump
+
+import sys
+from mercurial import revlog, node, util
+
+for fp in (sys.stdin, sys.stdout, sys.stderr):
+ util.setbinary(fp)
+
+for f in sys.argv[1:]:
+ binopen = lambda fn: open(fn, 'rb')
+ r = revlog.revlog(binopen, f)
+ print "file:", f
+ for i in r:
+ n = r.node(i)
+ p = r.parents(n)
+ d = r.revision(n)
+ print "node:", node.hex(n)
+ print "linkrev:", r.linkrev(i)
+ print "parents:", node.hex(p[0]), node.hex(p[1])
+ print "length:", len(d)
+ print "-start-"
+ print d
+ print "-end-"
diff --git a/contrib/hg-ssh b/contrib/hg-ssh
new file mode 100755
index 0000000..5021958
--- /dev/null
+++ b/contrib/hg-ssh
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
+#
+# Author(s):
+# Thomas Arendsen Hein <thomas@intevation.de>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""
+hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
+
+To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
+command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ...
+(probably together with these other useful options:
+ no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
+
+This allows pull/push over ssh from/to the repositories given as arguments.
+
+If all your repositories are subdirectories of a common directory, you can
+allow shorter paths with:
+command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
+
+You can use pattern matching of your normal shell, e.g.:
+command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
+
+You can also add a --read-only flag to allow read-only access to a key, e.g.:
+command="hg-ssh --read-only repos/*"
+"""
+
+# enable importing on demand to reduce startup time
+from mercurial import demandimport; demandimport.enable()
+
+from mercurial import dispatch
+
+import sys, os, shlex
+
+def main():
+ cwd = os.getcwd()
+ readonly = False
+ args = sys.argv[1:]
+ while len(args):
+ if args[0] == '--read-only':
+ readonly = True
+ args.pop(0)
+ else:
+ break
+ allowed_paths = [os.path.normpath(os.path.join(cwd,
+ os.path.expanduser(path)))
+ for path in args]
+ orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?')
+ try:
+ cmdargv = shlex.split(orig_cmd)
+ except ValueError, e:
+ sys.stderr.write('Illegal command "%s": %s\n' % (orig_cmd, e))
+ sys.exit(255)
+
+ if cmdargv[:2] == ['hg', '-R'] and cmdargv[3:] == ['serve', '--stdio']:
+ path = cmdargv[2]
+ repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
+ if repo in allowed_paths:
+ cmd = ['-R', repo, 'serve', '--stdio']
+ if readonly:
+ cmd += [
+ '--config',
+ 'hooks.prechangegroup.hg-ssh=python:__main__.rejectpush',
+ '--config',
+ 'hooks.prepushkey.hg-ssh=python:__main__.rejectpush'
+ ]
+ dispatch.dispatch(dispatch.request(cmd))
+ else:
+ sys.stderr.write('Illegal repository "%s"\n' % repo)
+ sys.exit(255)
+ else:
+ sys.stderr.write('Illegal command "%s"\n' % orig_cmd)
+ sys.exit(255)
+
+def rejectpush(ui, **kwargs):
+ ui.warn("Permission denied\n")
+ # mercurial hooks use unix process conventions for hook return values
+ # so a truthy return means failure
+ return True
+
+if __name__ == '__main__':
+ main()
diff --git a/contrib/hgfixes/__init__.py b/contrib/hgfixes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/hgfixes/__init__.py
diff --git a/contrib/hgfixes/fix_bytes.py b/contrib/hgfixes/fix_bytes.py
new file mode 100644
index 0000000..0890c21
--- /dev/null
+++ b/contrib/hgfixes/fix_bytes.py
@@ -0,0 +1,97 @@
+"""Fixer that changes plain strings to bytes strings."""
+
+import re
+
+from lib2to3 import fixer_base
+from lib2to3.pgen2 import token
+from lib2to3.fixer_util import Name
+from lib2to3.pygram import python_symbols as syms
+
+_re = re.compile(r'[rR]?[\'\"]')
+
+# XXX: Implementing a blacklist in 2to3 turned out to be more troublesome than
+# blacklisting some modules inside the fixers. So, this is what I came with.
+
+blacklist = ['mercurial/demandimport.py',
+ 'mercurial/py3kcompat.py', # valid python 3 already
+ 'mercurial/i18n.py',
+ ]
+
+def isdocstring(node):
+ def isclassorfunction(ancestor):
+ symbols = (syms.funcdef, syms.classdef)
+ # if the current node is a child of a function definition, a class
+ # definition or a file, then it is a docstring
+ if ancestor.type == syms.simple_stmt:
+ try:
+ while True:
+ if ancestor.type in symbols:
+ return True
+ ancestor = ancestor.parent
+ except AttributeError:
+ return False
+ return False
+
+ def ismodule(ancestor):
+ # Our child is a docstring if we are a simple statement, and our
+ # ancestor is file_input. In other words, our child is a lone string in
+ # the source file.
+ try:
+ if (ancestor.type == syms.simple_stmt and
+ ancestor.parent.type == syms.file_input):
+ return True
+ except AttributeError:
+ return False
+
+ def isdocassignment(ancestor):
+ # Assigning to __doc__, definitely a string
+ try:
+ while True:
+ if (ancestor.type == syms.expr_stmt and
+ Name('__doc__') in ancestor.children):
+ return True
+ ancestor = ancestor.parent
+ except AttributeError:
+ return False
+
+ if ismodule(node.parent) or \
+ isdocassignment(node.parent) or \
+ isclassorfunction(node.parent):
+ return True
+ return False
+
+def shouldtransform(node):
+ specialnames = ['__main__']
+
+ if node.value in specialnames:
+ return False
+
+ ggparent = node.parent.parent.parent
+ sggparent = str(ggparent)
+
+ if 'getattr' in sggparent or \
+ 'hasattr' in sggparent or \
+ 'setattr' in sggparent or \
+ 'encode' in sggparent or \
+ 'decode' in sggparent:
+ return False
+
+ return True
+
+class FixBytes(fixer_base.BaseFix):
+
+ PATTERN = 'STRING'
+
+ def transform(self, node, results):
+ if self.filename in blacklist:
+ return
+ if node.type == token.STRING:
+ if _re.match(node.value):
+ if isdocstring(node):
+ return
+ if not shouldtransform(node):
+ return
+ new = node.clone()
+ new.value = 'b' + new.value
+ return new
+
diff --git a/contrib/hgfixes/fix_bytesmod.py b/contrib/hgfixes/fix_bytesmod.py
new file mode 100644
index 0000000..a72037b
--- /dev/null
+++ b/contrib/hgfixes/fix_bytesmod.py
@@ -0,0 +1,63 @@
+"""Fixer that changes bytes % whatever to a function that actually formats
+it."""
+
+from lib2to3 import fixer_base
+from lib2to3.fixer_util import is_tuple, Call, Comma, Name, touch_import
+
+# XXX: Implementing a blacklist in 2to3 turned out to be more troublesome than
+# blacklisting some modules inside the fixers. So, this is what I came with.
+
+blacklist = ['mercurial/demandimport.py',
+ 'mercurial/py3kcompat.py',
+ 'mercurial/i18n.py',
+ ]
+
+def isnumberremainder(formatstr, data):
+ try:
+ if data.value.isdigit():
+ return True
+ except AttributeError:
+ return False
+
+class FixBytesmod(fixer_base.BaseFix):
+ # XXX: There's one case (I suppose) I can't handle: when a remainder
+ # operation like foo % bar is performed, I can't really know what the
+ # contents of foo and bar are. I believe the best approach is to "correct"
+ # the to-be-converted code and let bytesformatter handle that case in
+ # runtime.
+ PATTERN = '''
+ term< formatstr=STRING '%' data=STRING > |
+ term< formatstr=STRING '%' data=atom > |
+ term< formatstr=NAME '%' data=any > |
+ term< formatstr=any '%' data=any >
+ '''
+
+ def transform(self, node, results):
+ if self.filename in blacklist:
+ return
+ elif self.filename == 'mercurial/util.py':
+ touch_import('.', 'py3kcompat', node=node)
+
+ formatstr = results['formatstr'].clone()
+ data = results['data'].clone()
+ formatstr.prefix = '' # remove spaces from start
+
+ if isnumberremainder(formatstr, data):
+ return
+
+ # We have two possibilities:
+ # 1- An identifier or name is passed, it is going to be a leaf, thus, we
+ # just need to copy its value as an argument to the formatter;
+ # 2- A tuple is explicitly passed. In this case, we're gonna explode it
+ # to pass to the formatter
+ # TODO: Check for normal strings. They don't need to be translated
+
+ if is_tuple(data):
+ args = [formatstr, Comma().clone()] + \
+ [c.clone() for c in data.children[:]]
+ else:
+ args = [formatstr, Comma().clone(), data]
+
+ call = Call(Name('bytesformatter', prefix = ' '), args)
+ return call
+
diff --git a/contrib/hgfixes/fix_leftover_imports.py b/contrib/hgfixes/fix_leftover_imports.py
new file mode 100644
index 0000000..d6b7de0
--- /dev/null
+++ b/contrib/hgfixes/fix_leftover_imports.py
@@ -0,0 +1,108 @@
+"Fixer that translates some APIs ignored by the default 2to3 fixers."
+
+# FIXME: This fixer has some ugly hacks. Its main design is based on that of
+# fix_imports, from lib2to3. Unfortunately, the fix_imports framework only
+# changes module names "without dots", meaning it won't work for some changes
+# in the email module/package. Thus this fixer was born. I believe that with a
+# bit more thinking, a more generic fixer can be implemented, but I'll leave
+# that as future work.
+
+from lib2to3.fixer_util import Name
+from lib2to3.fixes import fix_imports
+
+# This maps the old names to the new names. Note that a drawback of the current
+# design is that the dictionary keys MUST have EXACTLY one dot (.) in them,
+# otherwise things will break. (If you don't need a module hierarchy, you're
+# better of just inherit from fix_imports and overriding the MAPPING dict.)
+
+MAPPING = {'email.Utils': 'email.utils',
+ 'email.Errors': 'email.errors',
+ 'email.Header': 'email.header',
+ 'email.Parser': 'email.parser',
+ 'email.Encoders': 'email.encoders',
+ 'email.MIMEText': 'email.mime.text',
+ 'email.MIMEBase': 'email.mime.base',
+ 'email.Generator': 'email.generator',
+ 'email.MIMEMultipart': 'email.mime.multipart',
+}
+
+def alternates(members):
+ return "(" + "|".join(map(repr, members)) + ")"
+
+def build_pattern(mapping=MAPPING):
+ packages = {}
+ for key in mapping:
+ # What we are doing here is the following: with dotted names, we'll
+ # have something like package_name <trailer '.' module>. Then, we are
+ # making a dictionary to copy this structure. For example, if
+ # mapping={'A.B': 'a.b', 'A.C': 'a.c'}, it will generate the dictionary
+ # {'A': ['b', 'c']} to, then, generate something like "A <trailer '.'
+ # ('b' | 'c')".
+ name = key.split('.')
+ prefix = name[0]
+ if prefix in packages:
+ packages[prefix].append(name[1:][0])
+ else:
+ packages[prefix] = name[1:]
+
+ mod_list = ' | '.join(["'%s' '.' ('%s')" %
+ (key, "' | '".join(packages[key])) for key in packages])
+ mod_list = '(' + mod_list + ' )'
+ bare_names = alternates(mapping.keys())
+
+ yield """name_import=import_name< 'import' module_name=dotted_name< %s > >
+ """ % mod_list
+
+ yield """name_import=import_name< 'import'
+ multiple_imports=dotted_as_names< any*
+ module_name=dotted_name< %s >
+ any* >
+ >""" % mod_list
+
+ packs = ' | '.join(["'%s' trailer<'.' ('%s')>" % (key,
+ "' | '".join(packages[key])) for key in packages])
+
+ yield "power< package=(%s) trailer<'.' any > any* >" % packs
+
+class FixLeftoverImports(fix_imports.FixImports):
+ # We want to run this fixer after fix_import has run (this shouldn't matter
+ # for hg, though, as setup3k prefers to run the default fixers first)
+ mapping = MAPPING
+
+ def build_pattern(self):
+ return "|".join(build_pattern(self.mapping))
+
+ def transform(self, node, results):
+ # Mostly copied from fix_imports.py
+ import_mod = results.get("module_name")
+ if import_mod:
+ try:
+ mod_name = import_mod.value
+ except AttributeError:
+ # XXX: A hack to remove whitespace prefixes and suffixes
+ mod_name = str(import_mod).strip()
+ new_name = self.mapping[mod_name]
+ import_mod.replace(Name(new_name, prefix=import_mod.prefix))
+ if "name_import" in results:
+ # If it's not a "from x import x, y" or "import x as y" import,
+ # marked its usage to be replaced.
+ self.replace[mod_name] = new_name
+ if "multiple_imports" in results:
+ # This is a nasty hack to fix multiple imports on a line (e.g.,
+ # "import StringIO, urlparse"). The problem is that I can't
+ # figure out an easy way to make a pattern recognize the keys of
+ # MAPPING randomly sprinkled in an import statement.
+ results = self.match(node)
+ if results:
+ self.transform(node, results)
+ else:
+ # Replace usage of the module.
+ # Now this is, mostly, a hack
+ bare_name = results["package"][0]
+ bare_name_text = ''.join(map(str, results['package'])).strip()
+ new_name = self.replace.get(bare_name_text)
+ prefix = results['package'][0].prefix
+ if new_name:
+ bare_name.replace(Name(new_name, prefix=prefix))
+ results["package"][1].replace(Name(''))
+
diff --git a/contrib/hgk b/contrib/hgk
new file mode 100755
index 0000000..337b241
--- /dev/null
+++ b/contrib/hgk
@@ -0,0 +1,4061 @@
+#!/usr/bin/env wish
+
+# Copyright (C) 2005 Paul Mackerras. All rights reserved.
+# This program is free software; it may be used, copied, modified
+# and distributed under the terms of the GNU General Public Licence,
+# either version 2, or (at your option) any later version.
+#
+# See hgk.py for extension usage and configuration.
+
+
+# Modified version of Tip 171:
+# http://www.tcl.tk/cgi-bin/tct/tip/171.html
+#
+# The in_mousewheel global was added to fix strange reentrancy issues.
+# The whole snipped is activated only under windows, mouse wheel
+# bindings working already under MacOSX and Linux.
+
+if {[tk windowingsystem] eq "win32"} {
+
+set mw_classes [list Text Listbox Table TreeCtrl]
+ foreach class $mw_classes { bind $class <MouseWheel> {} }
+
+set in_mousewheel 0
+
+proc ::tk::MouseWheel {wFired X Y D {shifted 0}} {
+ global in_mousewheel
+ if { $in_mousewheel != 0 } { return }
+ # Set event to check based on call
+ set evt "<[expr {$shifted?{Shift-}:{}}]MouseWheel>"
+ # do not double-fire in case the class already has a binding
+ if {[bind [winfo class $wFired] $evt] ne ""} { return }
+ # obtain the window the mouse is over
+ set w [winfo containing $X $Y]
+ # if we are outside the app, try and scroll the focus widget
+ if {![winfo exists $w]} { catch {set w [focus]} }
+ if {[winfo exists $w]} {
+
+ if {[bind $w $evt] ne ""} {
+ # Awkward ... this widget has a MouseWheel binding, but to
+ # trigger successfully in it, we must give it focus.
+ catch {focus} old
+ if {$w ne $old} { focus $w }
+ set in_mousewheel 1
+ event generate $w $evt -rootx $X -rooty $Y -delta $D
+ set in_mousewheel 0
+ if {$w ne $old} { focus $old }
+ return
+ }
+
+ # aqua and x11/win32 have different delta handling
+ if {[tk windowingsystem] ne "aqua"} {
+ set delta [expr {- ($D / 30)}]
+ } else {
+ set delta [expr {- ($D)}]
+ }
+ # scrollbars have different call conventions
+ if {[string match "*Scrollbar" [winfo class $w]]} {
+ catch {tk::ScrollByUnits $w \
+ [string index [$w cget -orient] 0] $delta}
+ } else {
+ set cmd [list $w [expr {$shifted ? "xview" : "yview"}] \
+ scroll $delta units]
+ # Walking up to find the proper widget (handles cases like
+ # embedded widgets in a canvas)
+ while {[catch $cmd] && [winfo toplevel $w] ne $w} {
+ set w [winfo parent $w]
+ }
+ }
+ }
+}
+
+bind all <MouseWheel> [list ::tk::MouseWheel %W %X %Y %D 0]
+
+# end of win32 section
+}
+
+
+# Unify right mouse button handling.
+# See "mouse buttons on macintosh" thread on comp.lang.tcl
+if {[tk windowingsystem] eq "aqua"} {
+ event add <<B3>> <Control-ButtonPress-1>
+ event add <<B3>> <Button-2>
+} else {
+ event add <<B3>> <Button-3>
+}
+
+proc gitdir {} {
+ global env
+ if {[info exists env(GIT_DIR)]} {
+ return $env(GIT_DIR)
+ } else {
+ return ".hg"
+ }
+}
+
+proc getcommits {rargs} {
+ global commits commfd phase canv mainfont env
+ global startmsecs nextupdate ncmupdate
+ global ctext maincursor textcursor leftover
+
+ # check that we can find a .git directory somewhere...
+ set gitdir [gitdir]
+ if {![file isdirectory $gitdir]} {
+ error_popup "Cannot find the git directory \"$gitdir\"."
+ exit 1
+ }
+ set commits {}
+ set phase getcommits
+ set startmsecs [clock clicks -milliseconds]
+ set nextupdate [expr $startmsecs + 100]
+ set ncmupdate 1
+ set limit 0
+ set revargs {}
+ for {set i 0} {$i < [llength $rargs]} {incr i} {
+ set opt [lindex $rargs $i]
+ if {$opt == "--limit"} {
+ incr i
+ set limit [lindex $rargs $i]
+ } else {
+ lappend revargs $opt
+ }
+ }
+ if [catch {
+ set parse_args [concat --default HEAD $revargs]
+ set parse_temp [eval exec {$env(HG)} --config ui.report_untrusted=false debug-rev-parse $parse_args]
+ regsub -all "\r\n" $parse_temp "\n" parse_temp
+ set parsed_args [split $parse_temp "\n"]
+ } err] {
+ # if git-rev-parse failed for some reason...
+ if {$rargs == {}} {
+ set revargs HEAD
+ }
+ set parsed_args $revargs
+ }
+ if {$limit > 0} {
+ set parsed_args [concat -n $limit $parsed_args]
+ }
+ if [catch {
+ set commfd [open "|{$env(HG)} --config ui.report_untrusted=false debug-rev-list --header --topo-order --parents $parsed_args" r]
+ } err] {
+ puts stderr "Error executing hg debug-rev-list: $err"
+ exit 1
+ }
+ set leftover {}
+ fconfigure $commfd -blocking 0 -translation lf
+ fileevent $commfd readable [list getcommitlines $commfd]
+ $canv delete all
+ $canv create text 3 3 -anchor nw -text "Reading commits..." \
+ -font $mainfont -tags textitems
+ . config -cursor watch
+ settextcursor watch
+}
+
+proc getcommitlines {commfd} {
+ global commits parents cdate children
+ global commitlisted phase commitinfo nextupdate
+ global stopped redisplaying leftover
+
+ set stuff [read $commfd]
+ if {$stuff == {}} {
+ if {![eof $commfd]} return
+ # set it blocking so we wait for the process to terminate
+ fconfigure $commfd -blocking 1
+ if {![catch {close $commfd} err]} {
+ after idle finishcommits
+ return
+ }
+ if {[string range $err 0 4] == "usage"} {
+ set err \
+{Gitk: error reading commits: bad arguments to git-rev-list.
+(Note: arguments to gitk are passed to git-rev-list
+to allow selection of commits to be displayed.)}
+ } else {
+ set err "Error reading commits: $err"
+ }
+ error_popup $err
+ exit 1
+ }
+ set start 0
+ while 1 {
+ set i [string first "\0" $stuff $start]
+ if {$i < 0} {
+ append leftover [string range $stuff $start end]
+ return
+ }
+ set cmit [string range $stuff $start [expr {$i - 1}]]
+ if {$start == 0} {
+ set cmit "$leftover$cmit"
+ set leftover {}
+ }
+ set start [expr {$i + 1}]
+ regsub -all "\r\n" $cmit "\n" cmit
+ set j [string first "\n" $cmit]
+ set ok 0
+ if {$j >= 0} {
+ set ids [string range $cmit 0 [expr {$j - 1}]]
+ set ok 1
+ foreach id $ids {
+ if {![regexp {^[0-9a-f]{12}$} $id]} {
+ set ok 0
+ break
+ }
+ }
+ }
+ if {!$ok} {
+ set shortcmit $cmit
+ if {[string length $shortcmit] > 80} {
+ set shortcmit "[string range $shortcmit 0 80]..."
+ }
+ error_popup "Can't parse hg debug-rev-list output: {$shortcmit}"
+ exit 1
+ }
+ set id [lindex $ids 0]
+ set olds [lrange $ids 1 end]
+ set cmit [string range $cmit [expr {$j + 1}] end]
+ lappend commits $id
+ set commitlisted($id) 1
+ parsecommit $id $cmit 1 [lrange $ids 1 end]
+ drawcommit $id
+ if {[clock clicks -milliseconds] >= $nextupdate} {
+ doupdate 1
+ }
+ while {$redisplaying} {
+ set redisplaying 0
+ if {$stopped == 1} {
+ set stopped 0
+ set phase "getcommits"
+ foreach id $commits {
+ drawcommit $id
+ if {$stopped} break
+ if {[clock clicks -milliseconds] >= $nextupdate} {
+ doupdate 1
+ }
+ }
+ }
+ }
+ }
+}
+
+proc doupdate {reading} {
+ global commfd nextupdate numcommits ncmupdate
+
+ if {$reading} {
+ fileevent $commfd readable {}
+ }
+ update
+ set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+ if {$numcommits < 100} {
+ set ncmupdate [expr {$numcommits + 1}]
+ } elseif {$numcommits < 10000} {
+ set ncmupdate [expr {$numcommits + 10}]
+ } else {
+ set ncmupdate [expr {$numcommits + 100}]
+ }
+ if {$reading} {
+ fileevent $commfd readable [list getcommitlines $commfd]
+ }
+}
+
+proc readcommit {id} {
+ global env
+ if [catch {set contents [exec $env(HG) --config ui.report_untrusted=false debug-cat-file commit $id]}] return
+ parsecommit $id $contents 0 {}
+}
+
+proc parsecommit {id contents listed olds} {
+ global commitinfo children nchildren parents nparents cdate ncleft
+ global firstparents
+
+ set inhdr 1
+ set comment {}
+ set headline {}
+ set auname {}
+ set audate {}
+ set comname {}
+ set comdate {}
+ set rev {}
+ set branch {}
+ set bookmark {}
+ if {![info exists nchildren($id)]} {
+ set children($id) {}
+ set nchildren($id) 0
+ set ncleft($id) 0
+ }
+ set parents($id) $olds
+ set nparents($id) [llength $olds]
+ foreach p $olds {
+ if {![info exists nchildren($p)]} {
+ set children($p) [list $id]
+ set nchildren($p) 1
+ set ncleft($p) 1
+ } elseif {[lsearch -exact $children($p) $id] < 0} {
+ lappend children($p) $id
+ incr nchildren($p)
+ incr ncleft($p)
+ }
+ }
+ regsub -all "\r\n" $contents "\n" contents
+ foreach line [split $contents "\n"] {
+ if {$inhdr} {
+ set line [split $line]
+ if {$line == {}} {
+ set inhdr 0
+ } else {
+ set tag [lindex $line 0]
+ if {$tag == "author"} {
+ set x [expr {[llength $line] - 2}]
+ set audate [lindex $line $x]
+ set auname [join [lrange $line 1 [expr {$x - 1}]]]
+ } elseif {$tag == "committer"} {
+ set x [expr {[llength $line] - 2}]
+ set comdate [lindex $line $x]
+ set comname [join [lrange $line 1 [expr {$x - 1}]]]
+ } elseif {$tag == "revision"} {
+ set rev [lindex $line 1]
+ } elseif {$tag == "branch"} {
+ set branch [join [lrange $line 1 end]]
+ } elseif {$tag == "bookmark"} {
+ set bookmark [join [lrange $line 1 end]]
+ }
+ }
+ } else {
+ if {$comment == {}} {
+ set headline [string trim $line]
+ } else {
+ append comment "\n"
+ }
+ if {!$listed} {
+ # git-rev-list indents the comment by 4 spaces;
+ # if we got this via git-cat-file, add the indentation
+ append comment " "
+ }
+ append comment $line
+ }
+ }
+ if {$audate != {}} {
+ set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
+ }
+ if {$comdate != {}} {
+ set cdate($id) $comdate
+ set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
+ }
+ set commitinfo($id) [list $headline $auname $audate \
+ $comname $comdate $comment $rev $branch $bookmark]
+
+ if {[info exists firstparents]} {
+ set i [lsearch $firstparents $id]
+ if {$i != -1} {
+ # remove the parent from firstparents, possible building
+ # an empty list
+ set firstparents [concat \
+ [lrange $firstparents 0 [expr $i - 1]] \
+ [lrange $firstparents [expr $i + 1] end]]
+ if {$firstparents eq {}} {
+ # we have found all parents of the first changeset
+ # which means that we can safely select the first line
+ after idle {
+ selectline 0 0
+ }
+ }
+ }
+ } else {
+ # this is the first changeset, save the parents
+ set firstparents $olds
+ if {$firstparents eq {}} {
+ # a repository with a single changeset
+ after idle {
+ selectline 0 0
+ }
+ }
+ }
+}
+
+proc readrefs {} {
+ global bookmarkcurrent bookmarkids tagids idtags idbookmarks headids idheads tagcontents env curid
+
+ set status [catch {exec $env(HG) --config ui.report_untrusted=false id} curid]
+ if { $status != 0 } {
+ puts $::errorInfo
+ if { ![string equal $::errorCode NONE] } {
+ exit 2
+ }
+ }
+ regexp -- {[[:xdigit:]]+} $curid curid
+
+ set status [catch {exec $env(HG) --config ui.report_untrusted=false tags} tags]
+ if { $status != 0 } {
+ puts $::errorInfo
+ if { ![string equal $::errorCode NONE] } {
+ exit 2
+ }
+ }
+ regsub -all "\r\n" $tags "\n" tags
+
+ set lines [split $tags "\n"]
+ foreach f $lines {
+ regexp {(\S+)$} $f full
+ regsub {\s+(\S+)$} $f "" direct
+ set sha [split $full ':']
+ set tag [lindex $sha 1]
+ lappend tagids($direct) $tag
+ lappend idtags($tag) $direct
+ }
+
+ set status [catch {exec $env(HG) --config ui.report_untrusted=false heads} heads]
+ if { $status != 0 } {
+ puts $::errorInfo
+ if { ![string equal $::errorCode NONE] } {
+ exit 2
+ }
+ }
+ regsub -all "\r\n" $heads "\n" heads
+
+ set lines [split $heads "\n"]
+ foreach f $lines {
+ set match ""
+ regexp {changeset:\s+(\S+):(\S+)$} $f match id sha
+ if {$match != ""} {
+ lappend idheads($sha) $id
+ }
+ }
+
+ set status [catch {exec $env(HG) --config ui.report_untrusted=false bookmarks} bookmarks]
+ if { $status != 0 } {
+ puts $::errorInfo
+ if { ![string equal $::errorCode NONE] } {
+ exit 2
+ }
+ }
+ set lines [split $bookmarks "\n"]
+ set bookmarkcurrent 0
+ foreach f $lines {
+ regexp {(\S+)$} $f full
+ regsub {\s+(\S+)$} $f "" direct
+ set sha [split $full ':']
+ set bookmark [lindex $sha 1]
+ set current [string first " * " $direct)]
+ regsub {^\s(\*|\s)\s} $direct "" direct
+ lappend bookmarkids($direct) $bookmark
+ lappend idbookmarks($bookmark) $direct
+ if {$current >= 0} {
+ set bookmarkcurrent $direct
+ }
+ }
+}
+
+proc readotherrefs {base dname excl} {
+ global otherrefids idotherrefs
+
+ set git [gitdir]
+ set files [glob -nocomplain -types f [file join $git $base *]]
+ foreach f $files {
+ catch {
+ set fd [open $f r]
+ set line [read $fd 40]
+ if {[regexp {^[0-9a-f]{12}} $line id]} {
+ set name "$dname[file tail $f]"
+ set otherrefids($name) $id
+ lappend idotherrefs($id) $name
+ }
+ close $fd
+ }
+ }
+ set dirs [glob -nocomplain -types d [file join $git $base *]]
+ foreach d $dirs {
+ set dir [file tail $d]
+ if {[lsearch -exact $excl $dir] >= 0} continue
+ readotherrefs [file join $base $dir] "$dname$dir/" {}
+ }
+}
+
+proc allcansmousewheel {delta} {
+ set delta [expr -5*(int($delta)/abs($delta))]
+ allcanvs yview scroll $delta units
+}
+
+proc error_popup msg {
+ set w .error
+ toplevel $w
+ wm transient $w .
+ message $w.m -text $msg -justify center -aspect 400
+ pack $w.m -side top -fill x -padx 20 -pady 20
+ button $w.ok -text OK -command "destroy $w"
+ pack $w.ok -side bottom -fill x
+ bind $w <Visibility> "grab $w; focus $w"
+ tkwait window $w
+}
+
+proc makewindow {} {
+ global canv canv2 canv3 linespc charspc ctext cflist textfont
+ global findtype findtypemenu findloc findstring fstring geometry
+ global entries sha1entry sha1string sha1but
+ global maincursor textcursor curtextcursor
+ global rowctxmenu gaudydiff mergemax
+ global hgvdiff bgcolor fgcolor diffremcolor diffaddcolor diffmerge1color
+ global diffmerge2color hunksepcolor
+ global posx posy
+
+ if {[info exists posx]} {
+ wm geometry . +$posx+$posy
+ }
+
+ menu .bar
+ .bar add cascade -label "File" -menu .bar.file
+ menu .bar.file
+ .bar.file add command -label "Reread references" -command rereadrefs
+ .bar.file add command -label "Quit" -command doquit
+ menu .bar.help
+ .bar add cascade -label "Help" -menu .bar.help
+ .bar.help add command -label "About hgk" -command about
+ . configure -menu .bar
+
+ if {![info exists geometry(canv1)]} {
+ set geometry(canv1) [expr 45 * $charspc]
+ set geometry(canv2) [expr 30 * $charspc]
+ set geometry(canv3) [expr 15 * $charspc]
+ set geometry(canvh) [expr 25 * $linespc + 4]
+ set geometry(ctextw) 80
+ set geometry(ctexth) 30
+ set geometry(cflistw) 30
+ }
+ panedwindow .ctop -orient vertical
+ if {[info exists geometry(width)]} {
+ .ctop conf -width $geometry(width) -height $geometry(height)
+ set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
+ set geometry(ctexth) [expr {($texth - 8) /
+ [font metrics $textfont -linespace]}]
+ }
+ frame .ctop.top
+ frame .ctop.top.bar
+ pack .ctop.top.bar -side bottom -fill x
+ set cscroll .ctop.top.csb
+ scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
+ pack $cscroll -side right -fill y
+ panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
+ pack .ctop.top.clist -side top -fill both -expand 1
+ .ctop add .ctop.top
+ set canv .ctop.top.clist.canv
+ canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
+ -bg $bgcolor -bd 0 \
+ -yscrollincr $linespc -yscrollcommand "$cscroll set" -selectbackground grey
+ .ctop.top.clist add $canv
+ set canv2 .ctop.top.clist.canv2
+ canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
+ -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
+ .ctop.top.clist add $canv2
+ set canv3 .ctop.top.clist.canv3
+ canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
+ -bg $bgcolor -bd 0 -yscrollincr $linespc -selectbackground grey
+ .ctop.top.clist add $canv3
+ bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
+
+ set sha1entry .ctop.top.bar.sha1
+ set entries $sha1entry
+ set sha1but .ctop.top.bar.sha1label
+ button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
+ -command gotocommit -width 8
+ $sha1but conf -disabledforeground [$sha1but cget -foreground]
+ pack .ctop.top.bar.sha1label -side left
+ entry $sha1entry -width 40 -font $textfont -textvariable sha1string
+ trace add variable sha1string write sha1change
+ pack $sha1entry -side left -pady 2
+
+ image create bitmap bm-left -data {
+ #define left_width 16
+ #define left_height 16
+ static unsigned char left_bits[] = {
+ 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
+ 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
+ 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
+ }
+ image create bitmap bm-right -data {
+ #define right_width 16
+ #define right_height 16
+ static unsigned char right_bits[] = {
+ 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
+ 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
+ 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
+ }
+ button .ctop.top.bar.leftbut -image bm-left -command goback \
+ -state disabled -width 26
+ pack .ctop.top.bar.leftbut -side left -fill y
+ button .ctop.top.bar.rightbut -image bm-right -command goforw \
+ -state disabled -width 26
+ pack .ctop.top.bar.rightbut -side left -fill y
+
+ button .ctop.top.bar.findbut -text "Find" -command dofind
+ pack .ctop.top.bar.findbut -side left
+ set findstring {}
+ set fstring .ctop.top.bar.findstring
+ lappend entries $fstring
+ entry $fstring -width 30 -font $textfont -textvariable findstring
+ pack $fstring -side left -expand 1 -fill x
+ set findtype Exact
+ set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
+ findtype Exact IgnCase Regexp]
+ set findloc "All fields"
+ tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
+ Comments Author Committer Files Pickaxe
+ pack .ctop.top.bar.findloc -side right
+ pack .ctop.top.bar.findtype -side right
+ # for making sure type==Exact whenever loc==Pickaxe
+ trace add variable findloc write findlocchange
+
+ panedwindow .ctop.cdet -orient horizontal
+ .ctop add .ctop.cdet
+ frame .ctop.cdet.left
+ set ctext .ctop.cdet.left.ctext
+ text $ctext -fg $fgcolor -bg $bgcolor -state disabled -font $textfont \
+ -width $geometry(ctextw) -height $geometry(ctexth) \
+ -yscrollcommand ".ctop.cdet.left.sb set" \
+ -xscrollcommand ".ctop.cdet.left.hb set" -wrap none
+ scrollbar .ctop.cdet.left.sb -command "$ctext yview"
+ scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview"
+ pack .ctop.cdet.left.sb -side right -fill y
+ pack .ctop.cdet.left.hb -side bottom -fill x
+ pack $ctext -side left -fill both -expand 1
+ .ctop.cdet add .ctop.cdet.left
+
+ $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
+ if {$gaudydiff} {
+ $ctext tag conf hunksep -back blue -fore white
+ $ctext tag conf d0 -back "#ff8080"
+ $ctext tag conf d1 -back green
+ } else {
+ $ctext tag conf hunksep -fore $hunksepcolor
+ $ctext tag conf d0 -fore $diffremcolor
+ $ctext tag conf d1 -fore $diffaddcolor
+
+ # The mX colours seem to be used in merge changesets, where m0
+ # is first parent, m1 is second parent and so on. Git can have
+ # several parents, Hg cannot, so I think the m2..mmax would be
+ # unused.
+ $ctext tag conf m0 -fore $diffmerge1color
+ $ctext tag conf m1 -fore $diffmerge2color
+ $ctext tag conf m2 -fore green
+ $ctext tag conf m3 -fore purple
+ $ctext tag conf m4 -fore brown
+ $ctext tag conf mmax -fore darkgrey
+ set mergemax 5
+ $ctext tag conf mresult -font [concat $textfont bold]
+ $ctext tag conf msep -font [concat $textfont bold]
+ $ctext tag conf found -back yellow
+ }
+
+ frame .ctop.cdet.right
+ set cflist .ctop.cdet.right.cfiles
+ listbox $cflist -fg $fgcolor -bg $bgcolor \
+ -selectmode extended -width $geometry(cflistw) \
+ -yscrollcommand ".ctop.cdet.right.sb set"
+ scrollbar .ctop.cdet.right.sb -command "$cflist yview"
+ pack .ctop.cdet.right.sb -side right -fill y
+ pack $cflist -side left -fill both -expand 1
+ .ctop.cdet add .ctop.cdet.right
+ bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
+
+ pack .ctop -side top -fill both -expand 1
+
+ bindall <1> {selcanvline %W %x %y}
+ #bindall <B1-Motion> {selcanvline %W %x %y}
+ bindall <MouseWheel> "allcansmousewheel %D"
+ bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
+ bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
+ bindall <2> "allcanvs scan mark 0 %y"
+ bindall <B2-Motion> "allcanvs scan dragto 0 %y"
+ bind . <Key-Up> "selnextline -1"
+ bind . <Key-Down> "selnextline 1"
+ bind . <Key-Prior> "allcanvs yview scroll -1 pages"
+ bind . <Key-Next> "allcanvs yview scroll 1 pages"
+ bindkey <Key-Delete> "$ctext yview scroll -1 pages"
+ bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
+ bindkey <Key-space> "$ctext yview scroll 1 pages"
+ bindkey p "selnextline -1"
+ bindkey n "selnextline 1"
+ bindkey b "$ctext yview scroll -1 pages"
+ bindkey d "$ctext yview scroll 18 units"
+ bindkey u "$ctext yview scroll -18 units"
+ bindkey / {findnext 1}
+ bindkey <Key-Return> {findnext 0}
+ bindkey ? findprev
+ bindkey f nextfile
+ bind . <Control-q> doquit
+ bind . <Control-w> doquit
+ bind . <Control-f> dofind
+ bind . <Control-g> {findnext 0}
+ bind . <Control-r> findprev
+ bind . <Control-equal> {incrfont 1}
+ bind . <Control-KP_Add> {incrfont 1}
+ bind . <Control-minus> {incrfont -1}
+ bind . <Control-KP_Subtract> {incrfont -1}
+ bind $cflist <<ListboxSelect>> listboxsel
+ bind . <Destroy> {savestuff %W}
+ bind . <Button-1> "click %W"
+ bind $fstring <Key-Return> dofind
+ bind $sha1entry <Key-Return> gotocommit
+ bind $sha1entry <<PasteSelection>> clearsha1
+
+ set maincursor [. cget -cursor]
+ set textcursor [$ctext cget -cursor]
+ set curtextcursor $textcursor
+
+ set rowctxmenu .rowctxmenu
+ menu $rowctxmenu -tearoff 0
+ $rowctxmenu add command -label "Diff this -> selected" \
+ -command {diffvssel 0}
+ $rowctxmenu add command -label "Diff selected -> this" \
+ -command {diffvssel 1}
+ $rowctxmenu add command -label "Make patch" -command mkpatch
+ $rowctxmenu add command -label "Create tag" -command mktag
+ $rowctxmenu add command -label "Write commit to file" -command writecommit
+ if { $hgvdiff ne "" } {
+ $rowctxmenu add command -label "Visual diff with parent" \
+ -command {vdiff 1}
+ $rowctxmenu add command -label "Visual diff with selected" \
+ -command {vdiff 0}
+ }
+}
+
+# when we make a key binding for the toplevel, make sure
+# it doesn't get triggered when that key is pressed in the
+# find string entry widget.
+proc bindkey {ev script} {
+ global entries
+ bind . $ev $script
+ set escript [bind Entry $ev]
+ if {$escript == {}} {
+ set escript [bind Entry <Key>]
+ }
+ foreach e $entries {
+ bind $e $ev "$escript; break"
+ }
+}
+
+# set the focus back to the toplevel for any click outside
+# the entry widgets
+proc click {w} {
+ global entries
+ foreach e $entries {
+ if {$w == $e} return
+ }
+ focus .
+}
+
+proc savestuff {w} {
+ global canv canv2 canv3 ctext cflist mainfont textfont
+ global stuffsaved findmergefiles gaudydiff maxgraphpct
+ global maxwidth authorcolors curidfont bgcolor fgcolor
+ global diffremcolor diffaddcolor hunksepcolor
+ global diffmerge1color diffmerge2color
+
+ if {$stuffsaved} return
+ if {![winfo viewable .]} return
+ catch {
+ set f [open "~/.hgk-new" w]
+ puts $f [list set mainfont $mainfont]
+ puts $f [list set curidfont $curidfont]
+ puts $f [list set textfont $textfont]
+ puts $f [list set findmergefiles $findmergefiles]
+ puts $f [list set gaudydiff $gaudydiff]
+ puts $f [list set maxgraphpct $maxgraphpct]
+ puts $f [list set maxwidth $maxwidth]
+ puts $f "set geometry(width) [winfo width .ctop]"
+ puts $f "set geometry(height) [winfo height .ctop]"
+ puts $f "set geometry(canv1) [expr [winfo width $canv]-2]"
+ puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]"
+ puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]"
+ puts $f "set geometry(canvh) [expr [winfo height $canv]-2]"
+ set wid [expr {([winfo width $ctext] - 8) \
+ / [font measure $textfont "0"]}]
+ puts $f "set geometry(ctextw) $wid"
+ set wid [expr {([winfo width $cflist] - 11) \
+ / [font measure [$cflist cget -font] "0"]}]
+ puts $f "set geometry(cflistw) $wid"
+ puts $f "#"
+ puts $f "# main window position:"
+ puts $f "set posx [winfo x .]"
+ puts $f "set posy [winfo y .]"
+ puts $f "#"
+ puts $f "# authorcolors format:"
+ puts $f "#"
+ puts $f "# zero or more sublists of"
+ puts $f "#"
+ puts $f "# { regex color }"
+ puts $f "#"
+ puts $f "# followed by a list of colors"
+ puts $f "#"
+ puts $f "# If the commit author matches a regex in a sublist,"
+ puts $f "# the commit will be colored by that color"
+ puts $f "# otherwise the next unused entry from the list of colors"
+ puts $f "# will be assigned to this commit and also all other commits"
+ puts $f "# of the same author. When the list of colors is exhausted,"
+ puts $f "# the last entry will be reused."
+ puts $f "#"
+ puts $f "set authorcolors {$authorcolors}"
+ puts $f "#"
+ puts $f "# The background color in the text windows"
+ puts $f "set bgcolor $bgcolor"
+ puts $f "#"
+ puts $f "# The text color used in the diff and file list view"
+ puts $f "set fgcolor $fgcolor"
+ puts $f "#"
+ puts $f "# Color to display + lines in diffs"
+ puts $f "set diffaddcolor $diffaddcolor"
+ puts $f "#"
+ puts $f "# Color to display - lines in diffs"
+ puts $f "set diffremcolor $diffremcolor"
+ puts $f "#"
+ puts $f "# Merge diffs: Color to signal lines from first parent"
+ puts $f "set diffmerge1color $diffmerge1color"
+ puts $f "#"
+ puts $f "# Merge diffs: Color to signal lines from second parent"
+ puts $f "set diffmerge2color $diffmerge2color"
+ puts $f "#"
+ puts $f "# Hunkseparator (@@ -lineno,lines +lineno,lines @@) color"
+ puts $f "set hunksepcolor $hunksepcolor"
+ close $f
+ file rename -force "~/.hgk-new" "~/.hgk"
+ }
+ set stuffsaved 1
+}
+
+proc resizeclistpanes {win w} {
+ global oldwidth
+ if [info exists oldwidth($win)] {
+ set s0 [$win sash coord 0]
+ set s1 [$win sash coord 1]
+ if {$w < 60} {
+ set sash0 [expr {int($w/2 - 2)}]
+ set sash1 [expr {int($w*5/6 - 2)}]
+ } else {
+ set factor [expr {1.0 * $w / $oldwidth($win)}]
+ set sash0 [expr {int($factor * [lindex $s0 0])}]
+ set sash1 [expr {int($factor * [lindex $s1 0])}]
+ if {$sash0 < 30} {
+ set sash0 30
+ }
+ if {$sash1 < $sash0 + 20} {
+ set sash1 [expr $sash0 + 20]
+ }
+ if {$sash1 > $w - 10} {
+ set sash1 [expr $w - 10]
+ if {$sash0 > $sash1 - 20} {
+ set sash0 [expr $sash1 - 20]
+ }
+ }
+ }
+ $win sash place 0 $sash0 [lindex $s0 1]
+ $win sash place 1 $sash1 [lindex $s1 1]
+ }
+ set oldwidth($win) $w
+}
+
+proc resizecdetpanes {win w} {
+ global oldwidth
+ if [info exists oldwidth($win)] {
+ set s0 [$win sash coord 0]
+ if {$w < 60} {
+ set sash0 [expr {int($w*3/4 - 2)}]
+ } else {
+ set factor [expr {1.0 * $w / $oldwidth($win)}]
+ set sash0 [expr {int($factor * [lindex $s0 0])}]
+ if {$sash0 < 45} {
+ set sash0 45
+ }
+ if {$sash0 > $w - 15} {
+ set sash0 [expr $w - 15]
+ }
+ }
+ $win sash place 0 $sash0 [lindex $s0 1]
+ }
+ set oldwidth($win) $w
+}
+
+proc allcanvs args {
+ global canv canv2 canv3
+ eval $canv $args
+ eval $canv2 $args
+ eval $canv3 $args
+}
+
+proc bindall {event action} {
+ global canv canv2 canv3
+ bind $canv $event $action
+ bind $canv2 $event $action
+ bind $canv3 $event $action
+}
+
+proc about {} {
+ set w .about
+ if {[winfo exists $w]} {
+ raise $w
+ return
+ }
+ toplevel $w
+ wm title $w "About hgk"
+ message $w.m -text {
+Hgk version 1.2
+
+Copyright © 2005 Paul Mackerras
+
+Use and redistribute under the terms of the GNU General Public License} \
+ -justify center -aspect 400
+ pack $w.m -side top -fill x -padx 20 -pady 20
+ button $w.ok -text Close -command "destroy $w"
+ pack $w.ok -side bottom
+}
+
+set aunextcolor 0
+proc assignauthorcolor {name} {
+ global authorcolors aucolormap aunextcolor
+ if [info exists aucolormap($name)] return
+
+ set randomcolors {black}
+ for {set i 0} {$i < [llength $authorcolors]} {incr i} {
+ set col [lindex $authorcolors $i]
+ if {[llength $col] > 1} {
+ set re [lindex $col 0]
+ set c [lindex $col 1]
+ if {[regexp -- $re $name]} {
+ set aucolormap($name) $c
+ return
+ }
+ } else {
+ set randomcolors [lrange $authorcolors $i end]
+ break
+ }
+ }
+
+ set ncolors [llength $randomcolors]
+ set c [lindex $randomcolors $aunextcolor]
+ if {[incr aunextcolor] >= $ncolors} {
+ incr aunextcolor -1
+ }
+ set aucolormap($name) $c
+}
+
+proc assigncolor {id} {
+ global commitinfo colormap commcolors colors nextcolor
+ global parents nparents children nchildren
+ global cornercrossings crossings
+
+ if [info exists colormap($id)] return
+ set ncolors [llength $colors]
+ if {$nparents($id) <= 1 && $nchildren($id) == 1} {
+ set child [lindex $children($id) 0]
+ if {[info exists colormap($child)]
+ && $nparents($child) == 1} {
+ set colormap($id) $colormap($child)
+ return
+ }
+ }
+ set badcolors {}
+ if {[info exists cornercrossings($id)]} {
+ foreach x $cornercrossings($id) {
+ if {[info exists colormap($x)]
+ && [lsearch -exact $badcolors $colormap($x)] < 0} {
+ lappend badcolors $colormap($x)
+ }
+ }
+ if {[llength $badcolors] >= $ncolors} {
+ set badcolors {}
+ }
+ }
+ set origbad $badcolors
+ if {[llength $badcolors] < $ncolors - 1} {
+ if {[info exists crossings($id)]} {
+ foreach x $crossings($id) {
+ if {[info exists colormap($x)]
+ && [lsearch -exact $badcolors $colormap($x)] < 0} {
+ lappend badcolors $colormap($x)
+ }
+ }
+ if {[llength $badcolors] >= $ncolors} {
+ set badcolors $origbad
+ }
+ }
+ set origbad $badcolors
+ }
+ if {[llength $badcolors] < $ncolors - 1} {
+ foreach child $children($id) {
+ if {[info exists colormap($child)]
+ && [lsearch -exact $badcolors $colormap($child)] < 0} {
+ lappend badcolors $colormap($child)
+ }
+ if {[info exists parents($child)]} {
+ foreach p $parents($child) {
+ if {[info exists colormap($p)]
+ && [lsearch -exact $badcolors $colormap($p)] < 0} {
+ lappend badcolors $colormap($p)
+ }
+ }
+ }
+ }
+ if {[llength $badcolors] >= $ncolors} {
+ set badcolors $origbad
+ }
+ }
+ for {set i 0} {$i <= $ncolors} {incr i} {
+ set c [lindex $colors $nextcolor]
+ if {[incr nextcolor] >= $ncolors} {
+ set nextcolor 0
+ }
+ if {[lsearch -exact $badcolors $c]} break
+ }
+ set colormap($id) $c
+}
+
+proc initgraph {} {
+ global canvy canvy0 lineno numcommits nextcolor linespc
+ global mainline mainlinearrow sidelines
+ global nchildren ncleft
+ global displist nhyperspace
+
+ allcanvs delete all
+ set nextcolor 0
+ set canvy $canvy0
+ set lineno -1
+ set numcommits 0
+ catch {unset mainline}
+ catch {unset mainlinearrow}
+ catch {unset sidelines}
+ foreach id [array names nchildren] {
+ set ncleft($id) $nchildren($id)
+ }
+ set displist {}
+ set nhyperspace 0
+}
+
+proc bindline {t id} {
+ global canv
+
+ $canv bind $t <Enter> "lineenter %x %y $id"
+ $canv bind $t <Motion> "linemotion %x %y $id"
+ $canv bind $t <Leave> "lineleave $id"
+ $canv bind $t <Button-1> "lineclick %x %y $id 1"
+}
+
+proc drawlines {id xtra} {
+ global mainline mainlinearrow sidelines lthickness colormap canv
+
+ $canv delete lines.$id
+ if {[info exists mainline($id)]} {
+ set t [$canv create line $mainline($id) \
+ -width [expr {($xtra + 1) * $lthickness}] \
+ -fill $colormap($id) -tags lines.$id \
+ -arrow $mainlinearrow($id)]
+ $canv lower $t
+ bindline $t $id
+ }
+ if {[info exists sidelines($id)]} {
+ foreach ls $sidelines($id) {
+ set coords [lindex $ls 0]
+ set thick [lindex $ls 1]
+ set arrow [lindex $ls 2]
+ set t [$canv create line $coords -fill $colormap($id) \
+ -width [expr {($thick + $xtra) * $lthickness}] \
+ -arrow $arrow -tags lines.$id]
+ $canv lower $t
+ bindline $t $id
+ }
+ }
+}
+
+# level here is an index in displist
+proc drawcommitline {level} {
+ global parents children nparents displist
+ global canv canv2 canv3 mainfont namefont canvy linespc
+ global lineid linehtag linentag linedtag commitinfo
+ global colormap numcommits currentparents dupparents
+ global idtags idline idheads idotherrefs idbookmarks
+ global lineno lthickness mainline mainlinearrow sidelines
+ global commitlisted rowtextx idpos lastuse displist
+ global oldnlines olddlevel olddisplist
+ global aucolormap curid curidfont
+
+ incr numcommits
+ incr lineno
+ set id [lindex $displist $level]
+ set lastuse($id) $lineno
+ set lineid($lineno) $id
+ set idline($id) $lineno
+ set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}]
+ if {![info exists commitinfo($id)]} {
+ readcommit $id
+ if {![info exists commitinfo($id)]} {
+ set commitinfo($id) {"No commit information available"}
+ set nparents($id) 0
+ }
+ }
+ assigncolor $id
+ set currentparents {}
+ set dupparents {}
+ if {[info exists commitlisted($id)] && [info exists parents($id)]} {
+ foreach p $parents($id) {
+ if {[lsearch -exact $currentparents $p] < 0} {
+ lappend currentparents $p
+ } else {
+ # remember that this parent was listed twice
+ lappend dupparents $p
+ }
+ }
+ }
+ set x [xcoord $level $level $lineno]
+ set y1 $canvy
+ set canvy [expr $canvy + $linespc]
+ allcanvs conf -scrollregion \
+ [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]]
+ if {[info exists mainline($id)]} {
+ lappend mainline($id) $x $y1
+ if {$mainlinearrow($id) ne "none"} {
+ set mainline($id) [trimdiagstart $mainline($id)]
+ }
+ }
+ drawlines $id 0
+ set orad [expr {$linespc / 3}]
+ set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \
+ [expr $x + $orad - 1] [expr $y1 + $orad - 1] \
+ -fill $ofill -outline black -width 1]
+ $canv raise $t
+ $canv bind $t <1> {selcanvline {} %x %y}
+ set xt [xcoord [llength $displist] $level $lineno]
+ if {[llength $currentparents] > 2} {
+ set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}]
+ }
+ set rowtextx($lineno) $xt
+ set idpos($id) [list $x $xt $y1]
+ if {[info exists idtags($id)] || [info exists idheads($id)]
+ || [info exists idotherrefs($id)] || [info exists idbookmarks($id)]} {
+ set xt [drawtags $id $x $xt $y1]
+ }
+ set headline [lindex $commitinfo($id) 0]
+ set name [lindex $commitinfo($id) 1]
+ assignauthorcolor $name
+ set fg $aucolormap($name)
+ if {$id == $curid} {
+ set fn $curidfont
+ } else {
+ set fn $mainfont
+ }
+
+ set date [lindex $commitinfo($id) 2]
+ set linehtag($lineno) [$canv create text $xt $y1 -anchor w \
+ -text $headline -font $fn \
+ -fill $fg]
+ $canv bind $linehtag($lineno) <<B3>> "rowmenu %X %Y $id"
+ set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \
+ -text $name -font $namefont \
+ -fill $fg]
+ set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \
+ -text $date -font $mainfont \
+ -fill $fg]
+
+ set olddlevel $level
+ set olddisplist $displist
+ set oldnlines [llength $displist]
+}
+
+proc drawtags {id x xt y1} {
+ global bookmarkcurrent idtags idbookmarks idheads idotherrefs commitinfo
+ global linespc lthickness
+ global canv mainfont idline rowtextx
+
+ set marks {}
+ set nbookmarks 0
+ set ntags 0
+ set nheads 0
+ if {[info exists idtags($id)]} {
+ set marks $idtags($id)
+ set ntags [llength $marks]
+ }
+ if {[info exists idbookmarks($id)]} {
+ set marks [concat $marks $idbookmarks($id)]
+ set nbookmarks [llength $idbookmarks($id)]
+ }
+ if {[info exists idheads($id)]} {
+ set headmark [lindex $commitinfo($id) 7]
+ if {$headmark ne "default"} {
+ lappend marks $headmark
+ set nheads 1
+ }
+ }
+ if {$marks eq {}} {
+ return $xt
+ }
+
+ set delta [expr {int(0.5 * ($linespc - $lthickness))}]
+ set yt [expr $y1 - 0.5 * $linespc]
+ set yb [expr $yt + $linespc - 1]
+ set xvals {}
+ set wvals {}
+ foreach tag $marks {
+ set wid [font measure $mainfont $tag]
+ lappend xvals $xt
+ lappend wvals $wid
+ set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
+ }
+ set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
+ -width $lthickness -fill black -tags tag.$id]
+ $canv lower $t
+ foreach tag $marks x $xvals wid $wvals {
+ set xl [expr $x + $delta]
+ set xr [expr $x + $delta + $wid + $lthickness]
+ if {[incr ntags -1] >= 0} {
+ # draw a tag
+ set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \
+ $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \
+ -width 1 -outline black -fill yellow -tags tag.$id]
+ $canv bind $t <1> [list showtag $tag 1]
+ set rowtextx($idline($id)) [expr {$xr + $linespc}]
+ } elseif {[incr nbookmarks -1] >= 0} {
+ # draw a tag
+ set col gray50
+ if {[string compare $bookmarkcurrent $tag] == 0} {
+ set col gray
+ }
+ set xl [expr $xl - $delta/2]
+ $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
+ -width 1 -outline black -fill $col -tags tag.$id
+ } else {
+ # draw a head or other ref
+ if {[incr nheads -1] >= 0} {
+ set col green
+ } else {
+ set col "#ddddff"
+ }
+ set xl [expr $xl - $delta/2]
+ $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
+ -width 1 -outline black -fill $col -tags tag.$id
+ }
+ set t [$canv create text $xl $y1 -anchor w -text $tag \
+ -font $mainfont -tags tag.$id]
+ if {$ntags >= 0} {
+ $canv bind $t <1> [list showtag $tag 1]
+ }
+ }
+ return $xt
+}
+
+proc notecrossings {id lo hi corner} {
+ global olddisplist crossings cornercrossings
+
+ for {set i $lo} {[incr i] < $hi} {} {
+ set p [lindex $olddisplist $i]
+ if {$p == {}} continue
+ if {$i == $corner} {
+ if {![info exists cornercrossings($id)]
+ || [lsearch -exact $cornercrossings($id) $p] < 0} {
+ lappend cornercrossings($id) $p
+ }
+ if {![info exists cornercrossings($p)]
+ || [lsearch -exact $cornercrossings($p) $id] < 0} {
+ lappend cornercrossings($p) $id
+ }
+ } else {
+ if {![info exists crossings($id)]
+ || [lsearch -exact $crossings($id) $p] < 0} {
+ lappend crossings($id) $p
+ }
+ if {![info exists crossings($p)]
+ || [lsearch -exact $crossings($p) $id] < 0} {
+ lappend crossings($p) $id
+ }
+ }
+ }
+}
+
+proc xcoord {i level ln} {
+ global canvx0 xspc1 xspc2
+
+ set x [expr {$canvx0 + $i * $xspc1($ln)}]
+ if {$i > 0 && $i == $level} {
+ set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
+ } elseif {$i > $level} {
+ set x [expr {$x + $xspc2 - $xspc1($ln)}]
+ }
+ return $x
+}
+
+# it seems Tk can't draw arrows on the end of diagonal line segments...
+proc trimdiagend {line} {
+ while {[llength $line] > 4} {
+ set x1 [lindex $line end-3]
+ set y1 [lindex $line end-2]
+ set x2 [lindex $line end-1]
+ set y2 [lindex $line end]
+ if {($x1 == $x2) != ($y1 == $y2)} break
+ set line [lreplace $line end-1 end]
+ }
+ return $line
+}
+
+proc trimdiagstart {line} {
+ while {[llength $line] > 4} {
+ set x1 [lindex $line 0]
+ set y1 [lindex $line 1]
+ set x2 [lindex $line 2]
+ set y2 [lindex $line 3]
+ if {($x1 == $x2) != ($y1 == $y2)} break
+ set line [lreplace $line 0 1]
+ }
+ return $line
+}
+
+proc drawslants {id needonscreen nohs} {
+ global canv mainline mainlinearrow sidelines
+ global canvx0 canvy xspc1 xspc2 lthickness
+ global currentparents dupparents
+ global lthickness linespc canvy colormap lineno geometry
+ global maxgraphpct maxwidth
+ global displist onscreen lastuse
+ global parents commitlisted
+ global oldnlines olddlevel olddisplist
+ global nhyperspace numcommits nnewparents
+
+ if {$lineno < 0} {
+ lappend displist $id
+ set onscreen($id) 1
+ return 0
+ }
+
+ set y1 [expr {$canvy - $linespc}]
+ set y2 $canvy
+
+ # work out what we need to get back on screen
+ set reins {}
+ if {$onscreen($id) < 0} {
+ # next to do isn't displayed, better get it on screen...
+ lappend reins [list $id 0]
+ }
+ # make sure all the previous commits's parents are on the screen
+ foreach p $currentparents {
+ if {$onscreen($p) < 0} {
+ lappend reins [list $p 0]
+ }
+ }
+ # bring back anything requested by caller
+ if {$needonscreen ne {}} {
+ lappend reins $needonscreen
+ }
+
+ # try the shortcut
+ if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} {
+ set dlevel $olddlevel
+ set x [xcoord $dlevel $dlevel $lineno]
+ set mainline($id) [list $x $y1]
+ set mainlinearrow($id) none
+ set lastuse($id) $lineno
+ set displist [lreplace $displist $dlevel $dlevel $id]
+ set onscreen($id) 1
+ set xspc1([expr {$lineno + 1}]) $xspc1($lineno)
+ return $dlevel
+ }
+
+ # update displist
+ set displist [lreplace $displist $olddlevel $olddlevel]
+ set j $olddlevel
+ foreach p $currentparents {
+ set lastuse($p) $lineno
+ if {$onscreen($p) == 0} {
+ set displist [linsert $displist $j $p]
+ set onscreen($p) 1
+ incr j
+ }
+ }
+ if {$onscreen($id) == 0} {
+ lappend displist $id
+ set onscreen($id) 1
+ }
+
+ # remove the null entry if present
+ set nullentry [lsearch -exact $displist {}]
+ if {$nullentry >= 0} {
+ set displist [lreplace $displist $nullentry $nullentry]
+ }
+
+ # bring back the ones we need now (if we did it earlier
+ # it would change displist and invalidate olddlevel)
+ foreach pi $reins {
+ # test again in case of duplicates in reins
+ set p [lindex $pi 0]
+ if {$onscreen($p) < 0} {
+ set onscreen($p) 1
+ set lastuse($p) $lineno
+ set displist [linsert $displist [lindex $pi 1] $p]
+ incr nhyperspace -1
+ }
+ }
+
+ set lastuse($id) $lineno
+
+ # see if we need to make any lines jump off into hyperspace
+ set displ [llength $displist]
+ if {$displ > $maxwidth} {
+ set ages {}
+ foreach x $displist {
+ lappend ages [list $lastuse($x) $x]
+ }
+ set ages [lsort -integer -index 0 $ages]
+ set k 0
+ while {$displ > $maxwidth} {
+ set use [lindex $ages $k 0]
+ set victim [lindex $ages $k 1]
+ if {$use >= $lineno - 5} break
+ incr k
+ if {[lsearch -exact $nohs $victim] >= 0} continue
+ set i [lsearch -exact $displist $victim]
+ set displist [lreplace $displist $i $i]
+ set onscreen($victim) -1
+ incr nhyperspace
+ incr displ -1
+ if {$i < $nullentry} {
+ incr nullentry -1
+ }
+ set x [lindex $mainline($victim) end-1]
+ lappend mainline($victim) $x $y1
+ set line [trimdiagend $mainline($victim)]
+ set arrow "last"
+ if {$mainlinearrow($victim) ne "none"} {
+ set line [trimdiagstart $line]
+ set arrow "both"
+ }
+ lappend sidelines($victim) [list $line 1 $arrow]
+ unset mainline($victim)
+ }
+ }
+
+ set dlevel [lsearch -exact $displist $id]
+
+ # If we are reducing, put in a null entry
+ if {$displ < $oldnlines} {
+ # does the next line look like a merge?
+ # i.e. does it have > 1 new parent?
+ if {$nnewparents($id) > 1} {
+ set i [expr {$dlevel + 1}]
+ } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} {
+ set i $olddlevel
+ if {$nullentry >= 0 && $nullentry < $i} {
+ incr i -1
+ }
+ } elseif {$nullentry >= 0} {
+ set i $nullentry
+ while {$i < $displ
+ && [lindex $olddisplist $i] == [lindex $displist $i]} {
+ incr i
+ }
+ } else {
+ set i $olddlevel
+ if {$dlevel >= $i} {
+ incr i
+ }
+ }
+ if {$i < $displ} {
+ set displist [linsert $displist $i {}]
+ incr displ
+ if {$dlevel >= $i} {
+ incr dlevel
+ }
+ }
+ }
+
+ # decide on the line spacing for the next line
+ set lj [expr {$lineno + 1}]
+ set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}]
+ if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} {
+ set xspc1($lj) $xspc2
+ } else {
+ set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}]
+ if {$xspc1($lj) < $lthickness} {
+ set xspc1($lj) $lthickness
+ }
+ }
+
+ foreach idi $reins {
+ set id [lindex $idi 0]
+ set j [lsearch -exact $displist $id]
+ set xj [xcoord $j $dlevel $lj]
+ set mainline($id) [list $xj $y2]
+ set mainlinearrow($id) first
+ }
+
+ set i -1
+ foreach id $olddisplist {
+ incr i
+ if {$id == {}} continue
+ if {$onscreen($id) <= 0} continue
+ set xi [xcoord $i $olddlevel $lineno]
+ if {$i == $olddlevel} {
+ foreach p $currentparents {
+ set j [lsearch -exact $displist $p]
+ set coords [list $xi $y1]
+ set xj [xcoord $j $dlevel $lj]
+ if {$xj < $xi - $linespc} {
+ lappend coords [expr {$xj + $linespc}] $y1
+ notecrossings $p $j $i [expr {$j + 1}]
+ } elseif {$xj > $xi + $linespc} {
+ lappend coords [expr {$xj - $linespc}] $y1
+ notecrossings $p $i $j [expr {$j - 1}]
+ }
+ if {[lsearch -exact $dupparents $p] >= 0} {
+ # draw a double-width line to indicate the doubled parent
+ lappend coords $xj $y2
+ lappend sidelines($p) [list $coords 2 none]
+ if {![info exists mainline($p)]} {
+ set mainline($p) [list $xj $y2]
+ set mainlinearrow($p) none
+ }
+ } else {
+ # normal case, no parent duplicated
+ set yb $y2
+ set dx [expr {abs($xi - $xj)}]
+ if {0 && $dx < $linespc} {
+ set yb [expr {$y1 + $dx}]
+ }
+ if {![info exists mainline($p)]} {
+ if {$xi != $xj} {
+ lappend coords $xj $yb
+ }
+ set mainline($p) $coords
+ set mainlinearrow($p) none
+ } else {
+ lappend coords $xj $yb
+ if {$yb < $y2} {
+ lappend coords $xj $y2
+ }
+ lappend sidelines($p) [list $coords 1 none]
+ }
+ }
+ }
+ } else {
+ set j $i
+ if {[lindex $displist $i] != $id} {
+ set j [lsearch -exact $displist $id]
+ }
+ if {$j != $i || $xspc1($lineno) != $xspc1($lj)
+ || ($olddlevel < $i && $i < $dlevel)
+ || ($dlevel < $i && $i < $olddlevel)} {
+ set xj [xcoord $j $dlevel $lj]
+ lappend mainline($id) $xi $y1 $xj $y2
+ }
+ }
+ }
+ return $dlevel
+}
+
+# search for x in a list of lists
+proc llsearch {llist x} {
+ set i 0
+ foreach l $llist {
+ if {$l == $x || [lsearch -exact $l $x] >= 0} {
+ return $i
+ }
+ incr i
+ }
+ return -1
+}
+
+proc drawmore {reading} {
+ global displayorder numcommits ncmupdate nextupdate
+ global stopped nhyperspace parents commitlisted
+ global maxwidth onscreen displist currentparents olddlevel
+
+ set n [llength $displayorder]
+ while {$numcommits < $n} {
+ set id [lindex $displayorder $numcommits]
+ set ctxend [expr {$numcommits + 10}]
+ if {!$reading && $ctxend > $n} {
+ set ctxend $n
+ }
+ set dlist {}
+ if {$numcommits > 0} {
+ set dlist [lreplace $displist $olddlevel $olddlevel]
+ set i $olddlevel
+ foreach p $currentparents {
+ if {$onscreen($p) == 0} {
+ set dlist [linsert $dlist $i $p]
+ incr i
+ }
+ }
+ }
+ set nohs {}
+ set reins {}
+ set isfat [expr {[llength $dlist] > $maxwidth}]
+ if {$nhyperspace > 0 || $isfat} {
+ if {$ctxend > $n} break
+ # work out what to bring back and
+ # what we want to don't want to send into hyperspace
+ set room 1
+ for {set k $numcommits} {$k < $ctxend} {incr k} {
+ set x [lindex $displayorder $k]
+ set i [llsearch $dlist $x]
+ if {$i < 0} {
+ set i [llength $dlist]
+ lappend dlist $x
+ }
+ if {[lsearch -exact $nohs $x] < 0} {
+ lappend nohs $x
+ }
+ if {$reins eq {} && $onscreen($x) < 0 && $room} {
+ set reins [list $x $i]
+ }
+ set newp {}
+ if {[info exists commitlisted($x)]} {
+ set right 0
+ foreach p $parents($x) {
+ if {[llsearch $dlist $p] < 0} {
+ lappend newp $p
+ if {[lsearch -exact $nohs $p] < 0} {
+ lappend nohs $p
+ }
+ if {$reins eq {} && $onscreen($p) < 0 && $room} {
+ set reins [list $p [expr {$i + $right}]]
+ }
+ }
+ set right 1
+ }
+ }
+ set l [lindex $dlist $i]
+ if {[llength $l] == 1} {
+ set l $newp
+ } else {
+ set j [lsearch -exact $l $x]
+ set l [concat [lreplace $l $j $j] $newp]
+ }
+ set dlist [lreplace $dlist $i $i $l]
+ if {$room && $isfat && [llength $newp] <= 1} {
+ set room 0
+ }
+ }
+ }
+
+ set dlevel [drawslants $id $reins $nohs]
+ drawcommitline $dlevel
+ if {[clock clicks -milliseconds] >= $nextupdate
+ && $numcommits >= $ncmupdate} {
+ doupdate $reading
+ if {$stopped} break
+ }
+ }
+}
+
+# level here is an index in todo
+proc updatetodo {level noshortcut} {
+ global ncleft todo nnewparents
+ global commitlisted parents onscreen
+
+ set id [lindex $todo $level]
+ set olds {}
+ if {[info exists commitlisted($id)]} {
+ foreach p $parents($id) {
+ if {[lsearch -exact $olds $p] < 0} {
+ lappend olds $p
+ }
+ }
+ }
+ if {!$noshortcut && [llength $olds] == 1} {
+ set p [lindex $olds 0]
+ if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} {
+ set ncleft($p) 0
+ set todo [lreplace $todo $level $level $p]
+ set onscreen($p) 0
+ set nnewparents($id) 1
+ return 0
+ }
+ }
+
+ set todo [lreplace $todo $level $level]
+ set i $level
+ set n 0
+ foreach p $olds {
+ incr ncleft($p) -1
+ set k [lsearch -exact $todo $p]
+ if {$k < 0} {
+ set todo [linsert $todo $i $p]
+ set onscreen($p) 0
+ incr i
+ incr n
+ }
+ }
+ set nnewparents($id) $n
+
+ return 1
+}
+
+proc decidenext {{noread 0}} {
+ global ncleft todo
+ global datemode cdate
+ global commitinfo
+
+ # choose which one to do next time around
+ set todol [llength $todo]
+ set level -1
+ set latest {}
+ for {set k $todol} {[incr k -1] >= 0} {} {
+ set p [lindex $todo $k]
+ if {$ncleft($p) == 0} {
+ if {$datemode} {
+ if {![info exists commitinfo($p)]} {
+ if {$noread} {
+ return {}
+ }
+ readcommit $p
+ }
+ if {$latest == {} || $cdate($p) > $latest} {
+ set level $k
+ set latest $cdate($p)
+ }
+ } else {
+ set level $k
+ break
+ }
+ }
+ }
+ if {$level < 0} {
+ if {$todo != {}} {
+ puts "ERROR: none of the pending commits can be done yet:"
+ foreach p $todo {
+ puts " $p ($ncleft($p))"
+ }
+ }
+ return -1
+ }
+
+ return $level
+}
+
+proc drawcommit {id} {
+ global phase todo nchildren datemode nextupdate
+ global numcommits ncmupdate displayorder todo onscreen
+
+ if {$phase != "incrdraw"} {
+ set phase incrdraw
+ set displayorder {}
+ set todo {}
+ initgraph
+ }
+ if {$nchildren($id) == 0} {
+ lappend todo $id
+ set onscreen($id) 0
+ }
+ set level [decidenext 1]
+ if {$level == {} || $id != [lindex $todo $level]} {
+ return
+ }
+ while 1 {
+ lappend displayorder [lindex $todo $level]
+ if {[updatetodo $level $datemode]} {
+ set level [decidenext 1]
+ if {$level == {}} break
+ }
+ set id [lindex $todo $level]
+ if {![info exists commitlisted($id)]} {
+ break
+ }
+ }
+ drawmore 1
+}
+
+proc finishcommits {} {
+ global phase
+ global canv mainfont ctext maincursor textcursor
+
+ if {$phase != "incrdraw"} {
+ $canv delete all
+ $canv create text 3 3 -anchor nw -text "No commits selected" \
+ -font $mainfont -tags textitems
+ set phase {}
+ } else {
+ drawrest
+ }
+ . config -cursor $maincursor
+ settextcursor $textcursor
+}
+
+# Don't change the text pane cursor if it is currently the hand cursor,
+# showing that we are over a sha1 ID link.
+proc settextcursor {c} {
+ global ctext curtextcursor
+
+ if {[$ctext cget -cursor] == $curtextcursor} {
+ $ctext config -cursor $c
+ }
+ set curtextcursor $c
+}
+
+proc drawgraph {} {
+ global nextupdate startmsecs ncmupdate
+ global displayorder onscreen
+
+ if {$displayorder == {}} return
+ set startmsecs [clock clicks -milliseconds]
+ set nextupdate [expr $startmsecs + 100]
+ set ncmupdate 1
+ initgraph
+ foreach id $displayorder {
+ set onscreen($id) 0
+ }
+ drawmore 0
+}
+
+proc drawrest {} {
+ global phase stopped redisplaying selectedline
+ global datemode todo displayorder
+ global numcommits ncmupdate
+ global nextupdate startmsecs
+
+ set level [decidenext]
+ if {$level >= 0} {
+ set phase drawgraph
+ while 1 {
+ lappend displayorder [lindex $todo $level]
+ set hard [updatetodo $level $datemode]
+ if {$hard} {
+ set level [decidenext]
+ if {$level < 0} break
+ }
+ }
+ drawmore 0
+ }
+ set phase {}
+ set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
+ #puts "overall $drawmsecs ms for $numcommits commits"
+ if {$redisplaying} {
+ if {$stopped == 0 && [info exists selectedline]} {
+ selectline $selectedline 0
+ }
+ if {$stopped == 1} {
+ set stopped 0
+ after idle drawgraph
+ } else {
+ set redisplaying 0
+ }
+ }
+}
+
+proc findmatches {f} {
+ global findtype foundstring foundstrlen
+ if {$findtype == "Regexp"} {
+ set matches [regexp -indices -all -inline $foundstring $f]
+ } else {
+ if {$findtype == "IgnCase"} {
+ set str [string tolower $f]
+ } else {
+ set str $f
+ }
+ set matches {}
+ set i 0
+ while {[set j [string first $foundstring $str $i]] >= 0} {
+ lappend matches [list $j [expr $j+$foundstrlen-1]]
+ set i [expr $j + $foundstrlen]
+ }
+ }
+ return $matches
+}
+
+proc dofind {} {
+ global findtype findloc findstring markedmatches commitinfo
+ global numcommits lineid linehtag linentag linedtag
+ global mainfont namefont canv canv2 canv3 selectedline
+ global matchinglines foundstring foundstrlen
+
+ stopfindproc
+ unmarkmatches
+ focus .
+ set matchinglines {}
+ if {$findloc == "Pickaxe"} {
+ findpatches
+ return
+ }
+ if {$findtype == "IgnCase"} {
+ set foundstring [string tolower $findstring]
+ } else {
+ set foundstring $findstring
+ }
+ set foundstrlen [string length $findstring]
+ if {$foundstrlen == 0} return
+ if {$findloc == "Files"} {
+ findfiles
+ return
+ }
+ if {![info exists selectedline]} {
+ set oldsel -1
+ } else {
+ set oldsel $selectedline
+ }
+ set didsel 0
+ set fldtypes {Headline Author Date Committer CDate Comment}
+ for {set l 0} {$l < $numcommits} {incr l} {
+ set id $lineid($l)
+ set info $commitinfo($id)
+ set doesmatch 0
+ foreach f $info ty $fldtypes {
+ if {$findloc != "All fields" && $findloc != $ty} {
+ continue
+ }
+ set matches [findmatches $f]
+ if {$matches == {}} continue
+ set doesmatch 1
+ if {$ty == "Headline"} {
+ markmatches $canv $l $f $linehtag($l) $matches $mainfont
+ } elseif {$ty == "Author"} {
+ markmatches $canv2 $l $f $linentag($l) $matches $namefont
+ } elseif {$ty == "Date"} {
+ markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
+ }
+ }
+ if {$doesmatch} {
+ lappend matchinglines $l
+ if {!$didsel && $l > $oldsel} {
+ findselectline $l
+ set didsel 1
+ }
+ }
+ }
+ if {$matchinglines == {}} {
+ bell
+ } elseif {!$didsel} {
+ findselectline [lindex $matchinglines 0]
+ }
+}
+
+proc findselectline {l} {
+ global findloc commentend ctext
+ selectline $l 1
+ if {$findloc == "All fields" || $findloc == "Comments"} {
+ # highlight the matches in the comments
+ set f [$ctext get 1.0 $commentend]
+ set matches [findmatches $f]
+ foreach match $matches {
+ set start [lindex $match 0]
+ set end [expr [lindex $match 1] + 1]
+ $ctext tag add found "1.0 + $start c" "1.0 + $end c"
+ }
+ }
+}
+
+proc findnext {restart} {
+ global matchinglines selectedline
+ if {![info exists matchinglines]} {
+ if {$restart} {
+ dofind
+ }
+ return
+ }
+ if {![info exists selectedline]} return
+ foreach l $matchinglines {
+ if {$l > $selectedline} {
+ findselectline $l
+ return
+ }
+ }
+ bell
+}
+
+proc findprev {} {
+ global matchinglines selectedline
+ if {![info exists matchinglines]} {
+ dofind
+ return
+ }
+ if {![info exists selectedline]} return
+ set prev {}
+ foreach l $matchinglines {
+ if {$l >= $selectedline} break
+ set prev $l
+ }
+ if {$prev != {}} {
+ findselectline $prev
+ } else {
+ bell
+ }
+}
+
+proc findlocchange {name ix op} {
+ global findloc findtype findtypemenu
+ if {$findloc == "Pickaxe"} {
+ set findtype Exact
+ set state disabled
+ } else {
+ set state normal
+ }
+ $findtypemenu entryconf 1 -state $state
+ $findtypemenu entryconf 2 -state $state
+}
+
+proc stopfindproc {{done 0}} {
+ global findprocpid findprocfile findids
+ global ctext findoldcursor phase maincursor textcursor
+ global findinprogress
+
+ catch {unset findids}
+ if {[info exists findprocpid]} {
+ if {!$done} {
+ catch {exec kill $findprocpid}
+ }
+ catch {close $findprocfile}
+ unset findprocpid
+ }
+ if {[info exists findinprogress]} {
+ unset findinprogress
+ if {$phase != "incrdraw"} {
+ . config -cursor $maincursor
+ settextcursor $textcursor
+ }
+ }
+}
+
+proc findpatches {} {
+ global findstring selectedline numcommits
+ global findprocpid findprocfile
+ global finddidsel ctext lineid findinprogress
+ global findinsertpos
+ global env
+
+ if {$numcommits == 0} return
+
+ # make a list of all the ids to search, starting at the one
+ # after the selected line (if any)
+ if {[info exists selectedline]} {
+ set l $selectedline
+ } else {
+ set l -1
+ }
+ set inputids {}
+ for {set i 0} {$i < $numcommits} {incr i} {
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ append inputids $lineid($l) "\n"
+ }
+
+ if {[catch {
+ set f [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree --stdin -s -r -S$findstring << $inputids] r]
+ } err]} {
+ error_popup "Error starting search process: $err"
+ return
+ }
+
+ set findinsertpos end
+ set findprocfile $f
+ set findprocpid [pid $f]
+ fconfigure $f -blocking 0
+ fileevent $f readable readfindproc
+ set finddidsel 0
+ . config -cursor watch
+ settextcursor watch
+ set findinprogress 1
+}
+
+proc readfindproc {} {
+ global findprocfile finddidsel
+ global idline matchinglines findinsertpos
+
+ set n [gets $findprocfile line]
+ if {$n < 0} {
+ if {[eof $findprocfile]} {
+ stopfindproc 1
+ if {!$finddidsel} {
+ bell
+ }
+ }
+ return
+ }
+ if {![regexp {^[0-9a-f]{12}} $line id]} {
+ error_popup "Can't parse git-diff-tree output: $line"
+ stopfindproc
+ return
+ }
+ if {![info exists idline($id)]} {
+ puts stderr "spurious id: $id"
+ return
+ }
+ set l $idline($id)
+ insertmatch $l $id
+}
+
+proc insertmatch {l id} {
+ global matchinglines findinsertpos finddidsel
+
+ if {$findinsertpos == "end"} {
+ if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
+ set matchinglines [linsert $matchinglines 0 $l]
+ set findinsertpos 1
+ } else {
+ lappend matchinglines $l
+ }
+ } else {
+ set matchinglines [linsert $matchinglines $findinsertpos $l]
+ incr findinsertpos
+ }
+ markheadline $l $id
+ if {!$finddidsel} {
+ findselectline $l
+ set finddidsel 1
+ }
+}
+
+proc findfiles {} {
+ global selectedline numcommits lineid ctext
+ global ffileline finddidsel parents nparents
+ global findinprogress findstartline findinsertpos
+ global treediffs fdiffids fdiffsneeded fdiffpos
+ global findmergefiles
+ global env
+
+ if {$numcommits == 0} return
+
+ if {[info exists selectedline]} {
+ set l [expr {$selectedline + 1}]
+ } else {
+ set l 0
+ }
+ set ffileline $l
+ set findstartline $l
+ set diffsneeded {}
+ set fdiffsneeded {}
+ while 1 {
+ set id $lineid($l)
+ if {$findmergefiles || $nparents($id) == 1} {
+ foreach p $parents($id) {
+ if {![info exists treediffs([list $id $p])]} {
+ append diffsneeded "$id $p\n"
+ lappend fdiffsneeded [list $id $p]
+ }
+ }
+ }
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ if {$l == $findstartline} break
+ }
+
+ # start off a git-diff-tree process if needed
+ if {$diffsneeded ne {}} {
+ if {[catch {
+ set df [open [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r --stdin << $diffsneeded] r]
+ } err ]} {
+ error_popup "Error starting search process: $err"
+ return
+ }
+ catch {unset fdiffids}
+ set fdiffpos 0
+ fconfigure $df -blocking 0
+ fileevent $df readable [list readfilediffs $df]
+ }
+
+ set finddidsel 0
+ set findinsertpos end
+ set id $lineid($l)
+ set p [lindex $parents($id) 0]
+ . config -cursor watch
+ settextcursor watch
+ set findinprogress 1
+ findcont [list $id $p]
+ update
+}
+
+proc readfilediffs {df} {
+ global findids fdiffids fdiffs
+
+ set n [gets $df line]
+ if {$n < 0} {
+ if {[eof $df]} {
+ donefilediff
+ if {[catch {close $df} err]} {
+ stopfindproc
+ bell
+ error_popup "Error in hg debug-diff-tree: $err"
+ } elseif {[info exists findids]} {
+ set ids $findids
+ stopfindproc
+ bell
+ error_popup "Couldn't find diffs for {$ids}"
+ }
+ }
+ return
+ }
+ if {[regexp {^([0-9a-f]{12}) \(from ([0-9a-f]{12})\)} $line match id p]} {
+ # start of a new string of diffs
+ donefilediff
+ set fdiffids [list $id $p]
+ set fdiffs {}
+ } elseif {[string match ":*" $line]} {
+ lappend fdiffs [lindex $line 5]
+ }
+}
+
+proc donefilediff {} {
+ global fdiffids fdiffs treediffs findids
+ global fdiffsneeded fdiffpos
+
+ if {[info exists fdiffids]} {
+ while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
+ && $fdiffpos < [llength $fdiffsneeded]} {
+ # git-diff-tree doesn't output anything for a commit
+ # which doesn't change anything
+ set nullids [lindex $fdiffsneeded $fdiffpos]
+ set treediffs($nullids) {}
+ if {[info exists findids] && $nullids eq $findids} {
+ unset findids
+ findcont $nullids
+ }
+ incr fdiffpos
+ }
+ incr fdiffpos
+
+ if {![info exists treediffs($fdiffids)]} {
+ set treediffs($fdiffids) $fdiffs
+ }
+ if {[info exists findids] && $fdiffids eq $findids} {
+ unset findids
+ findcont $fdiffids
+ }
+ }
+}
+
+proc findcont {ids} {
+ global findids treediffs parents nparents
+ global ffileline findstartline finddidsel
+ global lineid numcommits matchinglines findinprogress
+ global findmergefiles
+
+ set id [lindex $ids 0]
+ set p [lindex $ids 1]
+ set pi [lsearch -exact $parents($id) $p]
+ set l $ffileline
+ while 1 {
+ if {$findmergefiles || $nparents($id) == 1} {
+ if {![info exists treediffs($ids)]} {
+ set findids $ids
+ set ffileline $l
+ return
+ }
+ set doesmatch 0
+ foreach f $treediffs($ids) {
+ set x [findmatches $f]
+ if {$x != {}} {
+ set doesmatch 1
+ break
+ }
+ }
+ if {$doesmatch} {
+ insertmatch $l $id
+ set pi $nparents($id)
+ }
+ } else {
+ set pi $nparents($id)
+ }
+ if {[incr pi] >= $nparents($id)} {
+ set pi 0
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ if {$l == $findstartline} break
+ set id $lineid($l)
+ }
+ set p [lindex $parents($id) $pi]
+ set ids [list $id $p]
+ }
+ stopfindproc
+ if {!$finddidsel} {
+ bell
+ }
+}
+
+# mark a commit as matching by putting a yellow background
+# behind the headline
+proc markheadline {l id} {
+ global canv mainfont linehtag commitinfo
+
+ set bbox [$canv bbox $linehtag($l)]
+ set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
+ $canv lower $t
+}
+
+# mark the bits of a headline, author or date that match a find string
+proc markmatches {canv l str tag matches font} {
+ set bbox [$canv bbox $tag]
+ set x0 [lindex $bbox 0]
+ set y0 [lindex $bbox 1]
+ set y1 [lindex $bbox 3]
+ foreach match $matches {
+ set start [lindex $match 0]
+ set end [lindex $match 1]
+ if {$start > $end} continue
+ set xoff [font measure $font [string range $str 0 [expr $start-1]]]
+ set xlen [font measure $font [string range $str 0 [expr $end]]]
+ set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
+ -outline {} -tags matches -fill yellow]
+ $canv lower $t
+ }
+}
+
+proc unmarkmatches {} {
+ global matchinglines findids
+ allcanvs delete matches
+ catch {unset matchinglines}
+ catch {unset findids}
+}
+
+proc selcanvline {w x y} {
+ global canv canvy0 ctext linespc
+ global lineid linehtag linentag linedtag rowtextx
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ if {$ymax == {}} return
+ set yfrac [lindex [$canv yview] 0]
+ set y [expr {$y + $yfrac * $ymax}]
+ set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
+ if {$l < 0} {
+ set l 0
+ }
+ if {$w eq $canv} {
+ if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
+ }
+ unmarkmatches
+ selectline $l 1
+}
+
+proc commit_descriptor {p} {
+ global commitinfo
+ set l "..."
+ if {[info exists commitinfo($p)]} {
+ set l [lindex $commitinfo($p) 0]
+ set r [lindex $commitinfo($p) 6]
+ }
+ return "$r:$p ($l)"
+}
+
+# append some text to the ctext widget, and make any SHA1 ID
+# that we know about be a clickable link.
+proc appendwithlinks {text} {
+ global ctext idline linknum
+
+ set start [$ctext index "end - 1c"]
+ $ctext insert end $text
+ $ctext insert end "\n"
+ set links [regexp -indices -all -inline {[0-9a-f]{12}} $text]
+ foreach l $links {
+ set s [lindex $l 0]
+ set e [lindex $l 1]
+ set linkid [string range $text $s $e]
+ if {![info exists idline($linkid)]} continue
+ incr e
+ $ctext tag add link "$start + $s c" "$start + $e c"
+ $ctext tag add link$linknum "$start + $s c" "$start + $e c"
+ $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1]
+ incr linknum
+ }
+ $ctext tag conf link -foreground blue -underline 1
+ $ctext tag bind link <Enter> { %W configure -cursor hand2 }
+ $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+}
+
+proc selectline {l isnew} {
+ global canv canv2 canv3 ctext commitinfo selectedline
+ global lineid linehtag linentag linedtag
+ global canvy0 linespc parents nparents children
+ global cflist currentid sha1entry
+ global commentend idtags idbookmarks idline linknum
+
+ $canv delete hover
+ normalline
+ if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
+ $canv delete secsel
+ set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
+ -tags secsel -fill [$canv cget -selectbackground]]
+ $canv lower $t
+ $canv2 delete secsel
+ set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
+ -tags secsel -fill [$canv2 cget -selectbackground]]
+ $canv2 lower $t
+ $canv3 delete secsel
+ set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
+ -tags secsel -fill [$canv3 cget -selectbackground]]
+ $canv3 lower $t
+ set y [expr {$canvy0 + $l * $linespc}]
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ set ytop [expr {$y - $linespc - 1}]
+ set ybot [expr {$y + $linespc + 1}]
+ set wnow [$canv yview]
+ set wtop [expr [lindex $wnow 0] * $ymax]
+ set wbot [expr [lindex $wnow 1] * $ymax]
+ set wh [expr {$wbot - $wtop}]
+ set newtop $wtop
+ if {$ytop < $wtop} {
+ if {$ybot < $wtop} {
+ set newtop [expr {$y - $wh / 2.0}]
+ } else {
+ set newtop $ytop
+ if {$newtop > $wtop - $linespc} {
+ set newtop [expr {$wtop - $linespc}]
+ }
+ }
+ } elseif {$ybot > $wbot} {
+ if {$ytop > $wbot} {
+ set newtop [expr {$y - $wh / 2.0}]
+ } else {
+ set newtop [expr {$ybot - $wh}]
+ if {$newtop < $wtop + $linespc} {
+ set newtop [expr {$wtop + $linespc}]
+ }
+ }
+ }
+ if {$newtop != $wtop} {
+ if {$newtop < 0} {
+ set newtop 0
+ }
+ allcanvs yview moveto [expr $newtop * 1.0 / $ymax]
+ }
+
+ if {$isnew} {
+ addtohistory [list selectline $l 0]
+ }
+
+ set selectedline $l
+
+ set id $lineid($l)
+ set currentid $id
+ $sha1entry delete 0 end
+ $sha1entry insert 0 $id
+ $sha1entry selection from 0
+ $sha1entry selection to end
+
+ $ctext conf -state normal
+ $ctext delete 0.0 end
+ set linknum 0
+ $ctext mark set fmark.0 0.0
+ $ctext mark gravity fmark.0 left
+ set info $commitinfo($id)
+ $ctext insert end "Revision: [lindex $info 6]\n"
+ if {[llength [lindex $info 7]] > 0} {
+ $ctext insert end "Branch: [lindex $info 7]\n"
+ }
+ $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
+ $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
+ if {[info exists idbookmarks($id)]} {
+ $ctext insert end "Bookmarks:"
+ foreach bookmark $idbookmarks($id) {
+ $ctext insert end " $bookmark"
+ }
+ $ctext insert end "\n"
+ }
+
+ if {[info exists idtags($id)]} {
+ $ctext insert end "Tags:"
+ foreach tag $idtags($id) {
+ $ctext insert end " $tag"
+ }
+ $ctext insert end "\n"
+ }
+
+ set comment {}
+ if {[info exists parents($id)]} {
+ foreach p $parents($id) {
+ append comment "Parent: [commit_descriptor $p]\n"
+ }
+ }
+ if {[info exists children($id)]} {
+ foreach c $children($id) {
+ append comment "Child: [commit_descriptor $c]\n"
+ }
+ }
+ append comment "\n"
+ append comment [lindex $info 5]
+
+ # make anything that looks like a SHA1 ID be a clickable link
+ appendwithlinks $comment
+
+ $ctext tag delete Comments
+ $ctext tag remove found 1.0 end
+ $ctext conf -state disabled
+ set commentend [$ctext index "end - 1c"]
+
+ $cflist delete 0 end
+ $cflist insert end "Comments"
+ if {$nparents($id) <= 1} {
+ set parent "null"
+ if {$nparents($id) == 1} {
+ set parent $parents($id)
+ }
+ startdiff [concat $id $parent]
+ } elseif {$nparents($id) > 1} {
+ mergediff $id
+ }
+}
+
+proc selnextline {dir} {
+ global selectedline
+ if {![info exists selectedline]} return
+ set l [expr $selectedline + $dir]
+ unmarkmatches
+ selectline $l 1
+}
+
+proc unselectline {} {
+ global selectedline
+
+ catch {unset selectedline}
+ allcanvs delete secsel
+}
+
+proc addtohistory {cmd} {
+ global history historyindex
+
+ if {$historyindex > 0
+ && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
+ return
+ }
+
+ if {$historyindex < [llength $history]} {
+ set history [lreplace $history $historyindex end $cmd]
+ } else {
+ lappend history $cmd
+ }
+ incr historyindex
+ if {$historyindex > 1} {
+ .ctop.top.bar.leftbut conf -state normal
+ } else {
+ .ctop.top.bar.leftbut conf -state disabled
+ }
+ .ctop.top.bar.rightbut conf -state disabled
+}
+
+proc goback {} {
+ global history historyindex
+
+ if {$historyindex > 1} {
+ incr historyindex -1
+ set cmd [lindex $history [expr {$historyindex - 1}]]
+ eval $cmd
+ .ctop.top.bar.rightbut conf -state normal
+ }
+ if {$historyindex <= 1} {
+ .ctop.top.bar.leftbut conf -state disabled
+ }
+}
+
+proc goforw {} {
+ global history historyindex
+
+ if {$historyindex < [llength $history]} {
+ set cmd [lindex $history $historyindex]
+ incr historyindex
+ eval $cmd
+ .ctop.top.bar.leftbut conf -state normal
+ }
+ if {$historyindex >= [llength $history]} {
+ .ctop.top.bar.rightbut conf -state disabled
+ }
+}
+
+proc mergediff {id} {
+ global parents diffmergeid diffmergegca mergefilelist diffpindex
+
+ set diffmergeid $id
+ set diffpindex -1
+ set diffmergegca [findgca $parents($id)]
+ if {[info exists mergefilelist($id)]} {
+ if {$mergefilelist($id) ne {}} {
+ showmergediff
+ }
+ } else {
+ contmergediff {}
+ }
+}
+
+proc findgca {ids} {
+ global env
+ set gca {}
+ foreach id $ids {
+ if {$gca eq {}} {
+ set gca $id
+ } else {
+ if {[catch {
+ set gca [exec $env(HG) --config ui.report_untrusted=false debug-merge-base $gca $id]
+ } err]} {
+ return {}
+ }
+ }
+ }
+ return $gca
+}
+
+proc contmergediff {ids} {
+ global diffmergeid diffpindex parents nparents diffmergegca
+ global treediffs mergefilelist diffids treepending
+
+ # diff the child against each of the parents, and diff
+ # each of the parents against the GCA.
+ while 1 {
+ if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} {
+ set ids [list [lindex $ids 1] $diffmergegca]
+ } else {
+ if {[incr diffpindex] >= $nparents($diffmergeid)} break
+ set p [lindex $parents($diffmergeid) $diffpindex]
+ set ids [list $diffmergeid $p]
+ }
+ if {![info exists treediffs($ids)]} {
+ set diffids $ids
+ if {![info exists treepending]} {
+ gettreediffs $ids
+ }
+ return
+ }
+ }
+
+ # If a file in some parent is different from the child and also
+ # different from the GCA, then it's interesting.
+ # If we don't have a GCA, then a file is interesting if it is
+ # different from the child in all the parents.
+ if {$diffmergegca ne {}} {
+ set files {}
+ foreach p $parents($diffmergeid) {
+ set gcadiffs $treediffs([list $p $diffmergegca])
+ foreach f $treediffs([list $diffmergeid $p]) {
+ if {[lsearch -exact $files $f] < 0
+ && [lsearch -exact $gcadiffs $f] >= 0} {
+ lappend files $f
+ }
+ }
+ }
+ set files [lsort $files]
+ } else {
+ set p [lindex $parents($diffmergeid) 0]
+ set files $treediffs([list $diffmergeid $p])
+ for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} {
+ set p [lindex $parents($diffmergeid) $i]
+ set df $treediffs([list $diffmergeid $p])
+ set nf {}
+ foreach f $files {
+ if {[lsearch -exact $df $f] >= 0} {
+ lappend nf $f
+ }
+ }
+ set files $nf
+ }
+ }
+
+ set mergefilelist($diffmergeid) $files
+ if {$files ne {}} {
+ showmergediff
+ }
+}
+
+proc showmergediff {} {
+ global cflist diffmergeid mergefilelist parents
+ global diffopts diffinhunk currentfile currenthunk filelines
+ global diffblocked groupfilelast mergefds groupfilenum grouphunks
+ global env
+
+ set files $mergefilelist($diffmergeid)
+ foreach f $files {
+ $cflist insert end $f
+ }
+ set env(GIT_DIFF_OPTS) $diffopts
+ set flist {}
+ catch {unset currentfile}
+ catch {unset currenthunk}
+ catch {unset filelines}
+ catch {unset groupfilenum}
+ catch {unset grouphunks}
+ set groupfilelast -1
+ foreach p $parents($diffmergeid) {
+ set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $p $diffmergeid]
+ set cmd [concat $cmd $mergefilelist($diffmergeid)]
+ if {[catch {set f [open $cmd r]} err]} {
+ error_popup "Error getting diffs: $err"
+ foreach f $flist {
+ catch {close $f}
+ }
+ return
+ }
+ lappend flist $f
+ set ids [list $diffmergeid $p]
+ set mergefds($ids) $f
+ set diffinhunk($ids) 0
+ set diffblocked($ids) 0
+ fconfigure $f -blocking 0
+ fileevent $f readable [list getmergediffline $f $ids $diffmergeid]
+ }
+}
+
+proc getmergediffline {f ids id} {
+ global diffmergeid diffinhunk diffoldlines diffnewlines
+ global currentfile currenthunk
+ global diffoldstart diffnewstart diffoldlno diffnewlno
+ global diffblocked mergefilelist
+ global noldlines nnewlines difflcounts filelines
+
+ set n [gets $f line]
+ if {$n < 0} {
+ if {![eof $f]} return
+ }
+
+ if {!([info exists diffmergeid] && $diffmergeid == $id)} {
+ if {$n < 0} {
+ close $f
+ }
+ return
+ }
+
+ if {$diffinhunk($ids) != 0} {
+ set fi $currentfile($ids)
+ if {$n > 0 && [regexp {^[-+ \\]} $line match]} {
+ # continuing an existing hunk
+ set line [string range $line 1 end]
+ set p [lindex $ids 1]
+ if {$match eq "-" || $match eq " "} {
+ set filelines($p,$fi,$diffoldlno($ids)) $line
+ incr diffoldlno($ids)
+ }
+ if {$match eq "+" || $match eq " "} {
+ set filelines($id,$fi,$diffnewlno($ids)) $line
+ incr diffnewlno($ids)
+ }
+ if {$match eq " "} {
+ if {$diffinhunk($ids) == 2} {
+ lappend difflcounts($ids) \
+ [list $noldlines($ids) $nnewlines($ids)]
+ set noldlines($ids) 0
+ set diffinhunk($ids) 1
+ }
+ incr noldlines($ids)
+ } elseif {$match eq "-" || $match eq "+"} {
+ if {$diffinhunk($ids) == 1} {
+ lappend difflcounts($ids) [list $noldlines($ids)]
+ set noldlines($ids) 0
+ set nnewlines($ids) 0
+ set diffinhunk($ids) 2
+ }
+ if {$match eq "-"} {
+ incr noldlines($ids)
+ } else {
+ incr nnewlines($ids)
+ }
+ }
+ # and if it's \ No newline at end of line, then what?
+ return
+ }
+ # end of a hunk
+ if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} {
+ lappend difflcounts($ids) [list $noldlines($ids)]
+ } elseif {$diffinhunk($ids) == 2
+ && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} {
+ lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)]
+ }
+ set currenthunk($ids) [list $currentfile($ids) \
+ $diffoldstart($ids) $diffnewstart($ids) \
+ $diffoldlno($ids) $diffnewlno($ids) \
+ $difflcounts($ids)]
+ set diffinhunk($ids) 0
+ # -1 = need to block, 0 = unblocked, 1 = is blocked
+ set diffblocked($ids) -1
+ processhunks
+ if {$diffblocked($ids) == -1} {
+ fileevent $f readable {}
+ set diffblocked($ids) 1
+ }
+ }
+
+ if {$n < 0} {
+ # eof
+ if {!$diffblocked($ids)} {
+ close $f
+ set currentfile($ids) [llength $mergefilelist($diffmergeid)]
+ set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}]
+ processhunks
+ }
+ } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} {
+ # start of a new file
+ set currentfile($ids) \
+ [lsearch -exact $mergefilelist($diffmergeid) $fname]
+ } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
+ $line match f1l f1c f2l f2c rest]} {
+ if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} {
+ # start of a new hunk
+ if {$f1l == 0 && $f1c == 0} {
+ set f1l 1
+ }
+ if {$f2l == 0 && $f2c == 0} {
+ set f2l 1
+ }
+ set diffinhunk($ids) 1
+ set diffoldstart($ids) $f1l
+ set diffnewstart($ids) $f2l
+ set diffoldlno($ids) $f1l
+ set diffnewlno($ids) $f2l
+ set difflcounts($ids) {}
+ set noldlines($ids) 0
+ set nnewlines($ids) 0
+ }
+ }
+}
+
+proc processhunks {} {
+ global diffmergeid parents nparents currenthunk
+ global mergefilelist diffblocked mergefds
+ global grouphunks grouplinestart grouplineend groupfilenum
+
+ set nfiles [llength $mergefilelist($diffmergeid)]
+ while 1 {
+ set fi $nfiles
+ set lno 0
+ # look for the earliest hunk
+ foreach p $parents($diffmergeid) {
+ set ids [list $diffmergeid $p]
+ if {![info exists currenthunk($ids)]} return
+ set i [lindex $currenthunk($ids) 0]
+ set l [lindex $currenthunk($ids) 2]
+ if {$i < $fi || ($i == $fi && $l < $lno)} {
+ set fi $i
+ set lno $l
+ set pi $p
+ }
+ }
+
+ if {$fi < $nfiles} {
+ set ids [list $diffmergeid $pi]
+ set hunk $currenthunk($ids)
+ unset currenthunk($ids)
+ if {$diffblocked($ids) > 0} {
+ fileevent $mergefds($ids) readable \
+ [list getmergediffline $mergefds($ids) $ids $diffmergeid]
+ }
+ set diffblocked($ids) 0
+
+ if {[info exists groupfilenum] && $groupfilenum == $fi
+ && $lno <= $grouplineend} {
+ # add this hunk to the pending group
+ lappend grouphunks($pi) $hunk
+ set endln [lindex $hunk 4]
+ if {$endln > $grouplineend} {
+ set grouplineend $endln
+ }
+ continue
+ }
+ }
+
+ # succeeding stuff doesn't belong in this group, so
+ # process the group now
+ if {[info exists groupfilenum]} {
+ processgroup
+ unset groupfilenum
+ unset grouphunks
+ }
+
+ if {$fi >= $nfiles} break
+
+ # start a new group
+ set groupfilenum $fi
+ set grouphunks($pi) [list $hunk]
+ set grouplinestart $lno
+ set grouplineend [lindex $hunk 4]
+ }
+}
+
+proc processgroup {} {
+ global groupfilelast groupfilenum difffilestart
+ global mergefilelist diffmergeid ctext filelines
+ global parents diffmergeid diffoffset
+ global grouphunks grouplinestart grouplineend nparents
+ global mergemax
+
+ $ctext conf -state normal
+ set id $diffmergeid
+ set f $groupfilenum
+ if {$groupfilelast != $f} {
+ $ctext insert end "\n"
+ set here [$ctext index "end - 1c"]
+ set difffilestart($f) $here
+ set mark fmark.[expr {$f + 1}]
+ $ctext mark set $mark $here
+ $ctext mark gravity $mark left
+ set header [lindex $mergefilelist($id) $f]
+ set l [expr {(78 - [string length $header]) / 2}]
+ set pad [string range "----------------------------------------" 1 $l]
+ $ctext insert end "$pad $header $pad\n" filesep
+ set groupfilelast $f
+ foreach p $parents($id) {
+ set diffoffset($p) 0
+ }
+ }
+
+ $ctext insert end "@@" msep
+ set nlines [expr {$grouplineend - $grouplinestart}]
+ set events {}
+ set pnum 0
+ foreach p $parents($id) {
+ set startline [expr {$grouplinestart + $diffoffset($p)}]
+ set ol $startline
+ set nl $grouplinestart
+ if {[info exists grouphunks($p)]} {
+ foreach h $grouphunks($p) {
+ set l [lindex $h 2]
+ if {$nl < $l} {
+ for {} {$nl < $l} {incr nl} {
+ set filelines($p,$f,$ol) $filelines($id,$f,$nl)
+ incr ol
+ }
+ }
+ foreach chunk [lindex $h 5] {
+ if {[llength $chunk] == 2} {
+ set olc [lindex $chunk 0]
+ set nlc [lindex $chunk 1]
+ set nnl [expr {$nl + $nlc}]
+ lappend events [list $nl $nnl $pnum $olc $nlc]
+ incr ol $olc
+ set nl $nnl
+ } else {
+ incr ol [lindex $chunk 0]
+ incr nl [lindex $chunk 0]
+ }
+ }
+ }
+ }
+ if {$nl < $grouplineend} {
+ for {} {$nl < $grouplineend} {incr nl} {
+ set filelines($p,$f,$ol) $filelines($id,$f,$nl)
+ incr ol
+ }
+ }
+ set nlines [expr {$ol - $startline}]
+ $ctext insert end " -$startline,$nlines" msep
+ incr pnum
+ }
+
+ set nlines [expr {$grouplineend - $grouplinestart}]
+ $ctext insert end " +$grouplinestart,$nlines @@\n" msep
+
+ set events [lsort -integer -index 0 $events]
+ set nevents [llength $events]
+ set nmerge $nparents($diffmergeid)
+ set l $grouplinestart
+ for {set i 0} {$i < $nevents} {set i $j} {
+ set nl [lindex $events $i 0]
+ while {$l < $nl} {
+ $ctext insert end " $filelines($id,$f,$l)\n"
+ incr l
+ }
+ set e [lindex $events $i]
+ set enl [lindex $e 1]
+ set j $i
+ set active {}
+ while 1 {
+ set pnum [lindex $e 2]
+ set olc [lindex $e 3]
+ set nlc [lindex $e 4]
+ if {![info exists delta($pnum)]} {
+ set delta($pnum) [expr {$olc - $nlc}]
+ lappend active $pnum
+ } else {
+ incr delta($pnum) [expr {$olc - $nlc}]
+ }
+ if {[incr j] >= $nevents} break
+ set e [lindex $events $j]
+ if {[lindex $e 0] >= $enl} break
+ if {[lindex $e 1] > $enl} {
+ set enl [lindex $e 1]
+ }
+ }
+ set nlc [expr {$enl - $l}]
+ set ncol mresult
+ set bestpn -1
+ if {[llength $active] == $nmerge - 1} {
+ # no diff for one of the parents, i.e. it's identical
+ for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
+ if {![info exists delta($pnum)]} {
+ if {$pnum < $mergemax} {
+ lappend ncol m$pnum
+ } else {
+ lappend ncol mmax
+ }
+ break
+ }
+ }
+ } elseif {[llength $active] == $nmerge} {
+ # all parents are different, see if one is very similar
+ set bestsim 30
+ for {set pnum 0} {$pnum < $nmerge} {incr pnum} {
+ set sim [similarity $pnum $l $nlc $f \
+ [lrange $events $i [expr {$j-1}]]]
+ if {$sim > $bestsim} {
+ set bestsim $sim
+ set bestpn $pnum
+ }
+ }
+ if {$bestpn >= 0} {
+ lappend ncol m$bestpn
+ }
+ }
+ set pnum -1
+ foreach p $parents($id) {
+ incr pnum
+ if {![info exists delta($pnum)] || $pnum == $bestpn} continue
+ set olc [expr {$nlc + $delta($pnum)}]
+ set ol [expr {$l + $diffoffset($p)}]
+ incr diffoffset($p) $delta($pnum)
+ unset delta($pnum)
+ for {} {$olc > 0} {incr olc -1} {
+ $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum
+ incr ol
+ }
+ }
+ set endl [expr {$l + $nlc}]
+ if {$bestpn >= 0} {
+ # show this pretty much as a normal diff
+ set p [lindex $parents($id) $bestpn]
+ set ol [expr {$l + $diffoffset($p)}]
+ incr diffoffset($p) $delta($bestpn)
+ unset delta($bestpn)
+ for {set k $i} {$k < $j} {incr k} {
+ set e [lindex $events $k]
+ if {[lindex $e 2] != $bestpn} continue
+ set nl [lindex $e 0]
+ set ol [expr {$ol + $nl - $l}]
+ for {} {$l < $nl} {incr l} {
+ $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
+ }
+ set c [lindex $e 3]
+ for {} {$c > 0} {incr c -1} {
+ $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn
+ incr ol
+ }
+ set nl [lindex $e 1]
+ for {} {$l < $nl} {incr l} {
+ $ctext insert end "+$filelines($id,$f,$l)\n" mresult
+ }
+ }
+ }
+ for {} {$l < $endl} {incr l} {
+ $ctext insert end "+$filelines($id,$f,$l)\n" $ncol
+ }
+ }
+ while {$l < $grouplineend} {
+ $ctext insert end " $filelines($id,$f,$l)\n"
+ incr l
+ }
+ $ctext conf -state disabled
+}
+
+proc similarity {pnum l nlc f events} {
+ global diffmergeid parents diffoffset filelines
+
+ set id $diffmergeid
+ set p [lindex $parents($id) $pnum]
+ set ol [expr {$l + $diffoffset($p)}]
+ set endl [expr {$l + $nlc}]
+ set same 0
+ set diff 0
+ foreach e $events {
+ if {[lindex $e 2] != $pnum} continue
+ set nl [lindex $e 0]
+ set ol [expr {$ol + $nl - $l}]
+ for {} {$l < $nl} {incr l} {
+ incr same [string length $filelines($id,$f,$l)]
+ incr same
+ }
+ set oc [lindex $e 3]
+ for {} {$oc > 0} {incr oc -1} {
+ incr diff [string length $filelines($p,$f,$ol)]
+ incr diff
+ incr ol
+ }
+ set nl [lindex $e 1]
+ for {} {$l < $nl} {incr l} {
+ incr diff [string length $filelines($id,$f,$l)]
+ incr diff
+ }
+ }
+ for {} {$l < $endl} {incr l} {
+ incr same [string length $filelines($id,$f,$l)]
+ incr same
+ }
+ if {$same == 0} {
+ return 0
+ }
+ return [expr {200 * $same / (2 * $same + $diff)}]
+}
+
+proc startdiff {ids} {
+ global treediffs diffids treepending diffmergeid
+
+ set diffids $ids
+ catch {unset diffmergeid}
+ if {![info exists treediffs($ids)]} {
+ if {![info exists treepending]} {
+ gettreediffs $ids
+ }
+ } else {
+ addtocflist $ids
+ }
+}
+
+proc addtocflist {ids} {
+ global treediffs cflist
+ foreach f $treediffs($ids) {
+ $cflist insert end $f
+ }
+ getblobdiffs $ids
+}
+
+proc gettreediffs {ids} {
+ global treediff parents treepending env
+ set treepending $ids
+ set treediff {}
+ set id [lindex $ids 0]
+ set p [lindex $ids 1]
+ if [catch {set gdtf [open "|{$env(HG)} --config ui.report_untrusted=false debug-diff-tree -r $p $id" r]}] return
+ fconfigure $gdtf -blocking 0
+ fileevent $gdtf readable [list gettreediffline $gdtf $ids]
+}
+
+proc gettreediffline {gdtf ids} {
+ global treediff treediffs treepending diffids diffmergeid
+
+ set n [gets $gdtf line]
+ if {$n < 0} {
+ if {![eof $gdtf]} return
+ close $gdtf
+ set treediffs($ids) $treediff
+ unset treepending
+ if {$ids != $diffids} {
+ gettreediffs $diffids
+ } else {
+ if {[info exists diffmergeid]} {
+ contmergediff $ids
+ } else {
+ addtocflist $ids
+ }
+ }
+ return
+ }
+ set tab1 [expr [string first "\t" $line] + 1]
+ set tab2 [expr [string first "\t" $line $tab1] - 1]
+ set file [string range $line $tab1 $tab2]
+ lappend treediff $file
+}
+
+proc getblobdiffs {ids} {
+ global diffopts blobdifffd diffids env curdifftag curtagstart
+ global difffilestart nextupdate diffinhdr treediffs
+
+ set id [lindex $ids 0]
+ set p [lindex $ids 1]
+ set env(GIT_DIFF_OPTS) $diffopts
+ set cmd [list | $env(HG) --config ui.report_untrusted=false debug-diff-tree -r -p -C $p $id]
+ if {[catch {set bdf [open $cmd r]} err]} {
+ puts "error getting diffs: $err"
+ return
+ }
+ set diffinhdr 0
+ fconfigure $bdf -blocking 0
+ set blobdifffd($ids) $bdf
+ set curdifftag Comments
+ set curtagstart 0.0
+ catch {unset difffilestart}
+ fileevent $bdf readable [list getblobdiffline $bdf $diffids]
+ set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+}
+
+proc getblobdiffline {bdf ids} {
+ global diffids blobdifffd ctext curdifftag curtagstart
+ global diffnexthead diffnextnote difffilestart
+ global nextupdate diffinhdr treediffs
+ global gaudydiff
+
+ set n [gets $bdf line]
+ if {$n < 0} {
+ if {[eof $bdf]} {
+ close $bdf
+ if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
+ $ctext tag add $curdifftag $curtagstart end
+ }
+ }
+ return
+ }
+ if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
+ return
+ }
+ regsub -all "\r" $line "" line
+ $ctext conf -state normal
+ if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
+ # start of a new file
+ $ctext insert end "\n"
+ $ctext tag add $curdifftag $curtagstart end
+ set curtagstart [$ctext index "end - 1c"]
+ set header $newname
+ set here [$ctext index "end - 1c"]
+ set i [lsearch -exact $treediffs($diffids) $fname]
+ if {$i >= 0} {
+ set difffilestart($i) $here
+ incr i
+ $ctext mark set fmark.$i $here
+ $ctext mark gravity fmark.$i left
+ }
+ if {$newname != $fname} {
+ set i [lsearch -exact $treediffs($diffids) $newname]
+ if {$i >= 0} {
+ set difffilestart($i) $here
+ incr i
+ $ctext mark set fmark.$i $here
+ $ctext mark gravity fmark.$i left
+ }
+ }
+ set curdifftag "f:$fname"
+ $ctext tag delete $curdifftag
+ set l [expr {(78 - [string length $header]) / 2}]
+ set pad [string range "----------------------------------------" 1 $l]
+ $ctext insert end "$pad $header $pad\n" filesep
+ set diffinhdr 1
+ } elseif {[regexp {^(---|\+\+\+) } $line] && $diffinhdr} {
+ set diffinhdr 1
+ } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
+ $line match f1l f1c f2l f2c rest]} {
+ if {$gaudydiff} {
+ $ctext insert end "\t" hunksep
+ $ctext insert end " $f1l " d0 " $f2l " d1
+ $ctext insert end " $rest \n" hunksep
+ } else {
+ $ctext insert end "$line\n" hunksep
+ }
+ set diffinhdr 0
+ } else {
+ set x [string range $line 0 0]
+ if {$x == "-" || $x == "+"} {
+ set tag [expr {$x == "+"}]
+ if {$gaudydiff} {
+ set line [string range $line 1 end]
+ }
+ $ctext insert end "$line\n" d$tag
+ } elseif {$x == " "} {
+ if {$gaudydiff} {
+ set line [string range $line 1 end]
+ }
+ $ctext insert end "$line\n"
+ } elseif {$diffinhdr || $x == "\\"} {
+ # e.g. "\ No newline at end of file"
+ $ctext insert end "$line\n" filesep
+ } elseif {$line != ""} {
+ # Something else we don't recognize
+ if {$curdifftag != "Comments"} {
+ $ctext insert end "\n"
+ $ctext tag add $curdifftag $curtagstart end
+ set curtagstart [$ctext index "end - 1c"]
+ set curdifftag Comments
+ }
+ $ctext insert end "$line\n" filesep
+ }
+ }
+ $ctext conf -state disabled
+ if {[clock clicks -milliseconds] >= $nextupdate} {
+ incr nextupdate 100
+ fileevent $bdf readable {}
+ update
+ fileevent $bdf readable "getblobdiffline $bdf {$ids}"
+ }
+}
+
+proc nextfile {} {
+ global difffilestart ctext
+ set here [$ctext index @0,0]
+ for {set i 0} {[info exists difffilestart($i)]} {incr i} {
+ if {[$ctext compare $difffilestart($i) > $here]} {
+ if {![info exists pos]
+ || [$ctext compare $difffilestart($i) < $pos]} {
+ set pos $difffilestart($i)
+ }
+ }
+ }
+ if {[info exists pos]} {
+ $ctext yview $pos
+ }
+}
+
+proc listboxsel {} {
+ global ctext cflist currentid
+ if {![info exists currentid]} return
+ set sel [lsort [$cflist curselection]]
+ if {$sel eq {}} return
+ set first [lindex $sel 0]
+ catch {$ctext yview fmark.$first}
+}
+
+proc setcoords {} {
+ global linespc charspc canvx0 canvy0 mainfont
+ global xspc1 xspc2 lthickness
+
+ set linespc [font metrics $mainfont -linespace]
+ set charspc [font measure $mainfont "m"]
+ set canvy0 [expr 3 + 0.5 * $linespc]
+ set canvx0 [expr 3 + 0.5 * $linespc]
+ set lthickness [expr {int($linespc / 9) + 1}]
+ set xspc1(0) $linespc
+ set xspc2 $linespc
+}
+
+proc redisplay {} {
+ global stopped redisplaying phase
+ if {$stopped > 1} return
+ if {$phase == "getcommits"} return
+ set redisplaying 1
+ if {$phase == "drawgraph" || $phase == "incrdraw"} {
+ set stopped 1
+ } else {
+ drawgraph
+ }
+}
+
+proc incrfont {inc} {
+ global mainfont namefont textfont ctext canv phase
+ global stopped entries curidfont
+ unmarkmatches
+ set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
+ set curidfont [lreplace $curidfont 1 1 [expr {[lindex $curidfont 1] + $inc}]]
+ set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
+ set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
+ setcoords
+ $ctext conf -font $textfont
+ $ctext tag conf filesep -font [concat $textfont bold]
+ foreach e $entries {
+ $e conf -font $mainfont
+ }
+ if {$phase == "getcommits"} {
+ $canv itemconf textitems -font $mainfont
+ }
+ redisplay
+}
+
+proc clearsha1 {} {
+ global sha1entry sha1string
+ if {[string length $sha1string] == 40} {
+ $sha1entry delete 0 end
+ }
+}
+
+proc sha1change {n1 n2 op} {
+ global sha1string currentid sha1but
+ if {$sha1string == {}
+ || ([info exists currentid] && $sha1string == $currentid)} {
+ set state disabled
+ } else {
+ set state normal
+ }
+ if {[$sha1but cget -state] == $state} return
+ if {$state == "normal"} {
+ $sha1but conf -state normal -relief raised -text "Goto: "
+ } else {
+ $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
+ }
+}
+
+proc gotocommit {} {
+ global sha1string currentid idline tagids
+ global lineid numcommits
+
+ if {$sha1string == {}
+ || ([info exists currentid] && $sha1string == $currentid)} return
+ if {[info exists tagids($sha1string)]} {
+ set id $tagids($sha1string)
+ } else {
+ set id [string tolower $sha1string]
+ if {[regexp {^[0-9a-f]{4,39}$} $id]} {
+ set matches {}
+ for {set l 0} {$l < $numcommits} {incr l} {
+ if {[string match $id* $lineid($l)]} {
+ lappend matches $lineid($l)
+ }
+ }
+ if {$matches ne {}} {
+ if {[llength $matches] > 1} {
+ error_popup "Short SHA1 id $id is ambiguous"
+ return
+ }
+ set id [lindex $matches 0]
+ }
+ }
+ }
+ if {[info exists idline($id)]} {
+ selectline $idline($id) 1
+ return
+ }
+ if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
+ set type "SHA1 id"
+ } else {
+ set type "Tag"
+ }
+ error_popup "$type $sha1string is not known"
+}
+
+proc lineenter {x y id} {
+ global hoverx hovery hoverid hovertimer
+ global commitinfo canv
+
+ if {![info exists commitinfo($id)]} return
+ set hoverx $x
+ set hovery $y
+ set hoverid $id
+ if {[info exists hovertimer]} {
+ after cancel $hovertimer
+ }
+ set hovertimer [after 500 linehover]
+ $canv delete hover
+}
+
+proc linemotion {x y id} {
+ global hoverx hovery hoverid hovertimer
+
+ if {[info exists hoverid] && $id == $hoverid} {
+ set hoverx $x
+ set hovery $y
+ if {[info exists hovertimer]} {
+ after cancel $hovertimer
+ }
+ set hovertimer [after 500 linehover]
+ }
+}
+
+proc lineleave {id} {
+ global hoverid hovertimer canv
+
+ if {[info exists hoverid] && $id == $hoverid} {
+ $canv delete hover
+ if {[info exists hovertimer]} {
+ after cancel $hovertimer
+ unset hovertimer
+ }
+ unset hoverid
+ }
+}
+
+proc linehover {} {
+ global hoverx hovery hoverid hovertimer
+ global canv linespc lthickness
+ global commitinfo mainfont
+
+ set text [lindex $commitinfo($hoverid) 0]
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ if {$ymax == {}} return
+ set yfrac [lindex [$canv yview] 0]
+ set x [expr {$hoverx + 2 * $linespc}]
+ set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
+ set x0 [expr {$x - 2 * $lthickness}]
+ set y0 [expr {$y - 2 * $lthickness}]
+ set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
+ set y1 [expr {$y + $linespc + 2 * $lthickness}]
+ set t [$canv create rectangle $x0 $y0 $x1 $y1 \
+ -fill \#ffff80 -outline black -width 1 -tags hover]
+ $canv raise $t
+ set t [$canv create text $x $y -anchor nw -text $text -tags hover]
+ $canv raise $t
+}
+
+proc clickisonarrow {id y} {
+ global mainline mainlinearrow sidelines lthickness
+
+ set thresh [expr {2 * $lthickness + 6}]
+ if {[info exists mainline($id)]} {
+ if {$mainlinearrow($id) ne "none"} {
+ if {abs([lindex $mainline($id) 1] - $y) < $thresh} {
+ return "up"
+ }
+ }
+ }
+ if {[info exists sidelines($id)]} {
+ foreach ls $sidelines($id) {
+ set coords [lindex $ls 0]
+ set arrow [lindex $ls 2]
+ if {$arrow eq "first" || $arrow eq "both"} {
+ if {abs([lindex $coords 1] - $y) < $thresh} {
+ return "up"
+ }
+ }
+ if {$arrow eq "last" || $arrow eq "both"} {
+ if {abs([lindex $coords end] - $y) < $thresh} {
+ return "down"
+ }
+ }
+ }
+ }
+ return {}
+}
+
+proc arrowjump {id dirn y} {
+ global mainline sidelines canv
+
+ set yt {}
+ if {$dirn eq "down"} {
+ if {[info exists mainline($id)]} {
+ set y1 [lindex $mainline($id) 1]
+ if {$y1 > $y} {
+ set yt $y1
+ }
+ }
+ if {[info exists sidelines($id)]} {
+ foreach ls $sidelines($id) {
+ set y1 [lindex $ls 0 1]
+ if {$y1 > $y && ($yt eq {} || $y1 < $yt)} {
+ set yt $y1
+ }
+ }
+ }
+ } else {
+ if {[info exists sidelines($id)]} {
+ foreach ls $sidelines($id) {
+ set y1 [lindex $ls 0 end]
+ if {$y1 < $y && ($yt eq {} || $y1 > $yt)} {
+ set yt $y1
+ }
+ }
+ }
+ }
+ if {$yt eq {}} return
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ if {$ymax eq {} || $ymax <= 0} return
+ set view [$canv yview]
+ set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
+ set yfrac [expr {$yt / $ymax - $yspan / 2}]
+ if {$yfrac < 0} {
+ set yfrac 0
+ }
+ $canv yview moveto $yfrac
+}
+
+proc lineclick {x y id isnew} {
+ global ctext commitinfo children cflist canv thickerline
+
+ unmarkmatches
+ unselectline
+ normalline
+ $canv delete hover
+ # draw this line thicker than normal
+ drawlines $id 1
+ set thickerline $id
+ if {$isnew} {
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ if {$ymax eq {}} return
+ set yfrac [lindex [$canv yview] 0]
+ set y [expr {$y + $yfrac * $ymax}]
+ }
+ set dirn [clickisonarrow $id $y]
+ if {$dirn ne {}} {
+ arrowjump $id $dirn $y
+ return
+ }
+
+ if {$isnew} {
+ addtohistory [list lineclick $x $y $id 0]
+ }
+ # fill the details pane with info about this line
+ $ctext conf -state normal
+ $ctext delete 0.0 end
+ $ctext tag conf link -foreground blue -underline 1
+ $ctext tag bind link <Enter> { %W configure -cursor hand2 }
+ $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+ $ctext insert end "Parent:\t"
+ $ctext insert end $id [list link link0]
+ $ctext tag bind link0 <1> [list selbyid $id]
+ set info $commitinfo($id)
+ $ctext insert end "\n\t[lindex $info 0]\n"
+ $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
+ $ctext insert end "\tDate:\t[lindex $info 2]\n"
+ if {[info exists children($id)]} {
+ $ctext insert end "\nChildren:"
+ set i 0
+ foreach child $children($id) {
+ incr i
+ set info $commitinfo($child)
+ $ctext insert end "\n\t"
+ $ctext insert end $child [list link link$i]
+ $ctext tag bind link$i <1> [list selbyid $child]
+ $ctext insert end "\n\t[lindex $info 0]"
+ $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
+ $ctext insert end "\n\tDate:\t[lindex $info 2]\n"
+ }
+ }
+ $ctext conf -state disabled
+
+ $cflist delete 0 end
+}
+
+proc normalline {} {
+ global thickerline
+ if {[info exists thickerline]} {
+ drawlines $thickerline 0
+ unset thickerline
+ }
+}
+
+proc selbyid {id} {
+ global idline
+ if {[info exists idline($id)]} {
+ selectline $idline($id) 1
+ }
+}
+
+proc mstime {} {
+ global startmstime
+ if {![info exists startmstime]} {
+ set startmstime [clock clicks -milliseconds]
+ }
+ return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
+}
+
+proc rowmenu {x y id} {
+ global rowctxmenu idline selectedline rowmenuid hgvdiff
+
+ if {![info exists selectedline] || $idline($id) eq $selectedline} {
+ set state disabled
+ } else {
+ set state normal
+ }
+ $rowctxmenu entryconfigure 0 -state $state
+ $rowctxmenu entryconfigure 1 -state $state
+ $rowctxmenu entryconfigure 2 -state $state
+ if { $hgvdiff ne "" } {
+ $rowctxmenu entryconfigure 6 -state $state
+ }
+ set rowmenuid $id
+ tk_popup $rowctxmenu $x $y
+}
+
+proc diffvssel {dirn} {
+ global rowmenuid selectedline lineid
+
+ if {![info exists selectedline]} return
+ if {$dirn} {
+ set oldid $lineid($selectedline)
+ set newid $rowmenuid
+ } else {
+ set oldid $rowmenuid
+ set newid $lineid($selectedline)
+ }
+ addtohistory [list doseldiff $oldid $newid]
+ doseldiff $oldid $newid
+}
+
+proc doseldiff {oldid newid} {
+ global ctext cflist
+ global commitinfo
+
+ $ctext conf -state normal
+ $ctext delete 0.0 end
+ $ctext mark set fmark.0 0.0
+ $ctext mark gravity fmark.0 left
+ $cflist delete 0 end
+ $cflist insert end "Top"
+ $ctext insert end "From "
+ $ctext tag conf link -foreground blue -underline 1
+ $ctext tag bind link <Enter> { %W configure -cursor hand2 }
+ $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+ $ctext tag bind link0 <1> [list selbyid $oldid]
+ $ctext insert end $oldid [list link link0]
+ $ctext insert end "\n "
+ $ctext insert end [lindex $commitinfo($oldid) 0]
+ $ctext insert end "\n\nTo "
+ $ctext tag bind link1 <1> [list selbyid $newid]
+ $ctext insert end $newid [list link link1]
+ $ctext insert end "\n "
+ $ctext insert end [lindex $commitinfo($newid) 0]
+ $ctext insert end "\n"
+ $ctext conf -state disabled
+ $ctext tag delete Comments
+ $ctext tag remove found 1.0 end
+ startdiff [list $newid $oldid]
+}
+
+proc mkpatch {} {
+ global rowmenuid currentid commitinfo patchtop patchnum
+
+ if {![info exists currentid]} return
+ set oldid $currentid
+ set oldhead [lindex $commitinfo($oldid) 0]
+ set newid $rowmenuid
+ set newhead [lindex $commitinfo($newid) 0]
+ set top .patch
+ set patchtop $top
+ catch {destroy $top}
+ toplevel $top
+ label $top.title -text "Generate patch"
+ grid $top.title - -pady 10
+ label $top.from -text "From:"
+ entry $top.fromsha1 -width 40 -relief flat
+ $top.fromsha1 insert 0 $oldid
+ $top.fromsha1 conf -state readonly
+ grid $top.from $top.fromsha1 -sticky w
+ entry $top.fromhead -width 60 -relief flat
+ $top.fromhead insert 0 $oldhead
+ $top.fromhead conf -state readonly
+ grid x $top.fromhead -sticky w
+ label $top.to -text "To:"
+ entry $top.tosha1 -width 40 -relief flat
+ $top.tosha1 insert 0 $newid
+ $top.tosha1 conf -state readonly
+ grid $top.to $top.tosha1 -sticky w
+ entry $top.tohead -width 60 -relief flat
+ $top.tohead insert 0 $newhead
+ $top.tohead conf -state readonly
+ grid x $top.tohead -sticky w
+ button $top.rev -text "Reverse" -command mkpatchrev -padx 5
+ grid $top.rev x -pady 10
+ label $top.flab -text "Output file:"
+ entry $top.fname -width 60
+ $top.fname insert 0 [file normalize "patch$patchnum.patch"]
+ incr patchnum
+ grid $top.flab $top.fname -sticky w
+ frame $top.buts
+ button $top.buts.gen -text "Generate" -command mkpatchgo
+ button $top.buts.can -text "Cancel" -command mkpatchcan
+ grid $top.buts.gen $top.buts.can
+ grid columnconfigure $top.buts 0 -weight 1 -uniform a
+ grid columnconfigure $top.buts 1 -weight 1 -uniform a
+ grid $top.buts - -pady 10 -sticky ew
+ focus $top.fname
+}
+
+proc mkpatchrev {} {
+ global patchtop
+
+ set oldid [$patchtop.fromsha1 get]
+ set oldhead [$patchtop.fromhead get]
+ set newid [$patchtop.tosha1 get]
+ set newhead [$patchtop.tohead get]
+ foreach e [list fromsha1 fromhead tosha1 tohead] \
+ v [list $newid $newhead $oldid $oldhead] {
+ $patchtop.$e conf -state normal
+ $patchtop.$e delete 0 end
+ $patchtop.$e insert 0 $v
+ $patchtop.$e conf -state readonly
+ }
+}
+
+proc mkpatchgo {} {
+ global patchtop env
+
+ set oldid [$patchtop.fromsha1 get]
+ set newid [$patchtop.tosha1 get]
+ set fname [$patchtop.fname get]
+ if {[catch {exec $env(HG) --config ui.report_untrusted=false debug-diff-tree -p $oldid $newid >$fname &} err]} {
+ error_popup "Error creating patch: $err"
+ }
+ catch {destroy $patchtop}
+ unset patchtop
+}
+
+proc mkpatchcan {} {
+ global patchtop
+
+ catch {destroy $patchtop}
+ unset patchtop
+}
+
+proc mktag {} {
+ global rowmenuid mktagtop commitinfo
+
+ set top .maketag
+ set mktagtop $top
+ catch {destroy $top}
+ toplevel $top
+ label $top.title -text "Create tag"
+ grid $top.title - -pady 10
+ label $top.id -text "ID:"
+ entry $top.sha1 -width 40 -relief flat
+ $top.sha1 insert 0 $rowmenuid
+ $top.sha1 conf -state readonly
+ grid $top.id $top.sha1 -sticky w
+ entry $top.head -width 60 -relief flat
+ $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
+ $top.head conf -state readonly
+ grid x $top.head -sticky w
+ label $top.tlab -text "Tag name:"
+ entry $top.tag -width 60
+ grid $top.tlab $top.tag -sticky w
+ frame $top.buts
+ button $top.buts.gen -text "Create" -command mktaggo
+ button $top.buts.can -text "Cancel" -command mktagcan
+ grid $top.buts.gen $top.buts.can
+ grid columnconfigure $top.buts 0 -weight 1 -uniform a
+ grid columnconfigure $top.buts 1 -weight 1 -uniform a
+ grid $top.buts - -pady 10 -sticky ew
+ focus $top.tag
+}
+
+proc domktag {} {
+ global mktagtop env tagids idtags
+
+ set id [$mktagtop.sha1 get]
+ set tag [$mktagtop.tag get]
+ if {$tag == {}} {
+ error_popup "No tag name specified"
+ return
+ }
+ if {[info exists tagids($tag)]} {
+ error_popup "Tag \"$tag\" already exists"
+ return
+ }
+ if {[catch {
+ set out [exec $env(HG) --config ui.report_untrusted=false tag -r $id $tag]
+ } err]} {
+ error_popup "Error creating tag: $err"
+ return
+ }
+
+ set tagids($tag) $id
+ lappend idtags($id) $tag
+ redrawtags $id
+}
+
+proc redrawtags {id} {
+ global canv linehtag idline idpos selectedline
+
+ if {![info exists idline($id)]} return
+ $canv delete tag.$id
+ set xt [eval drawtags $id $idpos($id)]
+ $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2]
+ if {[info exists selectedline] && $selectedline == $idline($id)} {
+ selectline $selectedline 0
+ }
+}
+
+proc mktagcan {} {
+ global mktagtop
+
+ catch {destroy $mktagtop}
+ unset mktagtop
+}
+
+proc mktaggo {} {
+ domktag
+ mktagcan
+}
+
+proc writecommit {} {
+ global rowmenuid wrcomtop commitinfo wrcomcmd
+
+ set top .writecommit
+ set wrcomtop $top
+ catch {destroy $top}
+ toplevel $top
+ label $top.title -text "Write commit to file"
+ grid $top.title - -pady 10
+ label $top.id -text "ID:"
+ entry $top.sha1 -width 40 -relief flat
+ $top.sha1 insert 0 $rowmenuid
+ $top.sha1 conf -state readonly
+ grid $top.id $top.sha1 -sticky w
+ entry $top.head -width 60 -relief flat
+ $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
+ $top.head conf -state readonly
+ grid x $top.head -sticky w
+ label $top.clab -text "Command:"
+ entry $top.cmd -width 60 -textvariable wrcomcmd
+ grid $top.clab $top.cmd -sticky w -pady 10
+ label $top.flab -text "Output file:"
+ entry $top.fname -width 60
+ $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
+ grid $top.flab $top.fname -sticky w
+ frame $top.buts
+ button $top.buts.gen -text "Write" -command wrcomgo
+ button $top.buts.can -text "Cancel" -command wrcomcan
+ grid $top.buts.gen $top.buts.can
+ grid columnconfigure $top.buts 0 -weight 1 -uniform a
+ grid columnconfigure $top.buts 1 -weight 1 -uniform a
+ grid $top.buts - -pady 10 -sticky ew
+ focus $top.fname
+}
+
+proc wrcomgo {} {
+ global wrcomtop
+
+ set id [$wrcomtop.sha1 get]
+ set cmd "echo $id | [$wrcomtop.cmd get]"
+ set fname [$wrcomtop.fname get]
+ if {[catch {exec sh -c $cmd > $fname &} err]} {
+ error_popup "Error writing commit: $err"
+ }
+ catch {destroy $wrcomtop}
+ unset wrcomtop
+}
+
+proc wrcomcan {} {
+ global wrcomtop
+
+ catch {destroy $wrcomtop}
+ unset wrcomtop
+}
+
+proc listrefs {id} {
+ global idtags idheads idotherrefs idbookmarks
+
+ set w {}
+ if {[info exists idbookmarks($id)]} {
+ set w $idbookmarks($id)
+ }
+ set x {}
+ if {[info exists idtags($id)]} {
+ set x $idtags($id)
+ }
+ set y {}
+ if {[info exists idheads($id)]} {
+ set y $idheads($id)
+ }
+ set z {}
+ if {[info exists idotherrefs($id)]} {
+ set z $idotherrefs($id)
+ }
+ return [list $w $x $y $z]
+}
+
+proc rereadrefs {} {
+ global idbookmarks idtags idheads idotherrefs
+ global bookmarkids tagids headids otherrefids
+
+ set refids [concat [array names idtags] \
+ [array names idheads] [array names idotherrefs] \
+ [array names idbookmarks]]
+ foreach id $refids {
+ if {![info exists ref($id)]} {
+ set ref($id) [listrefs $id]
+ }
+ }
+ foreach v {tagids idtags headids idheads otherrefids idotherrefs \
+ bookmarkids idbookmarks} {
+ catch {unset $v}
+ }
+ readrefs
+ set refids [lsort -unique [concat $refids [array names idtags] \
+ [array names idheads] [array names idotherrefs] \
+ [array names idbookmarks]]]
+ foreach id $refids {
+ set v [listrefs $id]
+ if {![info exists ref($id)] || $ref($id) != $v} {
+ redrawtags $id
+ }
+ }
+}
+
+proc vdiff {withparent} {
+ global env rowmenuid selectedline lineid hgvdiff
+
+ if {![info exists rowmenuid]} return
+ set curid $rowmenuid
+
+ if {$withparent} {
+ set parents [exec $env(HG) --config ui.report_untrusted=false parents --rev $curid --template "{node}\n"]
+ set firstparent [lindex [split $parents "\n"] 0]
+ set otherid $firstparent
+ } else {
+ if {![info exists selectedline]} return
+ set otherid $lineid($selectedline)
+ }
+ set range "$otherid:$curid"
+ if {[catch {exec $env(HG) --config ui.report_untrusted=false $hgvdiff -r $range} err]} {
+ # Ignore errors, this is just visualization
+ }
+}
+
+proc showtag {tag isnew} {
+ global ctext cflist tagcontents tagids linknum
+
+ if {$isnew} {
+ addtohistory [list showtag $tag 0]
+ }
+ $ctext conf -state normal
+ $ctext delete 0.0 end
+ set linknum 0
+ if {[info exists tagcontents($tag)]} {
+ set text $tagcontents($tag)
+ } else {
+ set text "Tag: $tag\nId: $tagids($tag)"
+ }
+ appendwithlinks $text
+ $ctext conf -state disabled
+ $cflist delete 0 end
+}
+
+proc doquit {} {
+ global stopped
+ set stopped 100
+ destroy .
+}
+
+proc getconfig {} {
+ global env
+
+ set lines [exec $env(HG) debug-config]
+ regsub -all "\r\n" $lines "\n" config
+ set config {}
+ foreach line [split $lines "\n"] {
+ regsub "^(k|v)=" $line "" line
+ lappend config $line
+ }
+ return $config
+}
+
+# defaults...
+set datemode 0
+set boldnames 0
+set diffopts "-U 5 -p"
+set wrcomcmd "\"\$HG\" --config ui.report_untrusted=false debug-diff-tree --stdin -p --pretty"
+
+set mainfont {Helvetica 9}
+set curidfont {}
+set textfont {Courier 9}
+set findmergefiles 0
+set gaudydiff 0
+set maxgraphpct 50
+set maxwidth 16
+
+set colors {green red blue magenta darkgrey brown orange}
+set authorcolors {
+ black blue deeppink mediumorchid blue burlywood4 goldenrod slateblue red2 navy dimgrey
+}
+set bgcolor white
+
+# This color should probably be some system color (provided by tk),
+# but as the bgcolor has always been set to white, I choose to ignore
+set fgcolor black
+set diffaddcolor "#00a000"
+set diffremcolor red
+set diffmerge1color red
+set diffmerge2color blue
+set hunksepcolor blue
+
+catch {source ~/.hgk}
+
+if {$curidfont == ""} { # initialize late based on current mainfont
+ set curidfont "$mainfont bold italic underline"
+}
+
+set namefont $mainfont
+if {$boldnames} {
+ lappend namefont bold
+}
+
+set revtreeargs {}
+foreach arg $argv {
+ switch -regexp -- $arg {
+ "^$" { }
+ "^-b" { set boldnames 1 }
+ "^-d" { set datemode 1 }
+ default {
+ lappend revtreeargs $arg
+ }
+ }
+}
+
+set history {}
+set historyindex 0
+
+set stopped 0
+set redisplaying 0
+set stuffsaved 0
+set patchnum 0
+
+array set config [getconfig]
+set hgvdiff $config(vdiff)
+setcoords
+makewindow
+readrefs
+set hgroot [exec $env(HG) root]
+wm title . "hgk $hgroot"
+getcommits $revtreeargs
diff --git a/contrib/hgsh/Makefile b/contrib/hgsh/Makefile
new file mode 100644
index 0000000..966158f
--- /dev/null
+++ b/contrib/hgsh/Makefile
@@ -0,0 +1,13 @@
+CC := gcc
+CFLAGS := -g -O2 -Wall -Werror
+
+prefix ?= /usr/bin
+
+hgsh: hgsh.o
+ $(CC) -o $@ $<
+
+install: hgsh
+ install -m755 hgsh $(prefix)
+
+clean:
+ rm -f *.o hgsh
diff --git a/contrib/hgsh/hgsh.c b/contrib/hgsh/hgsh.c
new file mode 100644
index 0000000..5446107
--- /dev/null
+++ b/contrib/hgsh/hgsh.c
@@ -0,0 +1,440 @@
+/*
+ * hgsh.c - restricted login shell for mercurial
+ *
+ * Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+ *
+ * This software may be used and distributed according to the terms of the
+ * GNU General Public License, incorporated herein by reference.
+ *
+ * this program is login shell for dedicated mercurial user account. it
+ * only allows few actions:
+ *
+ * 1. run hg in server mode on specific repository. no other hg commands
+ * are allowed. we try to verify that repo to be accessed exists under
+ * given top-level directory.
+ *
+ * 2. (optional) forward ssh connection from firewall/gateway machine to
+ * "real" mercurial host, to let users outside intranet pull and push
+ * changes through firewall.
+ *
+ * 3. (optional) run normal shell, to allow to "su" to mercurial user, use
+ * "sudo" to run programs as that user, or run cron jobs as that user.
+ *
+ * only tested on linux yet. patches for non-linux systems welcome.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* for asprintf */
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+/*
+ * user config.
+ *
+ * if you see a hostname below, just use first part of hostname. example,
+ * if you have host named foo.bar.com, use "foo".
+ */
+
+/*
+ * HG_GATEWAY: hostname of gateway/firewall machine that people outside your
+ * intranet ssh into if they need to ssh to other machines. if you do not
+ * have such machine, set to NULL.
+ */
+#ifndef HG_GATEWAY
+#define HG_GATEWAY "gateway"
+#endif
+
+/*
+ * HG_HOST: hostname of mercurial server. if any machine is allowed, set to
+ * NULL.
+ */
+#ifndef HG_HOST
+#define HG_HOST "mercurial"
+#endif
+
+/*
+ * HG_USER: username to log in from HG_GATEWAY to HG_HOST. if gateway and
+ * host username are same, set to NULL.
+ */
+#ifndef HG_USER
+#define HG_USER "hg"
+#endif
+
+/*
+ * HG_ROOT: root of tree full of mercurial repos. if you do not want to
+ * validate location of repo when someone is try to access, set to NULL.
+ */
+#ifndef HG_ROOT
+#define HG_ROOT "/home/hg/repos"
+#endif
+
+/*
+ * HG: path to the mercurial executable to run.
+ */
+#ifndef HG
+#define HG "/home/hg/bin/hg"
+#endif
+
+/*
+ * HG_SHELL: shell to use for actions like "sudo" and "su" access to
+ * mercurial user, and cron jobs. if you want to make these things
+ * impossible, set to NULL.
+ */
+#ifndef HG_SHELL
+#define HG_SHELL NULL
+/* #define HG_SHELL "/bin/bash" */
+#endif
+
+/*
+ * HG_HELP: some way for users to get support if they have problem. if they
+ * should not get helpful message, set to NULL.
+ */
+#ifndef HG_HELP
+#define HG_HELP "please contact support@example.com for help."
+#endif
+
+/*
+ * SSH: path to ssh executable to run, if forwarding from HG_GATEWAY to
+ * HG_HOST. if you want to use rsh instead (why?), you need to modify
+ * arguments it is called with. see forward_through_gateway.
+ */
+#ifndef SSH
+#define SSH "/usr/bin/ssh"
+#endif
+
+/*
+ * tell whether to print command that is to be executed. useful for
+ * debugging. should not interfere with mercurial operation, since
+ * mercurial only cares about stdin and stdout, and this prints to stderr.
+ */
+static const int debug = 0;
+
+static void print_cmdline(int argc, char **argv)
+{
+ FILE *fp = stderr;
+ int i;
+
+ fputs("command: ", fp);
+
+ for (i = 0; i < argc; i++) {
+ char *spc = strpbrk(argv[i], " \t\r\n");
+ if (spc) {
+ fputc('\'', fp);
+ }
+ fputs(argv[i], fp);
+ if (spc) {
+ fputc('\'', fp);
+ }
+ if (i < argc - 1) {
+ fputc(' ', fp);
+ }
+ }
+ fputc('\n', fp);
+ fflush(fp);
+}
+
+static void usage(const char *reason, int exitcode)
+{
+ char *hg_help = HG_HELP;
+
+ if (reason) {
+ fprintf(stderr, "*** Error: %s.\n", reason);
+ }
+ fprintf(stderr, "*** This program has been invoked incorrectly.\n");
+ if (hg_help) {
+ fprintf(stderr, "*** %s\n", hg_help);
+ }
+ exit(exitcode ? exitcode : EX_USAGE);
+}
+
+/*
+ * run on gateway host to make another ssh connection, to "real" mercurial
+ * server. it sends its command line unmodified to far end.
+ *
+ * never called if HG_GATEWAY is NULL.
+ */
+static void forward_through_gateway(int argc, char **argv)
+{
+ char *ssh = SSH;
+ char *hg_host = HG_HOST;
+ char *hg_user = HG_USER;
+ char **nargv = alloca((10 + argc) * sizeof(char *));
+ int i = 0, j;
+
+ nargv[i++] = ssh;
+ nargv[i++] = "-q";
+ nargv[i++] = "-T";
+ nargv[i++] = "-x";
+ if (hg_user) {
+ nargv[i++] = "-l";
+ nargv[i++] = hg_user;
+ }
+ nargv[i++] = hg_host;
+
+ /*
+ * sshd called us with added "-c", because it thinks we are a shell.
+ * drop it if we find it.
+ */
+ j = 1;
+ if (j < argc && strcmp(argv[j], "-c") == 0) {
+ j++;
+ }
+
+ for (; j < argc; i++, j++) {
+ nargv[i] = argv[j];
+ }
+ nargv[i] = NULL;
+
+ if (debug) {
+ print_cmdline(i, nargv);
+ }
+
+ execv(ssh, nargv);
+ perror(ssh);
+ exit(EX_UNAVAILABLE);
+}
+
+/*
+ * run shell. let administrator "su" to mercurial user's account to do
+ * administrative works.
+ *
+ * never called if HG_SHELL is NULL.
+ */
+static void run_shell(int argc, char **argv)
+{
+ char *hg_shell = HG_SHELL;
+ char **nargv;
+ char *c;
+ int i;
+
+ nargv = alloca((argc + 3) * sizeof(char *));
+ c = strrchr(hg_shell, '/');
+
+ /* tell "real" shell it is login shell, if needed. */
+
+ if (argv[0][0] == '-' && c) {
+ nargv[0] = strdup(c);
+ if (nargv[0] == NULL) {
+ perror("malloc");
+ exit(EX_OSERR);
+ }
+ nargv[0][0] = '-';
+ } else {
+ nargv[0] = hg_shell;
+ }
+
+ for (i = 1; i < argc; i++) {
+ nargv[i] = argv[i];
+ }
+ nargv[i] = NULL;
+
+ if (debug) {
+ print_cmdline(i, nargv);
+ }
+
+ execv(hg_shell, nargv);
+ perror(hg_shell);
+ exit(EX_OSFILE);
+}
+
+enum cmdline {
+ hg_init,
+ hg_serve,
+};
+
+
+/*
+ * attempt to verify that a directory is really a hg repo, by testing
+ * for the existence of a subdirectory.
+ */
+static int validate_repo(const char *repo_root, const char *subdir)
+{
+ char *abs_path;
+ struct stat st;
+ int ret;
+
+ if (asprintf(&abs_path, "%s.hg/%s", repo_root, subdir) == -1) {
+ ret = -1;
+ goto bail;
+ }
+
+ /* verify that we really are looking at valid repo. */
+
+ if (stat(abs_path, &st) == -1) {
+ ret = 0;
+ } else {
+ ret = 1;
+ }
+
+bail:
+ return ret;
+}
+
+/*
+ * paranoid wrapper, runs hg executable in server mode.
+ */
+static void serve_data(int argc, char **argv)
+{
+ char *hg_root = HG_ROOT;
+ char *repo, *repo_root;
+ enum cmdline cmd;
+ char *nargv[6];
+ size_t repolen;
+ int i;
+
+ /*
+ * check argv for looking okay. we should be invoked with argv
+ * resembling like this:
+ *
+ * hgsh
+ * -c
+ * hg -R some/path serve --stdio
+ *
+ * the "-c" is added by sshd, because it thinks we are login shell.
+ */
+
+ if (argc != 3) {
+ goto badargs;
+ }
+
+ if (strcmp(argv[1], "-c") != 0) {
+ goto badargs;
+ }
+
+ if (sscanf(argv[2], "hg init %as", &repo) == 1) {
+ cmd = hg_init;
+ }
+ else if (sscanf(argv[2], "hg -R %as serve --stdio", &repo) == 1) {
+ cmd = hg_serve;
+ } else {
+ goto badargs;
+ }
+
+ repolen = repo ? strlen(repo) : 0;
+
+ if (repolen == 0) {
+ goto badargs;
+ }
+
+ if (hg_root) {
+ if (asprintf(&repo_root, "%s/%s/", hg_root, repo) == -1) {
+ goto badargs;
+ }
+
+ /*
+ * attempt to stop break out from inside the
+ * repository tree. could do something more clever
+ * here, because e.g. we could traverse a symlink that
+ * looks safe, but really breaks us out of tree.
+ */
+
+ if (strstr(repo_root, "/../") != NULL) {
+ goto badargs;
+ }
+
+ /* only hg init expects no repo. */
+
+ if (cmd != hg_init) {
+ int valid;
+
+ valid = validate_repo(repo_root, "data");
+
+ if (valid == -1) {
+ goto badargs;
+ }
+
+ if (valid == 0) {
+ valid = validate_repo(repo_root, "store");
+
+ if (valid == -1) {
+ goto badargs;
+ }
+ }
+
+ if (valid == 0) {
+ perror(repo);
+ exit(EX_DATAERR);
+ }
+ }
+
+ if (chdir(hg_root) == -1) {
+ perror(hg_root);
+ exit(EX_SOFTWARE);
+ }
+ }
+
+ i = 0;
+
+ switch (cmd) {
+ case hg_serve:
+ nargv[i++] = HG;
+ nargv[i++] = "-R";
+ nargv[i++] = repo;
+ nargv[i++] = "serve";
+ nargv[i++] = "--stdio";
+ break;
+ case hg_init:
+ nargv[i++] = HG;
+ nargv[i++] = "init";
+ nargv[i++] = repo;
+ break;
+ }
+
+ nargv[i] = NULL;
+
+ if (debug) {
+ print_cmdline(i, nargv);
+ }
+
+ execv(HG, nargv);
+ perror(HG);
+ exit(EX_UNAVAILABLE);
+
+badargs:
+ /* print useless error message. */
+
+ usage("invalid arguments", EX_DATAERR);
+}
+
+int main(int argc, char **argv)
+{
+ char host[1024];
+ char *c;
+
+ if (gethostname(host, sizeof(host)) == -1) {
+ perror("gethostname");
+ exit(EX_OSERR);
+ }
+
+ if ((c = strchr(host, '.')) != NULL) {
+ *c = '\0';
+ }
+
+ if (getenv("SSH_CLIENT")) {
+ char *hg_gateway = HG_GATEWAY;
+ char *hg_host = HG_HOST;
+
+ if (hg_gateway && strcmp(host, hg_gateway) == 0) {
+ forward_through_gateway(argc, argv);
+ }
+
+ if (hg_host && strcmp(host, hg_host) != 0) {
+ usage("invoked on unexpected host", EX_USAGE);
+ }
+
+ serve_data(argc, argv);
+ } else if (HG_SHELL) {
+ run_shell(argc, argv);
+ } else {
+ usage("invalid arguments", EX_DATAERR);
+ }
+
+ return 0;
+}
diff --git a/contrib/hgweb.fcgi b/contrib/hgweb.fcgi
new file mode 100755
index 0000000..dc62e71
--- /dev/null
+++ b/contrib/hgweb.fcgi
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+#
+# An example FastCGI script for use with flup, edit as necessary
+
+# Path to repo or hgweb config to serve (see 'hg help hgweb')
+config = "/path/to/repo/or/config"
+
+# Uncomment and adjust if Mercurial is not installed system-wide
+# (consult "installed modules" path from 'hg debuginstall'):
+#import sys; sys.path.insert(0, "/path/to/python/lib")
+
+# Uncomment to send python tracebacks to the browser if an error occurs:
+#import cgitb; cgitb.enable()
+
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb import hgweb
+from flup.server.fcgi import WSGIServer
+application = hgweb(config)
+WSGIServer(application).run()
diff --git a/contrib/hgweb.wsgi b/contrib/hgweb.wsgi
new file mode 100644
index 0000000..c2baf84
--- /dev/null
+++ b/contrib/hgweb.wsgi
@@ -0,0 +1,18 @@
+# An example WSGI for use with mod_wsgi, edit as necessary
+# See http://mercurial.selenic.com/wiki/modwsgi for more information
+
+# Path to repo or hgweb config to serve (see 'hg help hgweb')
+config = "/path/to/repo/or/config"
+
+# Uncomment and adjust if Mercurial is not installed system-wide
+# (consult "installed modules" path from 'hg debuginstall'):
+#import sys; sys.path.insert(0, "/path/to/python/lib")
+
+# Uncomment to send python tracebacks to the browser if an error occurs:
+#import cgitb; cgitb.enable()
+
+# enable demandloading to reduce startup time
+from mercurial import demandimport; demandimport.enable()
+
+from mercurial.hgweb import hgweb
+application = hgweb(config)
diff --git a/contrib/logo-droplets.svg b/contrib/logo-droplets.svg
new file mode 100644
index 0000000..b104beb
--- /dev/null
+++ b/contrib/logo-droplets.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="Layer_1" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" height="120" width="100" version="1.0" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 124.766 152.099"><metadata id="metadata6845"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title>Mercurial &quot;droplets&quot; logo</dc:title><dc:creator><cc:Agent><dc:title>Cali Mastny and Matt Mackall</dc:title></cc:Agent></dc:creator><cc:license rdf:resource="http://creativecommons.org/licenses/GPL/2.0/"/><dc:date>Feb 12 2008</dc:date></cc:Work><cc:License rdf:about="http://creativecommons.org/licenses/GPL/2.0/"><cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/><cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/><cc:requires rdf:resource="http://web.resource.org/cc/Notice"/><cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/><cc:requires rdf:resource="http://web.resource.org/cc/ShareAlike"/><cc:requires rdf:resource="http://web.resource.org/cc/SourceCode"/></cc:License></rdf:RDF></metadata>
+<rect id="rect6847" stroke-linejoin="miter" style="stroke-dasharray:none;" height="150.12" width="124.77" stroke="#000" stroke-miterlimit="4" y="0.98776" x="0.3169" stroke-width="1.9755" fill="#FFF"/><path id="text2611" style="stroke-dasharray:none;" d="M9.848,124.61c1.777-0.79,3.665-1.18,5.479-1.18,1.74,0,2.851,0.43,3.48,1.32,1.332-0.89,3.146-1.32,4.553-1.32,4.221,0,4.369,1.71,4.369,6.73v11.11c0,0.49,0.074,0.49-2.036,0.49v-11.81c0-3.63-0.074-4.74-2.48-4.74-1.073,0-2.184,0.25-3.369,1.03v15.27c-0.037,0.15-0.111,0.18-0.369,0.22-0.038,0-0.074,0.03-0.112,0.03h-1.555v-11.81c0-3.49,0-4.77-2.517-4.77-1.074,0-2.147,0.21-3.406,0.82v15.27c0,0.49,0.074,0.49-2.0361,0.49v-17.15m27.831-1.18c-3.146,0-6.626,0.89-6.626,10.4,0,7.33,2.554,8.47,6.071,8.47,2.701,0,5.034-0.89,5.034-1.32,0-0.53-0.074-1.35-0.259-1.82-1.148,0.79-2.777,1.21-4.59,1.21-2.48,0-4.146-0.71-4.184-6.22,1.629,0,5.776-0.04,8.848-0.65,0.259-1.17,0.37-2.88,0.37-4.37,0-3.56-1.444-5.7-4.664-5.7m-0.185,1.78c2.221,0,2.813,1.46,2.85,4.31,0,0.75-0.037,1.64-0.148,2.49-2.073,0.5-5.591,0.5-7.072,0.5,0.261-6.48,2.481-7.3,4.37-7.3m8.07-0.21c1.739-1.14,3.332-1.57,4.961-1.57,1.814,0,2.666,0.5,2.666,1.11,0,0.35-0.112,0.96-0.297,1.31-0.519-0.28-1.11-0.53-2.074-0.53-1.184,0-2.295,0.32-3.183,1.1v14.85c0,0.49,0.037,0.49-2.073,0.49v-16.76m18.69-0.39c0-0.47-1.554-1.18-3.11-1.18-2.999,0-6.664,1.03-6.664,9.83,0,8.33,2.222,9.07,6.109,9.07,1.924,0,3.665-1.03,3.665-1.6,0-0.32-0.074-0.82-0.26-1.24-0.778,0.56-1.962,1.1-3.22,1.1-2.665,0-4.22-0.75-4.22-7.23,0-7.15,2.554-8.15,4.775-8.15,1.258,0,1.962,0.36,2.665,0.82,0.186-0.43,0.26-1.03,0.26-1.42m14.181,16.55c-1.63,0.82-3.776,1.14-5.627,1.14-4.739,0-5.442-1.99-5.442-6.73v-11.14c0-0.46-0.037-0.46,2.074-0.46v11.82c0,3.56,0.517,4.77,3.294,4.77,1.073,0,2.554-0.22,3.665-0.86v-15.27c0-0.46-0.074-0.46,2.036-0.46v17.19m4.221-16.16c1.739-1.14,3.332-1.57,4.96-1.57,1.814,0,2.666,0.5,2.666,1.11,0,0.35-0.111,0.96-0.296,1.31-0.519-0.28-1.111-0.53-2.074-0.53-1.184,0-2.295,0.32-3.183,1.1v14.85c0,0.49,0.037,0.49-2.073,0.49v-16.76m12.379-1.03c-1.629,0-2.11,0-2.11,0.96v16.83c2.073,0,2.11,0,2.11-0.49v-17.3m-2.184-6.27c0,1.18,0.37,1.6,1.11,1.64,0.851,0,1.259-0.61,1.259-1.67,0.037-1.11-0.26-1.61-1.111-1.61-0.814,0-1.221,0.61-1.258,1.64m5.696,7.3c0-0.39,0.074-0.61,0.222-0.71,0.704-0.39,3.41-0.86,6.48-0.86,2.33,0,3.81,1.11,3.81,4.31v2.31c0,6.34-0.18,11.07-0.18,11.07-0.85,0.47-2.45,1.18-5.04,1.18-2.66,0.03-5.329-0.22-5.329-5.48,0-5.02,2.739-5.81,5.479-5.81,1.04,0,2.26,0.11,3.07,0.43v-3.31c0-2.31-1.18-2.81-2.59-2.81-1.89,0-4.514,0.35-5.662,0.89-0.222-0.39-0.26-1-0.26-1.21m8.512,7.9c-0.7-0.25-1.7-0.35-2.4-0.35-2.11,0-4.04,0.42-4.04,4.34,0,3.66,1.59,3.7,3.48,3.7,1.19,0,2.37-0.32,2.78-0.75,0,0,0.18-4.27,0.18-6.94m7.86,8.37c0,0.49,0.04,0.49-2.04,0.49v-25.2c0-0.96,0.41-0.96,2.04-0.96v25.67" stroke-miterlimit="4" stroke-width="2.02999997" fill="#010101"/><g id="g4503" transform="matrix(0.9351326,0,0,0.9351326,150.39508,-1.251766)"><path id="path2339" fill="#1b1a1b" d="M-45.75,92.692c20.04-33.321-4.232-87.363-48.614-81.873-40.096,4.958-40.746,47.165-5.405,57.191,30.583,8.685,6.318,28.084,7.027,41,0.712,12.92,26.587,17.6,46.992-16.318z"/><circle id="circle2341" transform="matrix(1.0917947,-0.2858168,0.2858168,1.0917947,-180.30817,13.494135)" cy="85.364" cx="33.728" r="15.414" fill="#1b1a1b"/><path id="path2343" fill="#1b1a1b" d="M-140.06,48.936c-6.26,0.606-10.84,6.164-10.24,12.422,0.61,6.262,6.17,10.847,12.43,10.241,6.26-0.614,10.84-6.171,10.23-12.43-0.61-6.253-6.16-10.839-12.42-10.233z"/><path id="path2561" fill="#bfbfbf" d="M-44.993,91.34c20.041-33.321-4.231-87.363-48.613-81.873-40.104,4.9568-40.744,47.166-5.406,57.193,30.583,8.684,6.318,28.083,7.027,41,0.713,12.92,26.587,17.6,46.992-16.32z"/><path id="path2563" fill="#000" d="M-86.842,112.76c-1.215-1.97,0.642-4.16,2.551-3.99,3.039,0.26,9.655-0.04,14.876-3,13.043-7.39,33.114-42.966,23.019-65.405-4.519-10.044-6.72-12.92-11.374-17.833-0.95-1.002-0.405-0.948,0.238-0.609,2.517,1.321,6.94,6.437,11.477,14.765,7.664,14.069,7.267,30.795,4.416,41.287-1.986,7.299-8.825,23.815-18.842,30.955-10.039,7.15-21.785,11.26-26.361,3.83z"/><path id="path2565" fill="#000" d="M-95.93,66.591c-6.83-2.028-15.64-4.853-20.74-11.517-3.75-4.914-5.66-10.277-6.15-13.318-0.17-1.085-0.32-1.991-0.01-2.24,0.15-0.117,2.81,5.896,6.79,10.936,3.97,5.04,9.53,7.988,14.16,9.059,4.117,0.952,12.646,3.044,15.532,5.503,2.967,2.527,3.215,7.987,2.216,8.603-1.006,0.62-3.048-4.429-11.798-7.026z"/><path id="path2567" fill="#FFF" d="M-81.841,113.72c-0.132,1.57,1.665,1.87,4.083,1.51,3.099-0.46,5.72-0.81,9.287-2.6,4.835-2.42,9.728-5.89,13.312-10.57,10.692-13.945,14.478-30.45,13.895-32.824-0.195,1.961-2.776,12.253-8.679,21.532-7.582,11.922-13.079,18.262-25.758,21.342-3.529,0.86-5.967-0.45-6.14,1.61z"/><path id="path2569" fill="#FFF" d="M-109.96,59.479c1.44,1.225,4.4,2.857,10.223,4.767,7.031,2.305,10.455,4.304,11.888,5.262,1.52,1.018,2.483,3.288,2.578,1.272,0.099-2.019-1.145-3.755-3.921-4.675-1.878-0.624-5.038-2.109-8.067-2.707-1.946-0.384-5.111-1.146-7.831-1.978-1.48-0.457-3-1.258-4.87-1.941z"/><circle id="circle2577" transform="matrix(1.0917947,-0.2858168,0.2858168,1.0917947,-180.30817,13.494135)" cy="84.375" cx="34.681" r="15.414" fill="#bfbfbf"/><path id="path2579" fill="#000" d="M-128.68,108.38c13.53,12.54,33.894-4.69,24.93-19.897-1.01-1.708-2.32-3.009-1.89-1.7,2.87,8.747,0.22,15.667-4.72,19.227-4.85,3.5-11.51,4.09-16.84,1.32-1.57-0.81-2.22,0.37-1.48,1.05z"/><path id="path2585" fill="#FFF" d="M-118.07,110.95c1.73-0.36,11.75-2.95,14.1-11.194,0.73-2.569,0.86-2.053,0.66-0.661-1.06,7.105-7.78,12.345-13.49,12.545-1.16,0.12-2.68-0.39-1.27-0.69z"/><path id="path2589" fill="#bfbfbf" d="M-139.3,47.584c-6.26,0.605-10.84,6.164-10.24,12.422,0.61,6.261,6.17,10.847,12.43,10.241,6.25-0.614,10.84-6.173,10.23-12.431-0.61-6.254-6.17-10.838-12.42-10.232z"/><path id="path2591" fill="#000" d="M-144.47,67.571c0.07,0.805,1.17,1.838,2.9,2.312,1.49,0.408,5.32,1.45,10.25-1.658,4.92-3.108,5.49-11.421,3.25-13.865-0.69-1.239-1.59-2.14-0.88-0.164,1.81,4.99-1.7,9.659-4.74,11.82-3.03,2.162-6.88,1.139-8.45,0.66s-2.4,0.064-2.33,0.895z"/><path id="path2597" fill="#FFF" d="M-138.11,68.688c0.45-0.406,2.73-0.24,4.79-1.35,2.07-1.109,4.52-3.54,4.95-6.994,0.26-2.029,0.34-1.519,0.44-0.415-0.32,5.743-5.6,8.916-8.62,9.334-0.82,0.113-2.25,0.044-1.56-0.575z"/><path id="path2561_1_" fill="#999" d="M-47.767,69.694c8.532-24.594-9.323-61.736-45.446-57.268-32.637,4.035-33.167,38.389-4.4,46.55,32.582,4.933,12.962,29.512,10.179,41.904-2.495,11.11,26.331,12.94,39.667-31.186z"/><path id="path2571" fill="#f3f3f3" d="M-70.093,88.904c-8.827-1.092-21.529,18.836-9.552,16.506,5.756-0.86,10.525-2.89,14.794-7.762,5.567-6.353,13.883-20.074,16.288-28.94,2.025-7.476,1.007-19.057-1.081-8.175-2.142,11.167-11.623,29.464-20.449,28.371z"/><path id="path2581" fill="#999" d="M-129.39,104.85c2.05,0.03,3.28,0.32,5.35,1.77,4.09,1.7,11.61,0.62,15.09-3.95,3.47-4.57,3.58-10.868,2.26-14.674-3.24-9.314-16.99-9.149-23.13-1.417-6.64,8.636-1.61,18.231,0.43,18.271z"/><path id="path2593_2_" fill="#999" d="M-147.64,61.684c0.41,1.282,1.45,3.154,3.65,3.466,2.94,0.417,3.54,1.743,7,1.055,3.47-0.688,6.09-3.528,7.14-6.67,1.21-4.347-0.59-6.591-3.31-8.595-2.71-2.003-8.67-1.788-12.23,1.458-2.53,2.305-3.24,6.163-2.25,9.286z"/><path id="path256" fill="#f3f3f3" d="M-136.11,64.558c2.66-0.697,6.18-4.325,4.44-7.096-2.16-3.413-8.17-0.491-8.37,3.309-0.21,3.802,1.11,4.526,3.93,3.787z"/><path id="path258" fill="#f3f3f3" d="M-116.12,105.51c2.28-0.6,9.24-3.43,7.93-13.547-0.66-5.126-3.46,6.361-8.63,8.077-7.85,2.61-6.97,7.48,0.7,5.47z"/></g>
+</svg>
diff --git a/contrib/macosx/Readme.html b/contrib/macosx/Readme.html
new file mode 100644
index 0000000..f8302d5
--- /dev/null
+++ b/contrib/macosx/Readme.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!-- This is the second screen displayed during the install. -->
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <title></title>
+ <style type="text/css">
+ p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
+ p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
+ p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
+ p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #000fed}
+ span.s1 {text-decoration: underline}
+ span.s2 {font: 12.0px Courier}
+ </style>
+</head>
+<body>
+<p class="p1"><b>Before you install</b></p>
+<p class="p2"><br></p>
+<p class="p3">This is an OS X 10.6 version of Mercurial that depends on the default Python 2.6 installation.</p>
+<p class="p2"><br></p>
+<p class="p1"><b>After you install</b></p>
+<p class="p2"><br></p>
+<p class="p3">This package installs the <span class="s2">hg</span> executable in <span class="s2">/usr/local/bin</span> and the Mercurial files in <span class="s2">/Library/Python/2.6/site-packages/mercurial.</span></p>
+<p class="p2"><br></p>
+<p class="p1"><b>Documentation</b></p>
+<p class="p2"><br></p>
+<p class="p3">Visit the <a href="http://mercurial.selenic.com/">Mercurial web site and wiki</a></p>
+<p class="p2"><br></p>
+<p class="p3">There's also a free book, <a href="http://hgbook.red-bean.com/">Distributed revision control with Mercurial</a></p>
+<p class="p2"><br></p>
+<p class="p1"><b>Reporting problems</b></p>
+<p class="p2"><br></p>
+<p class="p3">If you run into any problems, please file a bug online:</p>
+<p class="p3"><a href="http://mercurial.selenic.com/bts/">http://mercurial.selenic.com/bts/</a></p>
+</body>
+</html>
diff --git a/contrib/macosx/Welcome.html b/contrib/macosx/Welcome.html
new file mode 100644
index 0000000..61ebabc
--- /dev/null
+++ b/contrib/macosx/Welcome.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!-- This is the second screen displayed during the install. -->
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <title></title>
+ <style type="text/css">
+ p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
+ p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
+ </style>
+</head>
+<body>
+<p class="p1">This is a prepackaged release of <a href="http://mercurial.selenic.com/">Mercurial</a> for Mac OS X.</p>
+<p class="p2"><br></p>
+<br>
+<p>
+Please be sure to read the latest <a href="http://mercurial.selenic.com/wiki/WhatsNew">release notes</a>.</p>
+</body>
+</html>
diff --git a/contrib/macosx/macosx-build.txt b/contrib/macosx/macosx-build.txt
new file mode 100644
index 0000000..f67ca53
--- /dev/null
+++ b/contrib/macosx/macosx-build.txt
@@ -0,0 +1,11 @@
+to build a new macosx binary package:
+
+install macpython from http://www.python.org/download/mac
+
+install py2app from http://pythonmac.org/packages/
+
+make sure /usr/local/bin is in your path
+
+run bdist_mpkg in top-level hg directory
+
+find packaged stuff in dist directory
diff --git a/contrib/memory.py b/contrib/memory.py
new file mode 100644
index 0000000..5ea38a1
--- /dev/null
+++ b/contrib/memory.py
@@ -0,0 +1,36 @@
+# memory.py - track memory usage
+#
+# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+'''helper extension to measure memory usage
+
+Reads current and peak memory usage from ``/proc/self/status`` and
+prints it to ``stderr`` on exit.
+'''
+
+import atexit
+
+def memusage(ui):
+ """Report memory usage of the current process."""
+ status = None
+ result = {'peak': 0, 'rss': 0}
+ try:
+ # This will only work on systems with a /proc file system
+ # (like Linux).
+ status = open('/proc/self/status', 'r')
+ for line in status:
+ parts = line.split()
+ key = parts[0][2:-1].lower()
+ if key in result:
+ result[key] = int(parts[1])
+ finally:
+ if status is not None:
+ status.close()
+ ui.write_err(", ".join(["%s: %.1f MiB" % (key, value / 1024.0)
+ for key, value in result.iteritems()]) + "\n")
+
+def extsetup(ui):
+ atexit.register(memusage, ui)
diff --git a/contrib/mercurial.el b/contrib/mercurial.el
new file mode 100644
index 0000000..c3ad538
--- /dev/null
+++ b/contrib/mercurial.el
@@ -0,0 +1,1293 @@
+;;; mercurial.el --- Emacs support for the Mercurial distributed SCM
+
+;; Copyright (C) 2005, 2006 Bryan O'Sullivan
+
+;; Author: Bryan O'Sullivan <bos@serpentine.com>
+
+;; mercurial.el is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License version
+;; 2 or any later version.
+
+;; mercurial.el is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with mercurial.el, GNU Emacs, or XEmacs; see the file COPYING
+;; (`C-h C-l'). If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; mercurial.el builds upon Emacs's VC mode to provide flexible
+;; integration with the Mercurial distributed SCM tool.
+
+;; To get going as quickly as possible, load mercurial.el into Emacs and
+;; type `C-c h h'; this runs hg-help-overview, which prints a helpful
+;; usage overview.
+
+;; Much of the inspiration for mercurial.el comes from Rajesh
+;; Vaidheeswarran's excellent p4.el, which does an admirably thorough
+;; job for the commercial Perforce SCM product. In fact, substantial
+;; chunks of code are adapted from p4.el.
+
+;; This code has been developed under XEmacs 21.5, and may not work as
+;; well under GNU Emacs (albeit tested under 21.4). Patches to
+;; enhance the portability of this code, fix bugs, and add features
+;; are most welcome.
+
+;; As of version 22.3, GNU Emacs's VC mode has direct support for
+;; Mercurial, so this package may not prove as useful there.
+
+;; Please send problem reports and suggestions to bos@serpentine.com.
+
+
+;;; Code:
+
+(eval-when-compile (require 'cl))
+(require 'diff-mode)
+(require 'easymenu)
+(require 'executable)
+(require 'vc)
+
+(defmacro hg-feature-cond (&rest clauses)
+ "Test CLAUSES for feature at compile time.
+Each clause is (FEATURE BODY...)."
+ (dolist (x clauses)
+ (let ((feature (car x))
+ (body (cdr x)))
+ (when (or (eq feature t)
+ (featurep feature))
+ (return (cons 'progn body))))))
+
+
+;;; XEmacs has view-less, while GNU Emacs has view. Joy.
+
+(hg-feature-cond
+ (xemacs (require 'view-less))
+ (t (require 'view)))
+
+
+;;; Variables accessible through the custom system.
+
+(defgroup mercurial nil
+ "Mercurial distributed SCM."
+ :group 'tools)
+
+(defcustom hg-binary
+ (or (executable-find "hg")
+ (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg"))
+ (when (file-executable-p path)
+ (return path))))
+ "The path to Mercurial's hg executable."
+ :type '(file :must-match t)
+ :group 'mercurial)
+
+(defcustom hg-mode-hook nil
+ "Hook run when a buffer enters hg-mode."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom hg-commit-mode-hook nil
+ "Hook run when a buffer is created to prepare a commit."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom hg-pre-commit-hook nil
+ "Hook run before a commit is performed.
+If you want to prevent the commit from proceeding, raise an error."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom hg-log-mode-hook nil
+ "Hook run after a buffer is filled with log information."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom hg-global-prefix "\C-ch"
+ "The global prefix for Mercurial keymap bindings."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom hg-commit-allow-empty-message nil
+ "Whether to allow changes to be committed with empty descriptions."
+ :type 'boolean
+ :group 'mercurial)
+
+(defcustom hg-commit-allow-empty-file-list nil
+ "Whether to allow changes to be committed without any modified files."
+ :type 'boolean
+ :group 'mercurial)
+
+(defcustom hg-rev-completion-limit 100
+ "The maximum number of revisions that hg-read-rev will offer to complete.
+This affects memory usage and performance when prompting for revisions
+in a repository with a lot of history."
+ :type 'integer
+ :group 'mercurial)
+
+(defcustom hg-log-limit 50
+ "The maximum number of revisions that hg-log will display."
+ :type 'integer
+ :group 'mercurial)
+
+(defcustom hg-update-modeline t
+ "Whether to update the modeline with the status of a file after every save.
+Set this to nil on platforms with poor process management, such as Windows."
+ :type 'boolean
+ :group 'mercurial)
+
+(defcustom hg-incoming-repository "default"
+ "The repository from which changes are pulled from by default.
+This should be a symbolic repository name, since it is used for all
+repository-related commands."
+ :type 'string
+ :group 'mercurial)
+
+(defcustom hg-outgoing-repository ""
+ "The repository to which changes are pushed to by default.
+This should be a symbolic repository name, since it is used for all
+repository-related commands."
+ :type 'string
+ :group 'mercurial)
+
+
+;;; Other variables.
+
+(defvar hg-mode nil
+ "Is this file managed by Mercurial?")
+(make-variable-buffer-local 'hg-mode)
+(put 'hg-mode 'permanent-local t)
+
+(defvar hg-status nil)
+(make-variable-buffer-local 'hg-status)
+(put 'hg-status 'permanent-local t)
+
+(defvar hg-prev-buffer nil)
+(make-variable-buffer-local 'hg-prev-buffer)
+(put 'hg-prev-buffer 'permanent-local t)
+
+(defvar hg-root nil)
+(make-variable-buffer-local 'hg-root)
+(put 'hg-root 'permanent-local t)
+
+(defvar hg-view-mode nil)
+(make-variable-buffer-local 'hg-view-mode)
+(put 'hg-view-mode 'permanent-local t)
+
+(defvar hg-view-file-name nil)
+(make-variable-buffer-local 'hg-view-file-name)
+(put 'hg-view-file-name 'permanent-local t)
+
+(defvar hg-output-buffer-name "*Hg*"
+ "The name to use for Mercurial output buffers.")
+
+(defvar hg-file-history nil)
+(defvar hg-repo-history nil)
+(defvar hg-rev-history nil)
+(defvar hg-repo-completion-table nil) ; shut up warnings
+
+
+;;; Random constants.
+
+(defconst hg-commit-message-start
+ "--- Enter your commit message. Type `C-c C-c' to commit. ---\n")
+
+(defconst hg-commit-message-end
+ "--- Files in bold will be committed. Click to toggle selection. ---\n")
+
+(defconst hg-state-alist
+ '((?M . modified)
+ (?A . added)
+ (?R . removed)
+ (?! . deleted)
+ (?C . normal)
+ (?I . ignored)
+ (?? . nil)))
+
+;;; hg-mode keymap.
+
+(defvar hg-prefix-map
+ (let ((map (make-sparse-keymap)))
+ (hg-feature-cond (xemacs (set-keymap-name map 'hg-prefix-map))) ; XEmacs
+ (set-keymap-parent map vc-prefix-map)
+ (define-key map "=" 'hg-diff)
+ (define-key map "c" 'hg-undo)
+ (define-key map "g" 'hg-annotate)
+ (define-key map "i" 'hg-add)
+ (define-key map "l" 'hg-log)
+ (define-key map "n" 'hg-commit-start)
+ ;; (define-key map "r" 'hg-update)
+ (define-key map "u" 'hg-revert-buffer)
+ (define-key map "~" 'hg-version-other-window)
+ map)
+ "This keymap overrides some default vc-mode bindings.")
+
+(defvar hg-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-xv" hg-prefix-map)
+ map))
+
+(add-minor-mode 'hg-mode 'hg-mode hg-mode-map)
+
+
+;;; Global keymap.
+
+(defvar hg-global-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "," 'hg-incoming)
+ (define-key map "." 'hg-outgoing)
+ (define-key map "<" 'hg-pull)
+ (define-key map "=" 'hg-diff-repo)
+ (define-key map ">" 'hg-push)
+ (define-key map "?" 'hg-help-overview)
+ (define-key map "A" 'hg-addremove)
+ (define-key map "U" 'hg-revert)
+ (define-key map "a" 'hg-add)
+ (define-key map "c" 'hg-commit-start)
+ (define-key map "f" 'hg-forget)
+ (define-key map "h" 'hg-help-overview)
+ (define-key map "i" 'hg-init)
+ (define-key map "l" 'hg-log-repo)
+ (define-key map "r" 'hg-root)
+ (define-key map "s" 'hg-status)
+ (define-key map "u" 'hg-update)
+ map))
+
+(global-set-key hg-global-prefix hg-global-map)
+
+;;; View mode keymap.
+
+(defvar hg-view-mode-map
+ (let ((map (make-sparse-keymap)))
+ (hg-feature-cond (xemacs (set-keymap-name map 'hg-view-mode-map))) ; XEmacs
+ (define-key map (hg-feature-cond (xemacs [button2])
+ (t [mouse-2]))
+ 'hg-buffer-mouse-clicked)
+ map))
+
+(add-minor-mode 'hg-view-mode "" hg-view-mode-map)
+
+
+;;; Commit mode keymaps.
+
+(defvar hg-commit-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-c\C-c" 'hg-commit-finish)
+ (define-key map "\C-c\C-k" 'hg-commit-kill)
+ (define-key map "\C-xv=" 'hg-diff-repo)
+ map))
+
+(defvar hg-commit-mode-file-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (hg-feature-cond (xemacs [button2])
+ (t [mouse-2]))
+ 'hg-commit-mouse-clicked)
+ (define-key map " " 'hg-commit-toggle-file)
+ (define-key map "\r" 'hg-commit-toggle-file)
+ map))
+
+
+;;; Convenience functions.
+
+(defsubst hg-binary ()
+ (if hg-binary
+ hg-binary
+ (error "No `hg' executable found!")))
+
+(defsubst hg-replace-in-string (str regexp newtext &optional literal)
+ "Replace all matches in STR for REGEXP with NEWTEXT string.
+Return the new string. Optional LITERAL non-nil means do a literal
+replacement.
+
+This function bridges yet another pointless impedance gap between
+XEmacs and GNU Emacs."
+ (hg-feature-cond
+ (xemacs (replace-in-string str regexp newtext literal))
+ (t (replace-regexp-in-string regexp newtext str nil literal))))
+
+(defsubst hg-strip (str)
+ "Strip leading and trailing blank lines from a string."
+ (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "")
+ "\\`[ \t\r\n]*[\r\n]" ""))
+
+(defsubst hg-chomp (str)
+ "Strip trailing newlines from a string."
+ (hg-replace-in-string str "[\r\n]+\\'" ""))
+
+(defun hg-run-command (command &rest args)
+ "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT).
+The list ARGS contains a list of arguments to pass to the command."
+ (let* (exit-code
+ (output
+ (with-output-to-string
+ (with-current-buffer
+ standard-output
+ (setq exit-code
+ (apply 'call-process command nil t nil args))))))
+ (cons exit-code output)))
+
+(defun hg-run (command &rest args)
+ "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)."
+ (apply 'hg-run-command (hg-binary) command args))
+
+(defun hg-run0 (command &rest args)
+ "Run the Mercurial command COMMAND, returning its output.
+If the command does not exit with a zero status code, raise an error."
+ (let ((res (apply 'hg-run-command (hg-binary) command args)))
+ (if (not (eq (car res) 0))
+ (error "Mercurial command failed %s - exit code %s"
+ (cons command args)
+ (car res))
+ (cdr res))))
+
+(defmacro hg-do-across-repo (path &rest body)
+ (let ((root-name (make-symbol "root-"))
+ (buf-name (make-symbol "buf-")))
+ `(let ((,root-name (hg-root ,path)))
+ (save-excursion
+ (dolist (,buf-name (buffer-list))
+ (set-buffer ,buf-name)
+ (when (and hg-status (equal (hg-root buffer-file-name) ,root-name))
+ ,@body))))))
+
+(put 'hg-do-across-repo 'lisp-indent-function 1)
+
+(defun hg-sync-buffers (path)
+ "Sync buffers visiting PATH with their on-disk copies.
+If PATH is not being visited, but is under the repository root, sync
+all buffers visiting files in the repository."
+ (let ((buf (find-buffer-visiting path)))
+ (if buf
+ (with-current-buffer buf
+ (vc-buffer-sync))
+ (hg-do-across-repo path
+ (vc-buffer-sync)))))
+
+(defun hg-buffer-commands (pnt)
+ "Use the properties of a character to do something sensible."
+ (interactive "d")
+ (let ((rev (get-char-property pnt 'rev))
+ (file (get-char-property pnt 'file)))
+ (cond
+ (file
+ (find-file-other-window file))
+ (rev
+ (hg-diff hg-view-file-name rev rev))
+ ((message "I don't know how to do that yet")))))
+
+(defsubst hg-event-point (event)
+ "Return the character position of the mouse event EVENT."
+ (hg-feature-cond (xemacs (event-point event))
+ (t (posn-point (event-start event)))))
+
+(defsubst hg-event-window (event)
+ "Return the window over which mouse event EVENT occurred."
+ (hg-feature-cond (xemacs (event-window event))
+ (t (posn-window (event-start event)))))
+
+(defun hg-buffer-mouse-clicked (event)
+ "Translate the mouse clicks in a HG log buffer to character events.
+These are then handed off to `hg-buffer-commands'.
+
+Handle frickin' frackin' gratuitous event-related incompatibilities."
+ (interactive "e")
+ (select-window (hg-event-window event))
+ (hg-buffer-commands (hg-event-point event)))
+
+(defsubst hg-abbrev-file-name (file)
+ "Portable wrapper around abbreviate-file-name."
+ (hg-feature-cond (xemacs (abbreviate-file-name file t))
+ (t (abbreviate-file-name file))))
+
+(defun hg-read-file-name (&optional prompt default)
+ "Read a file or directory name, or a pattern, to use with a command."
+ (save-excursion
+ (while hg-prev-buffer
+ (set-buffer hg-prev-buffer))
+ (let ((path (or default
+ (buffer-file-name)
+ (expand-file-name default-directory))))
+ (if (or (not path) current-prefix-arg)
+ (expand-file-name
+ (eval (list* 'read-file-name
+ (format "File, directory or pattern%s: "
+ (or prompt ""))
+ (and path (file-name-directory path))
+ nil nil
+ (and path (file-name-nondirectory path))
+ (hg-feature-cond
+ (xemacs (cons (quote 'hg-file-history) nil))
+ (t nil)))))
+ path))))
+
+(defun hg-read-number (&optional prompt default)
+ "Read a integer value."
+ (save-excursion
+ (if (or (not default) current-prefix-arg)
+ (string-to-number
+ (eval (list* 'read-string
+ (or prompt "")
+ (if default (cons (format "%d" default) nil) nil))))
+ default)))
+
+(defun hg-read-config ()
+ "Return an alist of (key . value) pairs of Mercurial config data.
+Each key is of the form (section . name)."
+ (let (items)
+ (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items)
+ (string-match "^\\([^=]*\\)=\\(.*\\)" line)
+ (let* ((left (substring line (match-beginning 1) (match-end 1)))
+ (right (substring line (match-beginning 2) (match-end 2)))
+ (key (split-string left "\\."))
+ (value (hg-replace-in-string right "\\\\n" "\n" t)))
+ (setq items (cons (cons (cons (car key) (cadr key)) value) items))))))
+
+(defun hg-config-section (section config)
+ "Return an alist of (name . value) pairs for SECTION of CONFIG."
+ (let (items)
+ (dolist (item config items)
+ (when (equal (caar item) section)
+ (setq items (cons (cons (cdar item) (cdr item)) items))))))
+
+(defun hg-string-starts-with (sub str)
+ "Indicate whether string STR starts with the substring or character SUB."
+ (if (not (stringp sub))
+ (and (> (length str) 0) (equal (elt str 0) sub))
+ (let ((sub-len (length sub)))
+ (and (<= sub-len (length str))
+ (string= sub (substring str 0 sub-len))))))
+
+(defun hg-complete-repo (string predicate all)
+ "Attempt to complete a repository name.
+We complete on either symbolic names from Mercurial's config or real
+directory names from the file system. We do not penalise URLs."
+ (or (if all
+ (all-completions string hg-repo-completion-table predicate)
+ (try-completion string hg-repo-completion-table predicate))
+ (let* ((str (expand-file-name string))
+ (dir (file-name-directory str))
+ (file (file-name-nondirectory str)))
+ (if all
+ (let (completions)
+ (dolist (name (delete "./" (file-name-all-completions file dir))
+ completions)
+ (let ((path (concat dir name)))
+ (when (file-directory-p path)
+ (setq completions (cons name completions))))))
+ (let ((comp (file-name-completion file dir)))
+ (if comp
+ (hg-abbrev-file-name (concat dir comp))))))))
+
+(defun hg-read-repo-name (&optional prompt initial-contents default)
+ "Read the location of a repository."
+ (save-excursion
+ (while hg-prev-buffer
+ (set-buffer hg-prev-buffer))
+ (let (hg-repo-completion-table)
+ (if current-prefix-arg
+ (progn
+ (dolist (path (hg-config-section "paths" (hg-read-config)))
+ (setq hg-repo-completion-table
+ (cons (cons (car path) t) hg-repo-completion-table))
+ (unless (hg-string-starts-with (hg-feature-cond
+ (xemacs directory-sep-char)
+ (t ?/))
+ (cdr path))
+ (setq hg-repo-completion-table
+ (cons (cons (cdr path) t) hg-repo-completion-table))))
+ (completing-read (format "Repository%s: " (or prompt ""))
+ 'hg-complete-repo
+ nil
+ nil
+ initial-contents
+ 'hg-repo-history
+ default))
+ default))))
+
+(defun hg-read-rev (&optional prompt default)
+ "Read a revision or tag, offering completions."
+ (save-excursion
+ (while hg-prev-buffer
+ (set-buffer hg-prev-buffer))
+ (let ((rev (or default "tip")))
+ (if current-prefix-arg
+ (let ((revs (split-string
+ (hg-chomp
+ (hg-run0 "-q" "log" "-l"
+ (format "%d" hg-rev-completion-limit)))
+ "[\n:]")))
+ (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n"))
+ (setq revs (cons (car (split-string line "\\s-")) revs)))
+ (completing-read (format "Revision%s (%s): "
+ (or prompt "")
+ (or default "tip"))
+ (mapcar (lambda (x) (cons x x)) revs)
+ nil
+ nil
+ nil
+ 'hg-rev-history
+ (or default "tip")))
+ rev))))
+
+(defun hg-parents-for-mode-line (root)
+ "Format the parents of the working directory for the mode line."
+ (let ((parents (split-string (hg-chomp
+ (hg-run0 "--cwd" root "parents" "--template"
+ "{rev}\n")) "\n")))
+ (mapconcat 'identity parents "+")))
+
+(defun hg-buffers-visiting-repo (&optional path)
+ "Return a list of buffers visiting the repository containing PATH."
+ (let ((root-name (hg-root (or path (buffer-file-name))))
+ bufs)
+ (save-excursion
+ (dolist (buf (buffer-list) bufs)
+ (set-buffer buf)
+ (let ((name (buffer-file-name)))
+ (when (and hg-status name (equal (hg-root name) root-name))
+ (setq bufs (cons buf bufs))))))))
+
+(defun hg-update-mode-lines (path)
+ "Update the mode lines of all buffers visiting the same repository as PATH."
+ (let* ((root (hg-root path))
+ (parents (hg-parents-for-mode-line root)))
+ (save-excursion
+ (dolist (info (hg-path-status
+ root
+ (mapcar
+ (function
+ (lambda (buf)
+ (substring (buffer-file-name buf) (length root))))
+ (hg-buffers-visiting-repo root))))
+ (let* ((name (car info))
+ (status (cdr info))
+ (buf (find-buffer-visiting (concat root name))))
+ (when buf
+ (set-buffer buf)
+ (hg-mode-line-internal status parents)))))))
+
+
+;;; View mode bits.
+
+(defun hg-exit-view-mode (buf)
+ "Exit from hg-view-mode.
+We delete the current window if entering hg-view-mode split the
+current frame."
+ (when (and (eq buf (current-buffer))
+ (> (length (window-list)) 1))
+ (delete-window))
+ (when (buffer-live-p buf)
+ (kill-buffer buf)))
+
+(defun hg-view-mode (prev-buffer &optional file-name)
+ (goto-char (point-min))
+ (set-buffer-modified-p nil)
+ (toggle-read-only t)
+ (hg-feature-cond (xemacs (view-minor-mode prev-buffer 'hg-exit-view-mode))
+ (t (view-mode-enter nil 'hg-exit-view-mode)))
+ (setq hg-view-mode t)
+ (setq truncate-lines t)
+ (when file-name
+ (setq hg-view-file-name
+ (hg-abbrev-file-name file-name))))
+
+(defun hg-file-status (file)
+ "Return status of FILE, or nil if FILE does not exist or is unmanaged."
+ (let* ((s (hg-run "status" file))
+ (exit (car s))
+ (output (cdr s)))
+ (if (= exit 0)
+ (let ((state (and (>= (length output) 2)
+ (= (aref output 1) ? )
+ (assq (aref output 0) hg-state-alist))))
+ (if state
+ (cdr state)
+ 'normal)))))
+
+(defun hg-path-status (root paths)
+ "Return status of PATHS in repo ROOT as an alist.
+Each entry is a pair (FILE-NAME . STATUS)."
+ (let ((s (apply 'hg-run "--cwd" root "status" "-marduc" paths))
+ result)
+ (dolist (entry (split-string (hg-chomp (cdr s)) "\n") (nreverse result))
+ (let (state name)
+ (cond ((= (aref entry 1) ? )
+ (setq state (assq (aref entry 0) hg-state-alist)
+ name (substring entry 2)))
+ ((string-match "\\(.*\\): " entry)
+ (setq name (match-string 1 entry))))
+ (setq result (cons (cons name state) result))))))
+
+(defmacro hg-view-output (args &rest body)
+ "Execute BODY in a clean buffer, then quickly display that buffer.
+If the buffer contains one line, its contents are displayed in the
+minibuffer. Otherwise, the buffer is displayed in view-mode.
+ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is
+the name of the buffer to create, and FILE is the name of the file
+being viewed."
+ (let ((prev-buf (make-symbol "prev-buf-"))
+ (v-b-name (car args))
+ (v-m-rest (cdr args)))
+ `(let ((view-buf-name ,v-b-name)
+ (,prev-buf (current-buffer)))
+ (get-buffer-create view-buf-name)
+ (kill-buffer view-buf-name)
+ (get-buffer-create view-buf-name)
+ (set-buffer view-buf-name)
+ (save-excursion
+ ,@body)
+ (case (count-lines (point-min) (point-max))
+ ((0)
+ (kill-buffer view-buf-name)
+ (message "(No output)"))
+ ((1)
+ (let ((msg (hg-chomp (buffer-substring (point-min) (point-max)))))
+ (kill-buffer view-buf-name)
+ (message "%s" msg)))
+ (t
+ (pop-to-buffer view-buf-name)
+ (setq hg-prev-buffer ,prev-buf)
+ (hg-view-mode ,prev-buf ,@v-m-rest))))))
+
+(put 'hg-view-output 'lisp-indent-function 1)
+
+;;; Context save and restore across revert and other operations.
+
+(defun hg-position-context (pos)
+ "Return information to help find the given position again."
+ (let* ((end (min (point-max) (+ pos 98))))
+ (list pos
+ (buffer-substring (max (point-min) (- pos 2)) end)
+ (- end pos))))
+
+(defun hg-buffer-context ()
+ "Return information to help restore a user's editing context.
+This is useful across reverts and merges, where a context is likely
+to have moved a little, but not really changed."
+ (let ((point-context (hg-position-context (point)))
+ (mark-context (let ((mark (mark-marker)))
+ (and mark
+ ;; make sure active mark
+ (marker-buffer mark)
+ (marker-position mark)
+ (hg-position-context mark)))))
+ (list point-context mark-context)))
+
+(defun hg-find-context (ctx)
+ "Attempt to find a context in the given buffer.
+Always returns a valid, hopefully sane, position."
+ (let ((pos (nth 0 ctx))
+ (str (nth 1 ctx))
+ (fixup (nth 2 ctx)))
+ (save-excursion
+ (goto-char (max (point-min) (- pos 15000)))
+ (if (and (not (equal str ""))
+ (search-forward str nil t))
+ (- (point) fixup)
+ (max pos (point-min))))))
+
+(defun hg-restore-context (ctx)
+ "Attempt to restore the user's editing context."
+ (let ((point-context (nth 0 ctx))
+ (mark-context (nth 1 ctx)))
+ (goto-char (hg-find-context point-context))
+ (when mark-context
+ (set-mark (hg-find-context mark-context)))))
+
+
+;;; Hooks.
+
+(defun hg-mode-line-internal (status parents)
+ (setq hg-status status
+ hg-mode (and status (concat " Hg:"
+ parents
+ (cdr (assq status
+ '((normal . "")
+ (removed . "r")
+ (added . "a")
+ (deleted . "!")
+ (modified . "m"))))))))
+
+(defun hg-mode-line (&optional force)
+ "Update the modeline with the current status of a file.
+An update occurs if optional argument FORCE is non-nil,
+hg-update-modeline is non-nil, or we have not yet checked the state of
+the file."
+ (let ((root (hg-root)))
+ (when (and root (or force hg-update-modeline (not hg-mode)))
+ (let ((status (hg-file-status buffer-file-name))
+ (parents (hg-parents-for-mode-line root)))
+ (hg-mode-line-internal status parents)
+ status))))
+
+(defun hg-mode (&optional toggle)
+ "Minor mode for Mercurial distributed SCM integration.
+
+The Mercurial mode user interface is based on that of VC mode, so if
+you're already familiar with VC, the same keybindings and functions
+will generally work.
+
+Below is a list of many common SCM tasks. In the list, `G/L\'
+indicates whether a key binding is global (G) to a repository or
+local (L) to a file. Many commands take a prefix argument.
+
+SCM Task G/L Key Binding Command Name
+-------- --- ----------- ------------
+Help overview (what you are reading) G C-c h h hg-help-overview
+
+Tell Mercurial to manage a file G C-c h a hg-add
+Commit changes to current file only L C-x v n hg-commit-start
+Undo changes to file since commit L C-x v u hg-revert-buffer
+
+Diff file vs last checkin L C-x v = hg-diff
+
+View file change history L C-x v l hg-log
+View annotated file L C-x v a hg-annotate
+
+Diff repo vs last checkin G C-c h = hg-diff-repo
+View status of files in repo G C-c h s hg-status
+Commit all changes G C-c h c hg-commit-start
+
+Undo all changes since last commit G C-c h U hg-revert
+View repo change history G C-c h l hg-log-repo
+
+See changes that can be pulled G C-c h , hg-incoming
+Pull changes G C-c h < hg-pull
+Update working directory after pull G C-c h u hg-update
+See changes that can be pushed G C-c h . hg-outgoing
+Push changes G C-c h > hg-push"
+ (unless vc-make-backup-files
+ (set (make-local-variable 'backup-inhibited) t))
+ (run-hooks 'hg-mode-hook))
+
+(defun hg-find-file-hook ()
+ (ignore-errors
+ (when (hg-mode-line)
+ (hg-mode))))
+
+(add-hook 'find-file-hooks 'hg-find-file-hook)
+
+(defun hg-after-save-hook ()
+ (ignore-errors
+ (let ((old-status hg-status))
+ (hg-mode-line)
+ (if (and (not old-status) hg-status)
+ (hg-mode)))))
+
+(add-hook 'after-save-hook 'hg-after-save-hook)
+
+
+;;; User interface functions.
+
+(defun hg-help-overview ()
+ "This is an overview of the Mercurial SCM mode for Emacs.
+
+You can find the source code, license (GPLv2+), and credits for this
+code by typing `M-x find-library mercurial RET'."
+ (interactive)
+ (hg-view-output ("Mercurial Help Overview")
+ (insert (documentation 'hg-help-overview))
+ (let ((pos (point)))
+ (insert (documentation 'hg-mode))
+ (goto-char pos)
+ (end-of-line 1)
+ (delete-region pos (point)))
+ (let ((hg-root-dir (hg-root)))
+ (if (not hg-root-dir)
+ (error "error: %s: directory is not part of a Mercurial repository."
+ default-directory)
+ (cd hg-root-dir)))))
+
+(defun hg-fix-paths ()
+ "Fix paths reported by some Mercurial commands."
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward " \\.\\.." nil t)
+ (replace-match " " nil nil))))
+
+(defun hg-add (path)
+ "Add PATH to the Mercurial repository on the next commit.
+With a prefix argument, prompt for the path to add."
+ (interactive (list (hg-read-file-name " to add")))
+ (let ((buf (current-buffer))
+ (update (equal buffer-file-name path)))
+ (hg-view-output (hg-output-buffer-name)
+ (apply 'call-process (hg-binary) nil t nil (list "add" path))
+ (hg-fix-paths)
+ (goto-char (point-min))
+ (cd (hg-root path)))
+ (when update
+ (unless vc-make-backup-files
+ (set (make-local-variable 'backup-inhibited) t))
+ (with-current-buffer buf
+ (hg-mode-line)))))
+
+(defun hg-addremove ()
+ (interactive)
+ (error "not implemented"))
+
+(defun hg-annotate ()
+ (interactive)
+ (error "not implemented"))
+
+(defun hg-commit-toggle-file (pos)
+ "Toggle whether or not the file at POS will be committed."
+ (interactive "d")
+ (save-excursion
+ (goto-char pos)
+ (let (face
+ (inhibit-read-only t)
+ bol)
+ (beginning-of-line)
+ (setq bol (+ (point) 4))
+ (setq face (get-text-property bol 'face))
+ (end-of-line)
+ (if (eq face 'bold)
+ (progn
+ (remove-text-properties bol (point) '(face nil))
+ (message "%s will not be committed"
+ (buffer-substring bol (point))))
+ (add-text-properties bol (point) '(face bold))
+ (message "%s will be committed"
+ (buffer-substring bol (point)))))))
+
+(defun hg-commit-mouse-clicked (event)
+ "Toggle whether or not the file at POS will be committed."
+ (interactive "@e")
+ (hg-commit-toggle-file (hg-event-point event)))
+
+(defun hg-commit-kill ()
+ "Kill the commit currently being prepared."
+ (interactive)
+ (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? "))
+ (let ((buf hg-prev-buffer))
+ (kill-buffer nil)
+ (switch-to-buffer buf))))
+
+(defun hg-commit-finish ()
+ "Finish preparing a commit, and perform the actual commit.
+The hook hg-pre-commit-hook is run before anything else is done. If
+the commit message is empty and hg-commit-allow-empty-message is nil,
+an error is raised. If the list of files to commit is empty and
+hg-commit-allow-empty-file-list is nil, an error is raised."
+ (interactive)
+ (let ((root hg-root))
+ (save-excursion
+ (run-hooks 'hg-pre-commit-hook)
+ (goto-char (point-min))
+ (search-forward hg-commit-message-start)
+ (let (message files)
+ (let ((start (point)))
+ (goto-char (point-max))
+ (search-backward hg-commit-message-end)
+ (setq message (hg-strip (buffer-substring start (point)))))
+ (when (and (= (length message) 0)
+ (not hg-commit-allow-empty-message))
+ (error "Cannot proceed - commit message is empty"))
+ (forward-line 1)
+ (beginning-of-line)
+ (while (< (point) (point-max))
+ (let ((pos (+ (point) 4)))
+ (end-of-line)
+ (when (eq (get-text-property pos 'face) 'bold)
+ (end-of-line)
+ (setq files (cons (buffer-substring pos (point)) files))))
+ (forward-line 1))
+ (when (and (= (length files) 0)
+ (not hg-commit-allow-empty-file-list))
+ (error "Cannot proceed - no files to commit"))
+ (setq message (concat message "\n"))
+ (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files))
+ (let ((buf hg-prev-buffer))
+ (kill-buffer nil)
+ (switch-to-buffer buf))
+ (hg-update-mode-lines root))))
+
+(defun hg-commit-mode ()
+ "Mode for describing a commit of changes to a Mercurial repository.
+This involves two actions: describing the changes with a commit
+message, and choosing the files to commit.
+
+To describe the commit, simply type some text in the designated area.
+
+By default, all modified, added and removed files are selected for
+committing. Files that will be committed are displayed in bold face\;
+those that will not are displayed in normal face.
+
+To toggle whether a file will be committed, move the cursor over a
+particular file and hit space or return. Alternatively, middle click
+on the file.
+
+Key bindings
+------------
+\\[hg-commit-finish] proceed with commit
+\\[hg-commit-kill] kill commit
+
+\\[hg-diff-repo] view diff of pending changes"
+ (interactive)
+ (use-local-map hg-commit-mode-map)
+ (set-syntax-table text-mode-syntax-table)
+ (setq local-abbrev-table text-mode-abbrev-table
+ major-mode 'hg-commit-mode
+ mode-name "Hg-Commit")
+ (set-buffer-modified-p nil)
+ (setq buffer-undo-list nil)
+ (run-hooks 'text-mode-hook 'hg-commit-mode-hook))
+
+(defun hg-commit-start ()
+ "Prepare a commit of changes to the repository containing the current file."
+ (interactive)
+ (while hg-prev-buffer
+ (set-buffer hg-prev-buffer))
+ (let ((root (hg-root))
+ (prev-buffer (current-buffer))
+ modified-files)
+ (unless root
+ (error "Cannot commit outside a repository!"))
+ (hg-sync-buffers root)
+ (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm")))
+ (when (and (= (length modified-files) 0)
+ (not hg-commit-allow-empty-file-list))
+ (error "No pending changes to commit"))
+ (let* ((buf-name (format "*Mercurial: Commit %s*" root)))
+ (pop-to-buffer (get-buffer-create buf-name))
+ (when (= (point-min) (point-max))
+ (set (make-local-variable 'hg-root) root)
+ (setq hg-prev-buffer prev-buffer)
+ (insert "\n")
+ (let ((bol (point)))
+ (insert hg-commit-message-end)
+ (add-text-properties bol (point) '(face bold-italic)))
+ (let ((file-area (point)))
+ (insert modified-files)
+ (goto-char file-area)
+ (while (< (point) (point-max))
+ (let ((bol (point)))
+ (forward-char 1)
+ (insert " ")
+ (end-of-line)
+ (add-text-properties (+ bol 4) (point)
+ '(face bold mouse-face highlight)))
+ (forward-line 1))
+ (goto-char file-area)
+ (add-text-properties (point) (point-max)
+ `(keymap ,hg-commit-mode-file-map))
+ (goto-char (point-min))
+ (insert hg-commit-message-start)
+ (add-text-properties (point-min) (point) '(face bold-italic))
+ (insert "\n\n")
+ (forward-line -1)
+ (save-excursion
+ (goto-char (point-max))
+ (search-backward hg-commit-message-end)
+ (add-text-properties (match-beginning 0) (point-max)
+ '(read-only t))
+ (goto-char (point-min))
+ (search-forward hg-commit-message-start)
+ (add-text-properties (match-beginning 0) (match-end 0)
+ '(read-only t)))
+ (hg-commit-mode)
+ (cd root))))))
+
+(defun hg-diff (path &optional rev1 rev2)
+ "Show the differences between REV1 and REV2 of PATH.
+When called interactively, the default behaviour is to treat REV1 as
+the \"parent\" revision, REV2 as the current edited version of the file, and
+PATH as the file edited in the current buffer.
+With a prefix argument, prompt for all of these."
+ (interactive (list (hg-read-file-name " to diff")
+ (let ((rev1 (hg-read-rev " to start with" 'parent)))
+ (and (not (eq rev1 'parent)) rev1))
+ (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
+ (and (not (eq rev2 'working-dir)) rev2))))
+ (hg-sync-buffers path)
+ (let ((a-path (hg-abbrev-file-name path))
+ ;; none revision is specified explicitly
+ (none (and (not rev1) (not rev2)))
+ ;; only one revision is specified explicitly
+ (one (or (and (or (equal rev1 rev2) (not rev2)) rev1)
+ (and (not rev1) rev2)))
+ diff)
+ (hg-view-output ((cond
+ (none
+ (format "Mercurial: Diff against parent of %s" a-path))
+ (one
+ (format "Mercurial: Diff of rev %s of %s" one a-path))
+ (t
+ (format "Mercurial: Diff from rev %s to %s of %s"
+ rev1 rev2 a-path))))
+ (cond
+ (none
+ (call-process (hg-binary) nil t nil "diff" path))
+ (one
+ (call-process (hg-binary) nil t nil "diff" "-r" one path))
+ (t
+ (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path)))
+ (diff-mode)
+ (setq diff (not (= (point-min) (point-max))))
+ (font-lock-fontify-buffer)
+ (cd (hg-root path)))
+ diff))
+
+(defun hg-diff-repo (path &optional rev1 rev2)
+ "Show the differences between REV1 and REV2 of repository containing PATH.
+When called interactively, the default behaviour is to treat REV1 as
+the \"parent\" revision, REV2 as the current edited version of the file, and
+PATH as the `hg-root' of the current buffer.
+With a prefix argument, prompt for all of these."
+ (interactive (list (hg-read-file-name " to diff")
+ (let ((rev1 (hg-read-rev " to start with" 'parent)))
+ (and (not (eq rev1 'parent)) rev1))
+ (let ((rev2 (hg-read-rev " to end with" 'working-dir)))
+ (and (not (eq rev2 'working-dir)) rev2))))
+ (hg-diff (hg-root path) rev1 rev2))
+
+(defun hg-forget (path)
+ "Lose track of PATH, which has been added, but not yet committed.
+This will prevent the file from being incorporated into the Mercurial
+repository on the next commit.
+With a prefix argument, prompt for the path to forget."
+ (interactive (list (hg-read-file-name " to forget")))
+ (let ((buf (current-buffer))
+ (update (equal buffer-file-name path)))
+ (hg-view-output (hg-output-buffer-name)
+ (apply 'call-process (hg-binary) nil t nil (list "forget" path))
+ ;; "hg forget" shows pathes relative NOT TO ROOT BUT TO REPOSITORY
+ (hg-fix-paths)
+ (goto-char (point-min))
+ (cd (hg-root path)))
+ (when update
+ (with-current-buffer buf
+ (when (local-variable-p 'backup-inhibited)
+ (kill-local-variable 'backup-inhibited))
+ (hg-mode-line)))))
+
+(defun hg-incoming (&optional repo)
+ "Display changesets present in REPO that are not present locally."
+ (interactive (list (hg-read-repo-name " where changes would come from")))
+ (hg-view-output ((format "Mercurial: Incoming from %s to %s"
+ (hg-abbrev-file-name (hg-root))
+ (hg-abbrev-file-name
+ (or repo hg-incoming-repository))))
+ (call-process (hg-binary) nil t nil "incoming"
+ (or repo hg-incoming-repository))
+ (hg-log-mode)
+ (cd (hg-root))))
+
+(defun hg-init ()
+ (interactive)
+ (error "not implemented"))
+
+(defun hg-log-mode ()
+ "Mode for viewing a Mercurial change log."
+ (goto-char (point-min))
+ (when (looking-at "^searching for changes.*$")
+ (delete-region (match-beginning 0) (match-end 0)))
+ (run-hooks 'hg-log-mode-hook))
+
+(defun hg-log (path &optional rev1 rev2 log-limit)
+ "Display the revision history of PATH.
+History is displayed between REV1 and REV2.
+Number of displayed changesets is limited to LOG-LIMIT.
+REV1 defaults to the tip, while REV2 defaults to 0.
+LOG-LIMIT defaults to `hg-log-limit'.
+With a prefix argument, prompt for each parameter."
+ (interactive (list (hg-read-file-name " to log")
+ (hg-read-rev " to start with"
+ "tip")
+ (hg-read-rev " to end with"
+ "0")
+ (hg-read-number "Output limited to: "
+ hg-log-limit)))
+ (let ((a-path (hg-abbrev-file-name path))
+ (r1 (or rev1 "tip"))
+ (r2 (or rev2 "0"))
+ (limit (format "%d" (or log-limit hg-log-limit))))
+ (hg-view-output ((if (equal r1 r2)
+ (format "Mercurial: Log of rev %s of %s" rev1 a-path)
+ (format
+ "Mercurial: at most %s log(s) from rev %s to %s of %s"
+ limit r1 r2 a-path)))
+ (eval (list* 'call-process (hg-binary) nil t nil
+ "log"
+ "-r" (format "%s:%s" r1 r2)
+ "-l" limit
+ (if (> (length path) (length (hg-root path)))
+ (cons path nil)
+ nil)))
+ (hg-log-mode)
+ (cd (hg-root path)))))
+
+(defun hg-log-repo (path &optional rev1 rev2 log-limit)
+ "Display the revision history of the repository containing PATH.
+History is displayed between REV1 and REV2.
+Number of displayed changesets is limited to LOG-LIMIT,
+REV1 defaults to the tip, while REV2 defaults to 0.
+LOG-LIMIT defaults to `hg-log-limit'.
+With a prefix argument, prompt for each parameter."
+ (interactive (list (hg-read-file-name " to log")
+ (hg-read-rev " to start with"
+ "tip")
+ (hg-read-rev " to end with"
+ "0")
+ (hg-read-number "Output limited to: "
+ hg-log-limit)))
+ (hg-log (hg-root path) rev1 rev2 log-limit))
+
+(defun hg-outgoing (&optional repo)
+ "Display changesets present locally that are not present in REPO."
+ (interactive (list (hg-read-repo-name " where changes would go to" nil
+ hg-outgoing-repository)))
+ (hg-view-output ((format "Mercurial: Outgoing from %s to %s"
+ (hg-abbrev-file-name (hg-root))
+ (hg-abbrev-file-name
+ (or repo hg-outgoing-repository))))
+ (call-process (hg-binary) nil t nil "outgoing"
+ (or repo hg-outgoing-repository))
+ (hg-log-mode)
+ (cd (hg-root))))
+
+(defun hg-pull (&optional repo)
+ "Pull changes from repository REPO.
+This does not update the working directory."
+ (interactive (list (hg-read-repo-name " to pull from")))
+ (hg-view-output ((format "Mercurial: Pull to %s from %s"
+ (hg-abbrev-file-name (hg-root))
+ (hg-abbrev-file-name
+ (or repo hg-incoming-repository))))
+ (call-process (hg-binary) nil t nil "pull"
+ (or repo hg-incoming-repository))
+ (cd (hg-root))))
+
+(defun hg-push (&optional repo)
+ "Push changes to repository REPO."
+ (interactive (list (hg-read-repo-name " to push to")))
+ (hg-view-output ((format "Mercurial: Push from %s to %s"
+ (hg-abbrev-file-name (hg-root))
+ (hg-abbrev-file-name
+ (or repo hg-outgoing-repository))))
+ (call-process (hg-binary) nil t nil "push"
+ (or repo hg-outgoing-repository))
+ (cd (hg-root))))
+
+(defun hg-revert-buffer-internal ()
+ (let ((ctx (hg-buffer-context)))
+ (message "Reverting %s..." buffer-file-name)
+ (hg-run0 "revert" buffer-file-name)
+ (revert-buffer t t t)
+ (hg-restore-context ctx)
+ (hg-mode-line)
+ (message "Reverting %s...done" buffer-file-name)))
+
+(defun hg-revert-buffer ()
+ "Revert current buffer's file back to the latest committed version.
+If the file has not changed, nothing happens. Otherwise, this
+displays a diff and asks for confirmation before reverting."
+ (interactive)
+ (let ((vc-suppress-confirm nil)
+ (obuf (current-buffer))
+ diff)
+ (vc-buffer-sync)
+ (unwind-protect
+ (setq diff (hg-diff buffer-file-name))
+ (when diff
+ (unless (yes-or-no-p "Discard changes? ")
+ (error "Revert cancelled")))
+ (when diff
+ (let ((buf (current-buffer)))
+ (delete-window (selected-window))
+ (kill-buffer buf))))
+ (set-buffer obuf)
+ (when diff
+ (hg-revert-buffer-internal))))
+
+(defun hg-root (&optional path)
+ "Return the root of the repository that contains the given path.
+If the path is outside a repository, return nil.
+When called interactively, the root is printed. A prefix argument
+prompts for a path to check."
+ (interactive (list (hg-read-file-name)))
+ (if (or path (not hg-root))
+ (let ((root (do ((prev nil dir)
+ (dir (file-name-directory
+ (or
+ path
+ buffer-file-name
+ (expand-file-name default-directory)))
+ (file-name-directory (directory-file-name dir))))
+ ((equal prev dir))
+ (when (file-directory-p (concat dir ".hg"))
+ (return dir)))))
+ (when (interactive-p)
+ (if root
+ (message "The root of this repository is `%s'." root)
+ (message "The path `%s' is not in a Mercurial repository."
+ (hg-abbrev-file-name path))))
+ root)
+ hg-root))
+
+(defun hg-cwd (&optional path)
+ "Return the current directory of PATH within the repository."
+ (do ((stack nil (cons (file-name-nondirectory
+ (directory-file-name dir))
+ stack))
+ (prev nil dir)
+ (dir (file-name-directory (or path buffer-file-name
+ (expand-file-name default-directory)))
+ (file-name-directory (directory-file-name dir))))
+ ((equal prev dir))
+ (when (file-directory-p (concat dir ".hg"))
+ (let ((cwd (mapconcat 'identity stack "/")))
+ (unless (equal cwd "")
+ (return (file-name-as-directory cwd)))))))
+
+(defun hg-status (path)
+ "Print revision control status of a file or directory.
+With prefix argument, prompt for the path to give status for.
+Names are displayed relative to the repository root."
+ (interactive (list (hg-read-file-name " for status" (hg-root))))
+ (let ((root (hg-root)))
+ (hg-view-output ((format "Mercurial: Status of %s in %s"
+ (let ((name (substring (expand-file-name path)
+ (length root))))
+ (if (> (length name) 0)
+ name
+ "*"))
+ (hg-abbrev-file-name root)))
+ (apply 'call-process (hg-binary) nil t nil
+ (list "--cwd" root "status" path))
+ (cd (hg-root path)))))
+
+(defun hg-undo ()
+ (interactive)
+ (error "not implemented"))
+
+(defun hg-update ()
+ (interactive)
+ (error "not implemented"))
+
+(defun hg-version-other-window (rev)
+ "Visit version REV of the current file in another window.
+If the current file is named `F', the version is named `F.~REV~'.
+If `F.~REV~' already exists, use it instead of checking it out again."
+ (interactive "sVersion to visit (default is workfile version): ")
+ (let* ((file buffer-file-name)
+ (version (if (string-equal rev "")
+ "tip"
+ rev))
+ (automatic-backup (vc-version-backup-file-name file version))
+ (manual-backup (vc-version-backup-file-name file version 'manual)))
+ (unless (file-exists-p manual-backup)
+ (if (file-exists-p automatic-backup)
+ (rename-file automatic-backup manual-backup nil)
+ (hg-run0 "-q" "cat" "-r" version "-o" manual-backup file)))
+ (find-file-other-window manual-backup)))
+
+
+(provide 'mercurial)
+
+
+;;; Local Variables:
+;;; prompt-to-byte-compile: nil
+;;; end:
diff --git a/contrib/mercurial.spec b/contrib/mercurial.spec
new file mode 100755
index 0000000..853eac5
--- /dev/null
+++ b/contrib/mercurial.spec
@@ -0,0 +1,87 @@
+Summary: A fast, lightweight Source Control Management system
+Name: mercurial
+Version: snapshot
+Release: 0
+License: GPLv2+
+Group: Development/Tools
+URL: http://mercurial.selenic.com/
+Source0: http://mercurial.selenic.com/release/%{name}-%{version}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
+
+# From the README:
+#
+# Note: some distributions fails to include bits of distutils by
+# default, you'll need python-dev to install. You'll also need a C
+# compiler and a 3-way merge tool like merge, tkdiff, or kdiff3.
+#
+# python-devel provides an adequate python-dev. The merge tool is a
+# run-time dependency.
+#
+BuildRequires: python >= 2.4, python-devel, make, gcc, python-docutils >= 0.5, gettext
+Provides: hg = %{version}-%{release}
+Requires: python >= 2.4
+# The hgk extension uses the wish tcl interpreter, but we don't enforce it
+#Requires: tk
+
+%define pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
+%define emacs_lispdir %{_datadir}/emacs/site-lisp
+
+%description
+Mercurial is a fast, lightweight source control management system designed
+for efficient handling of very large distributed projects.
+
+%prep
+%setup -q
+
+%build
+make all
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir}
+
+install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}
+install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}
+
+bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
+mkdir -p $bash_completion_dir
+install -m 644 contrib/bash_completion $bash_completion_dir/mercurial.sh
+
+zsh_completion_dir=$RPM_BUILD_ROOT%{_datadir}/zsh/site-functions
+mkdir -p $zsh_completion_dir
+install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial
+
+mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir}
+install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}
+install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}
+
+mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/mercurial/hgrc.d
+install -m 644 contrib/mergetools.hgrc $RPM_BUILD_ROOT%{_sysconfdir}/mercurial/hgrc.d/mergetools.rc
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root,-)
+%doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi
+%doc %attr(644,root,root) %{_mandir}/man?/hg*
+%doc %attr(644,root,root) contrib/*.svg contrib/sample.hgrc
+%dir %{_datadir}/zsh/
+%dir %{_datadir}/zsh/site-functions/
+%{_datadir}/zsh/site-functions/_mercurial
+%dir %{_datadir}/emacs/site-lisp/
+%{_datadir}/emacs/site-lisp/mercurial.el
+%{_datadir}/emacs/site-lisp/mq.el
+%{_bindir}/hg
+%{_bindir}/hgk
+%{_bindir}/hg-ssh
+%dir %{_sysconfdir}/bash_completion.d/
+%config(noreplace) %{_sysconfdir}/bash_completion.d/mercurial.sh
+%dir %{_sysconfdir}/mercurial
+%dir %{_sysconfdir}/mercurial/hgrc.d
+%config(noreplace) %{_sysconfdir}/mercurial/hgrc.d/mergetools.rc
+%if "%{?pythonver}" != "2.4"
+%{_libdir}/python%{pythonver}/site-packages/%{name}-*-py%{pythonver}.egg-info
+%endif
+%{_libdir}/python%{pythonver}/site-packages/%{name}
+%{_libdir}/python%{pythonver}/site-packages/hgext
diff --git a/contrib/mergetools.hgrc b/contrib/mergetools.hgrc
new file mode 100644
index 0000000..b2c2e31
--- /dev/null
+++ b/contrib/mergetools.hgrc
@@ -0,0 +1,123 @@
+# Some default global settings for common merge tools
+
+[merge-tools]
+kdiff3.args=--auto --L1 base --L2 local --L3 other $base $local $other -o $output
+kdiff3.regkey=Software\KDiff3
+kdiff3.regkeyalt=Software\Wow6432Node\KDiff3
+kdiff3.regappend=\kdiff3.exe
+kdiff3.fixeol=True
+kdiff3.gui=True
+kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
+
+gvimdiff.args=--nofork -d -g -O $local $other $base
+gvimdiff.regkey=Software\Vim\GVim
+gvimdiff.regkeyalt=Software\Wow6432Node\Vim\GVim
+gvimdiff.regname=path
+gvimdiff.priority=-9
+
+vimdiff.args=$local $other $base
+vimdiff.priority=-10
+
+merge.checkconflicts=True
+merge.priority=-100
+
+gpyfm.gui=True
+
+meld.gui=True
+meld.args=--label='local' $local --label='base' $base --label='other' $other
+meld.diffargs=-a --label='$plabel1' $parent --label='$clabel' $child
+
+tkdiff.args=$local $other -a $base -o $output
+tkdiff.gui=True
+tkdiff.priority=-8
+tkdiff.diffargs=-L '$plabel1' $parent -L '$clabel' $child
+
+xxdiff.args=--show-merged-pane --exit-with-merge-status --title1 local --title2 base --title3 other --merged-filename $output --merge $local $base $other
+xxdiff.gui=True
+xxdiff.priority=-8
+xxdiff.diffargs=--title1 '$plabel1' $parent --title2 '$clabel' $child
+
+diffmerge.regkey=Software\SourceGear\SourceGear DiffMerge\
+diffmerge.regkeyalt=Software\Wow6432Node\SourceGear\SourceGear DiffMerge\
+diffmerge.regname=Location
+diffmerge.priority=-7
+diffmerge.args=-nosplash -merge -title1=local -title2=merged -title3=other $local $base $other -result=$output
+diffmerge.checkchanged=True
+diffmerge.gui=True
+diffmerge.diffargs=--nosplash --title1='$plabel1' --title2='$clabel' $parent $child
+
+p4merge.args=$base $local $other $output
+p4merge.regkey=Software\Perforce\Environment
+p4merge.regkeyalt=Software\Wow6432Node\Perforce\Environment
+p4merge.regname=P4INSTROOT
+p4merge.regappend=\p4merge.exe
+p4merge.gui=True
+p4merge.priority=-8
+p4merge.diffargs=$parent $child
+
+tortoisemerge.args=/base:$base /mine:$local /theirs:$other /merged:$output
+tortoisemerge.regkey=Software\TortoiseSVN
+tortoisemerge.regkeyalt=Software\Wow6432Node\TortoiseSVN
+tortoisemerge.checkchanged=True
+tortoisemerge.gui=True
+tortoisemerge.priority=-8
+tortoisemerge.diffargs=/base:$parent /mine:$child /basename:'$plabel1' /minename:'$clabel'
+
+ecmerge.args=$base $local $other --mode=merge3 --title0=base --title1=local --title2=other --to=$output
+ecmerge.regkey=Software\Elli\xc3\xa9 Computing\Merge
+ecmerge.regkeyalt=Software\Wow6432Node\Elli\xc3\xa9 Computing\Merge
+ecmerge.gui=True
+ecmerge.diffargs=$parent $child --mode=diff2 --title1='$plabel1' --title2='$clabel'
+
+filemerge.executable=/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge
+filemerge.args=-left $other -right $local -ancestor $base -merge $output
+filemerge.gui=True
+
+; Windows version of Beyond Compare
+beyondcompare3.args=$local $other $base $output /ro /lefttitle=local /centertitle=base /righttitle=other /automerge /reviewconflicts /solo
+beyondcompare3.regkey=Software\Scooter Software\Beyond Compare 3
+beyondcompare3.regname=ExePath
+beyondcompare3.gui=True
+beyondcompare3.priority=-2
+beyondcompare3.diffargs=/lro /lefttitle='$plabel1' /righttitle='$clabel' /solo /expandall $parent $child
+
+; Linux version of Beyond Compare
+bcompare.args=$local $other $base -mergeoutput=$output -ro -lefttitle=parent1 -centertitle=base -righttitle=parent2 -outputtitle=merged -automerge -reviewconflicts -solo
+bcompare.premerge=False
+bcompare.gui=True
+bcompare.priority=-1
+bcompare.diffargs=-lro -lefttitle='$plabel1' -righttitle='$clabel' -solo -expandall $parent $child
+
+winmerge.args=/e /x /wl /ub /dl other /dr local $other $local $output
+winmerge.regkey=Software\Thingamahoochie\WinMerge
+winmerge.regkeyalt=Software\Wow6432Node\Thingamahoochie\WinMerge\
+winmerge.regname=Executable
+winmerge.checkchanged=True
+winmerge.gui=True
+winmerge.priority=-10
+winmerge.diffargs=/r /e /x /ub /wl /dl '$plabel1' /dr '$clabel' $parent $child
+
+araxis.regkey=SOFTWARE\Classes\TypeLib\{46799e0a-7bd1-4330-911c-9660bb964ea2}\7.0\HELPDIR
+araxis.regappend=\ConsoleCompare.exe
+araxis.priority=-2
+araxis.args=/3 /a2 /wait /merge /title1:"Other" /title2:"Base" /title3:"Local :"$local $other $base $local $output
+araxis.premerge=False
+araxis.checkconflict=True
+araxis.binary=True
+araxis.gui=True
+araxis.diffargs=/2 /wait /title1:"$plabel1" /title2:"$clabel" $parent $child
+
+diffuse.priority=-3
+diffuse.args=$local $base $other
+diffuse.gui=True
+diffuse.diffargs=$parent $child
+
+UltraCompare.regkey=Software\Microsoft\Windows\CurrentVersion\App Paths\UC.exe
+UltraCompare.regkeyalt=Software\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths\UC.exe
+UltraCompare.args = $base $local $other -title1 base -title3 other
+UltraCompare.priority = -2
+UltraCompare.gui = True
+UltraCompare.binary = True
+UltraCompare.checkconflicts = True
+UltraCompare.checkchanged = True
+UltraCompare.diffargs=$child $parent -title1 $clabel -title2 $plabel1
diff --git a/contrib/mq.el b/contrib/mq.el
new file mode 100644
index 0000000..dbc9165
--- /dev/null
+++ b/contrib/mq.el
@@ -0,0 +1,417 @@
+;;; mq.el --- Emacs support for Mercurial Queues
+
+;; Copyright (C) 2006 Bryan O'Sullivan
+
+;; Author: Bryan O'Sullivan <bos@serpentine.com>
+
+;; mq.el is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License version 2 or any
+;; later version.
+
+;; mq.el is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with mq.el, GNU Emacs, or XEmacs; see the file COPYING (`C-h
+;; C-l'). If not, see <http://www.gnu.org/licenses/>.
+
+(eval-when-compile (require 'cl))
+(require 'mercurial)
+
+
+(defcustom mq-mode-hook nil
+ "Hook run when a buffer enters mq-mode."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom mq-global-prefix "\C-cq"
+ "The global prefix for Mercurial Queues keymap bindings."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom mq-edit-mode-hook nil
+ "Hook run after a buffer is populated to edit a patch description."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom mq-edit-finish-hook nil
+ "Hook run before a patch description is finished up with."
+ :type 'sexp
+ :group 'mercurial)
+
+(defcustom mq-signoff-address nil
+ "Address with which to sign off on a patch."
+ :type 'string
+ :group 'mercurial)
+
+
+;;; Internal variables.
+
+(defvar mq-mode nil
+ "Is this file managed by MQ?")
+(make-variable-buffer-local 'mq-mode)
+(put 'mq-mode 'permanent-local t)
+
+(defvar mq-patch-history nil)
+
+(defvar mq-top-patch '(nil))
+
+(defvar mq-prev-buffer nil)
+(make-variable-buffer-local 'mq-prev-buffer)
+(put 'mq-prev-buffer 'permanent-local t)
+
+(defvar mq-top nil)
+(make-variable-buffer-local 'mq-top)
+(put 'mq-top 'permanent-local t)
+
+;;; Global keymap.
+
+(defvar mq-global-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "." 'mq-push)
+ (define-key map ">" 'mq-push-all)
+ (define-key map "," 'mq-pop)
+ (define-key map "<" 'mq-pop-all)
+ (define-key map "=" 'mq-diff)
+ (define-key map "r" 'mq-refresh)
+ (define-key map "e" 'mq-refresh-edit)
+ (define-key map "i" 'mq-new)
+ (define-key map "n" 'mq-next)
+ (define-key map "o" 'mq-signoff)
+ (define-key map "p" 'mq-previous)
+ (define-key map "s" 'mq-edit-series)
+ (define-key map "t" 'mq-top)
+ map))
+
+(global-set-key mq-global-prefix mq-global-map)
+
+(add-minor-mode 'mq-mode 'mq-mode)
+
+
+;;; Refresh edit mode keymap.
+
+(defvar mq-edit-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "\C-c\C-c" 'mq-edit-finish)
+ (define-key map "\C-c\C-k" 'mq-edit-kill)
+ (define-key map "\C-c\C-s" 'mq-signoff)
+ map))
+
+
+;;; Helper functions.
+
+(defun mq-read-patch-name (&optional source prompt force)
+ "Read a patch name to use with a command.
+May return nil, meaning \"use the default\"."
+ (let ((patches (split-string
+ (hg-chomp (hg-run0 (or source "qseries"))) "\n")))
+ (when force
+ (completing-read (format "Patch%s: " (or prompt ""))
+ (mapcar (lambda (x) (cons x x)) patches)
+ nil
+ nil
+ nil
+ 'mq-patch-history))))
+
+(defun mq-refresh-buffers (root)
+ (save-excursion
+ (dolist (buf (hg-buffers-visiting-repo root))
+ (when (not (verify-visited-file-modtime buf))
+ (set-buffer buf)
+ (let ((ctx (hg-buffer-context)))
+ (message "Refreshing %s..." (buffer-name))
+ (revert-buffer t t t)
+ (hg-restore-context ctx)
+ (message "Refreshing %s...done" (buffer-name))))))
+ (hg-update-mode-lines root)
+ (mq-update-mode-lines root))
+
+(defun mq-last-line ()
+ (goto-char (point-max))
+ (beginning-of-line)
+ (when (looking-at "^$")
+ (forward-line -1))
+ (let ((bol (point)))
+ (end-of-line)
+ (let ((line (buffer-substring bol (point))))
+ (when (> (length line) 0)
+ line))))
+
+(defun mq-push (&optional patch)
+ "Push patches until PATCH is reached.
+If PATCH is nil, push at most one patch."
+ (interactive (list (mq-read-patch-name "qunapplied" " to push"
+ current-prefix-arg)))
+ (let ((root (hg-root))
+ (prev-buf (current-buffer))
+ last-line ok)
+ (unless root
+ (error "Cannot push outside a repository!"))
+ (hg-sync-buffers root)
+ (let ((buf-name (format "MQ: Push %s" (or patch "next patch"))))
+ (kill-buffer (get-buffer-create buf-name))
+ (split-window-vertically)
+ (other-window 1)
+ (switch-to-buffer (get-buffer-create buf-name))
+ (cd root)
+ (message "Pushing...")
+ (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpush"
+ (if patch (list patch))))
+ last-line (mq-last-line))
+ (let ((lines (count-lines (point-min) (point-max))))
+ (if (or (<= lines 1)
+ (and (equal lines 2) (string-match "Now at:" last-line)))
+ (progn
+ (kill-buffer (current-buffer))
+ (delete-window))
+ (hg-view-mode prev-buf))))
+ (mq-refresh-buffers root)
+ (sit-for 0)
+ (when last-line
+ (if ok
+ (message "Pushing... %s" last-line)
+ (error "Pushing... %s" last-line)))))
+
+(defun mq-push-all ()
+ "Push patches until all are applied."
+ (interactive)
+ (mq-push "-a"))
+
+(defun mq-pop (&optional patch)
+ "Pop patches until PATCH is reached.
+If PATCH is nil, pop at most one patch."
+ (interactive (list (mq-read-patch-name "qapplied" " to pop to"
+ current-prefix-arg)))
+ (let ((root (hg-root))
+ last-line ok)
+ (unless root
+ (error "Cannot pop outside a repository!"))
+ (hg-sync-buffers root)
+ (set-buffer (generate-new-buffer "qpop"))
+ (cd root)
+ (message "Popping...")
+ (setq ok (= 0 (apply 'call-process (hg-binary) nil t t "qpop"
+ (if patch (list patch))))
+ last-line (mq-last-line))
+ (kill-buffer (current-buffer))
+ (mq-refresh-buffers root)
+ (sit-for 0)
+ (when last-line
+ (if ok
+ (message "Popping... %s" last-line)
+ (error "Popping... %s" last-line)))))
+
+(defun mq-pop-all ()
+ "Push patches until none are applied."
+ (interactive)
+ (mq-pop "-a"))
+
+(defun mq-refresh-internal (root &rest args)
+ (hg-sync-buffers root)
+ (let ((patch (mq-patch-info "qtop")))
+ (message "Refreshing %s..." patch)
+ (let ((ret (apply 'hg-run "qrefresh" args)))
+ (if (equal (car ret) 0)
+ (message "Refreshing %s... done." patch)
+ (error "Refreshing %s... %s" patch (hg-chomp (cdr ret)))))))
+
+(defun mq-refresh (&optional git)
+ "Refresh the topmost applied patch.
+With a prefix argument, generate a git-compatible patch."
+ (interactive "P")
+ (let ((root (hg-root)))
+ (unless root
+ (error "Cannot refresh outside of a repository!"))
+ (apply 'mq-refresh-internal root (if git '("--git")))))
+
+(defun mq-patch-info (cmd &optional msg)
+ (let* ((ret (hg-run cmd))
+ (info (hg-chomp (cdr ret))))
+ (if (equal (car ret) 0)
+ (if msg
+ (message "%s patch: %s" msg info)
+ info)
+ (error "%s" info))))
+
+(defun mq-top ()
+ "Print the name of the topmost applied patch."
+ (interactive)
+ (mq-patch-info "qtop" "Top"))
+
+(defun mq-next ()
+ "Print the name of the next patch to be pushed."
+ (interactive)
+ (mq-patch-info "qnext" "Next"))
+
+(defun mq-previous ()
+ "Print the name of the first patch below the topmost applied patch.
+This would become the active patch if popped to."
+ (interactive)
+ (mq-patch-info "qprev" "Previous"))
+
+(defun mq-edit-finish ()
+ "Finish editing the description of this patch, and refresh the patch."
+ (interactive)
+ (unless (equal (mq-patch-info "qtop") mq-top)
+ (error "Topmost patch has changed!"))
+ (hg-sync-buffers hg-root)
+ (run-hooks 'mq-edit-finish-hook)
+ (mq-refresh-internal hg-root "-m" (buffer-substring (point-min) (point-max)))
+ (let ((buf mq-prev-buffer))
+ (kill-buffer nil)
+ (switch-to-buffer buf)))
+
+(defun mq-edit-kill ()
+ "Kill the edit currently being prepared."
+ (interactive)
+ (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this edit? "))
+ (let ((buf mq-prev-buffer))
+ (kill-buffer nil)
+ (switch-to-buffer buf))))
+
+(defun mq-get-top (root)
+ (let ((entry (assoc root mq-top-patch)))
+ (if entry
+ (cdr entry))))
+
+(defun mq-set-top (root patch)
+ (let ((entry (assoc root mq-top-patch)))
+ (if entry
+ (if patch
+ (setcdr entry patch)
+ (setq mq-top-patch (delq entry mq-top-patch)))
+ (setq mq-top-patch (cons (cons root patch) mq-top-patch)))))
+
+(defun mq-update-mode-lines (root)
+ (let ((cwd default-directory))
+ (cd root)
+ (condition-case nil
+ (mq-set-top root (mq-patch-info "qtop"))
+ (error (mq-set-top root nil)))
+ (cd cwd))
+ (let ((patch (mq-get-top root)))
+ (save-excursion
+ (dolist (buf (hg-buffers-visiting-repo root))
+ (set-buffer buf)
+ (if mq-mode
+ (setq mq-mode (or (and patch (concat " MQ:" patch)) " MQ")))))))
+
+(defun mq-mode (&optional arg)
+ "Minor mode for Mercurial repositories with an MQ patch queue"
+ (interactive "i")
+ (cond ((hg-root)
+ (setq mq-mode (if (null arg) (not mq-mode)
+ arg))
+ (mq-update-mode-lines (hg-root))))
+ (run-hooks 'mq-mode-hook))
+
+(defun mq-edit-mode ()
+ "Mode for editing the description of a patch.
+
+Key bindings
+------------
+\\[mq-edit-finish] use this description
+\\[mq-edit-kill] abandon this description"
+ (interactive)
+ (use-local-map mq-edit-mode-map)
+ (set-syntax-table text-mode-syntax-table)
+ (setq local-abbrev-table text-mode-abbrev-table
+ major-mode 'mq-edit-mode
+ mode-name "MQ-Edit")
+ (set-buffer-modified-p nil)
+ (setq buffer-undo-list nil)
+ (run-hooks 'text-mode-hook 'mq-edit-mode-hook))
+
+(defun mq-refresh-edit ()
+ "Refresh the topmost applied patch, editing the patch description."
+ (interactive)
+ (while mq-prev-buffer
+ (set-buffer mq-prev-buffer))
+ (let ((root (hg-root))
+ (prev-buffer (current-buffer))
+ (patch (mq-patch-info "qtop")))
+ (hg-sync-buffers root)
+ (let ((buf-name (format "*MQ: Edit description of %s*" patch)))
+ (switch-to-buffer (get-buffer-create buf-name))
+ (when (= (point-min) (point-max))
+ (set (make-local-variable 'hg-root) root)
+ (set (make-local-variable 'mq-top) patch)
+ (setq mq-prev-buffer prev-buffer)
+ (insert (hg-run0 "qheader"))
+ (goto-char (point-min)))
+ (mq-edit-mode)
+ (cd root)))
+ (message "Type `C-c C-c' to finish editing and refresh the patch."))
+
+(defun mq-new (name)
+ "Create a new empty patch named NAME.
+The patch is applied on top of the current topmost patch.
+With a prefix argument, forcibly create the patch even if the working
+directory is modified."
+ (interactive (list (mq-read-patch-name "qseries" " to create" t)))
+ (message "Creating patch...")
+ (let ((ret (if current-prefix-arg
+ (hg-run "qnew" "-f" name)
+ (hg-run "qnew" name))))
+ (if (equal (car ret) 0)
+ (progn
+ (hg-update-mode-lines (buffer-file-name))
+ (message "Creating patch... done."))
+ (error "Creating patch... %s" (hg-chomp (cdr ret))))))
+
+(defun mq-edit-series ()
+ "Edit the MQ series file directly."
+ (interactive)
+ (let ((root (hg-root)))
+ (unless root
+ (error "Not in an MQ repository!"))
+ (find-file (concat root ".hg/patches/series"))))
+
+(defun mq-diff (&optional git)
+ "Display a diff of the topmost applied patch.
+With a prefix argument, display a git-compatible diff."
+ (interactive "P")
+ (hg-view-output ((format "MQ: Diff of %s" (mq-patch-info "qtop")))
+ (if git
+ (call-process (hg-binary) nil t nil "qdiff" "--git")
+ (call-process (hg-binary) nil t nil "qdiff"))
+ (diff-mode)
+ (font-lock-fontify-buffer)))
+
+(defun mq-signoff ()
+ "Sign off on the current patch, in the style used by the Linux kernel.
+If the variable mq-signoff-address is non-nil, it will be used, otherwise
+the value of the ui.username item from your hgrc will be used."
+ (interactive)
+ (let ((was-editing (eq major-mode 'mq-edit-mode))
+ signed)
+ (unless was-editing
+ (mq-refresh-edit))
+ (save-excursion
+ (let* ((user (or mq-signoff-address
+ (hg-run0 "debugconfig" "ui.username")))
+ (signoff (concat "Signed-off-by: " user)))
+ (if (search-forward signoff nil t)
+ (message "You have already signed off on this patch.")
+ (goto-char (point-max))
+ (let ((case-fold-search t))
+ (if (re-search-backward "^Signed-off-by: " nil t)
+ (forward-line 1)
+ (insert "\n")))
+ (insert signoff)
+ (message "%s" signoff)
+ (setq signed t))))
+ (unless was-editing
+ (if signed
+ (mq-edit-finish)
+ (mq-edit-kill)))))
+
+
+(provide 'mq)
+
+
+;;; Local Variables:
+;;; prompt-to-byte-compile: nil
+;;; end:
diff --git a/contrib/perf.py b/contrib/perf.py
new file mode 100644
index 0000000..574e899
--- /dev/null
+++ b/contrib/perf.py
@@ -0,0 +1,252 @@
+# perf.py - performance test routines
+'''helper extension to measure performance'''
+
+from mercurial import cmdutil, scmutil, util, match, commands
+import time, os, sys
+
+def timer(func, title=None):
+ results = []
+ begin = time.time()
+ count = 0
+ while True:
+ 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 title:
+ sys.stderr.write("! %s\n" % title)
+ 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 = scmutil.match(repo[None], pats, {})
+ timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
+ except Exception:
+ try:
+ m = scmutil.match(repo[None], pats, {})
+ timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
+ except Exception:
+ 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 clearcaches(cl):
+ # behave somewhat consistently across internal API changes
+ if util.safehasattr(cl, 'clearcaches'):
+ cl.clearcaches()
+ elif util.safehasattr(cl, '_nodecache'):
+ from mercurial.node import nullid, nullrev
+ cl._nodecache = {nullid: nullrev}
+ cl._nodepos = None
+
+def perfheads(ui, repo):
+ cl = repo.changelog
+ def d():
+ len(cl.headrevs())
+ clearcaches(cl)
+ timer(d)
+
+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._tags = None
+ return len(repo.tags())
+ timer(t)
+
+def perfancestors(ui, repo):
+ heads = repo.changelog.headrevs()
+ def d():
+ for a in repo.changelog.ancestors(heads):
+ pass
+ timer(d)
+
+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 perfdirstatewrite(ui, repo):
+ ds = repo.dirstate
+ "a" in ds
+ def d():
+ ds._dirty = True
+ ds.write()
+ 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 perfchangeset(ui, repo, rev):
+ n = repo[rev].node()
+ def d():
+ c = repo.changelog.read(n)
+ #repo.changelog._cache = None
+ timer(d)
+
+def perfindex(ui, repo):
+ import mercurial.revlog
+ mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
+ n = repo["tip"].node()
+ def d():
+ cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
+ cl.rev(n)
+ 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)))
+
+def perfrevrange(ui, repo, *specs):
+ revrange = scmutil.revrange
+ timer(lambda: len(revrange(repo, specs)))
+
+def perfnodelookup(ui, repo, rev):
+ import mercurial.revlog
+ mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
+ n = repo[rev].node()
+ def d():
+ cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
+ cl.rev(n)
+ timer(d)
+
+def perfnodelookup(ui, repo, rev):
+ import mercurial.revlog
+ mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
+ n = repo[rev].node()
+ cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
+ def d():
+ cl.rev(n)
+ clearcaches(cl)
+ timer(d)
+
+def perflog(ui, repo, **opts):
+ ui.pushbuffer()
+ timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
+ copies=opts.get('rename')))
+ ui.popbuffer()
+
+def perftemplating(ui, repo):
+ ui.pushbuffer()
+ timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
+ template='{date|shortdate} [{rev}:{node|short}]'
+ ' {author|person}: {desc|firstline}\n'))
+ ui.popbuffer()
+
+def perfcca(ui, repo):
+ timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
+
+def perffncacheload(ui, repo):
+ from mercurial import scmutil, store
+ s = store.store(set(['store','fncache']), repo.path, scmutil.opener)
+ def d():
+ s.fncache._load()
+ timer(d)
+
+def perffncachewrite(ui, repo):
+ from mercurial import scmutil, store
+ s = store.store(set(['store','fncache']), repo.path, scmutil.opener)
+ s.fncache._load()
+ def d():
+ s.fncache._dirty = True
+ s.fncache.write()
+ timer(d)
+
+def perfdiffwd(ui, repo):
+ """Profile diff of working directory changes"""
+ options = {
+ 'w': 'ignore_all_space',
+ 'b': 'ignore_space_change',
+ 'B': 'ignore_blank_lines',
+ }
+
+ for diffopt in ('', 'w', 'b', 'B', 'wB'):
+ opts = dict((options[c], '1') for c in diffopt)
+ def d():
+ ui.pushbuffer()
+ commands.diff(ui, repo, **opts)
+ ui.popbuffer()
+ title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
+ timer(d, title)
+
+def perfrevlog(ui, repo, file_, **opts):
+ from mercurial import revlog
+ dist = opts['dist']
+ def d():
+ r = revlog.revlog(lambda fn: open(fn, 'rb'), file_)
+ for x in xrange(0, len(r), dist):
+ r.revision(r.node(x))
+
+ timer(d)
+
+cmdtable = {
+ 'perfcca': (perfcca, []),
+ 'perffncacheload': (perffncacheload, []),
+ 'perffncachewrite': (perffncachewrite, []),
+ 'perflookup': (perflookup, []),
+ 'perfrevrange': (perfrevrange, []),
+ 'perfnodelookup': (perfnodelookup, []),
+ 'perfparents': (perfparents, []),
+ 'perfstartup': (perfstartup, []),
+ 'perfstatus': (perfstatus, []),
+ 'perfwalk': (perfwalk, []),
+ 'perfmanifest': (perfmanifest, []),
+ 'perfchangeset': (perfchangeset, []),
+ 'perfindex': (perfindex, []),
+ 'perfheads': (perfheads, []),
+ 'perftags': (perftags, []),
+ 'perfancestors': (perfancestors, []),
+ 'perfdirstate': (perfdirstate, []),
+ 'perfdirstatedirs': (perfdirstate, []),
+ 'perfdirstatewrite': (perfdirstatewrite, []),
+ 'perflog': (perflog,
+ [('', 'rename', False, 'ask log to follow renames')]),
+ 'perftemplating': (perftemplating, []),
+ 'perfdiffwd': (perfdiffwd, []),
+ 'perfrevlog': (perfrevlog,
+ [('d', 'dist', 100, 'distance between the revisions')],
+ "[INDEXFILE]"),
+}
diff --git a/contrib/plan9/9diff b/contrib/plan9/9diff
new file mode 100755
index 0000000..02af2a9
--- /dev/null
+++ b/contrib/plan9/9diff
@@ -0,0 +1,42 @@
+#!/bin/rc
+# 9diff - Mercurial extdiff wrapper for diff(1)
+
+rfork e
+
+fn getfiles {
+ cd $1 &&
+ for(f in `{du -as | awk '{print $2}'})
+ test -f $f && echo `{cleanname $f}
+}
+
+fn usage {
+ echo >[1=2] usage: 9diff [diff options] parent child root
+ exit usage
+}
+
+opts=()
+while(~ $1 -*){
+ opts=($opts $1)
+ shift
+}
+if(! ~ $#* 3)
+ usage
+
+# extdiff will set the parent and child to a single file if there is
+# only one change. If there are multiple changes, directories will be
+# set. diff(1) does not cope particularly with directories; instead we
+# do the recursion ourselves and diff each file individually.
+if(test -f $1)
+ diff $opts $1 $2
+if not{
+ # extdiff will create a snapshot of the working copy to prevent
+ # conflicts during the diff. We circumvent this behavior by
+ # diffing against the repository root to produce plumbable
+ # output. This is antisocial.
+ for(f in `{sort -u <{getfiles $1} <{getfiles $2}}){
+ file1=$1/$f; test -f $file1 || file1=/dev/null
+ file2=$3/$f; test -f $file2 || file2=/dev/null
+ diff $opts $file1 $file2
+ }
+}
+exit ''
diff --git a/contrib/plan9/README b/contrib/plan9/README
new file mode 100644
index 0000000..6adbf53
--- /dev/null
+++ b/contrib/plan9/README
@@ -0,0 +1,39 @@
+Mercurial for Plan 9 from Bell Labs
+===================================
+
+This directory contains support for Mercurial on Plan 9 from Bell Labs
+platforms. It is assumed that the version of Python running on these
+systems supports the ANSI/POSIX Environment (APE). At the time of this
+writing, the bichued/python port is the most commonly installed version
+of Python on these platforms. If a native port of Python is ever made,
+some minor modification will need to be made to support some of the more
+esoteric requirements of the platform rather than those currently made
+(cf. posix.py).
+
+By default, installations will have the factotum extension enabled; this
+extension permits factotum(4) to act as an authentication agent for
+HTTP repositories. Additionally, an extdiff command named 9diff is
+enabled which generates diff(1) compatible output suitable for use with
+the plumber(4).
+
+Commit messages are plumbed using E if no editor is defined; users must
+update the plumbed file to continue, otherwise the hg process must be
+interrupted.
+
+Some work remains with regard to documentation. Section 5 manual page
+references for hgignore and hgrc need to be re-numbered to section 6 (file
+formats) and a new man page writer should be written to support the
+Plan 9 man macro set. Until these issues can be resolved, manual pages
+are elided from the installation.
+
+Basic install:
+
+ % mk install # do a system-wide install
+ % hg debuginstall # sanity-check setup
+ % hg # see help
+
+A proto(2) file is included in this directory as an example of how a
+binary distribution could be packaged, ostensibly with contrib(1).
+
+See http://mercurial.selenic.com/ for detailed installation
+instructions, platform-specific notes, and Mercurial user information.
diff --git a/contrib/plan9/hgrc.d/9diff.rc b/contrib/plan9/hgrc.d/9diff.rc
new file mode 100644
index 0000000..757e24c
--- /dev/null
+++ b/contrib/plan9/hgrc.d/9diff.rc
@@ -0,0 +1,7 @@
+# The 9diff extdiff command generates diff(1) compatible output
+# suitable for use with the plumber(4).
+[extensions]
+extdiff =
+
+[extdiff]
+9diff = 9diff -cm $parent $child $root
diff --git a/contrib/plan9/hgrc.d/factotum.rc b/contrib/plan9/hgrc.d/factotum.rc
new file mode 100644
index 0000000..0fa2334
--- /dev/null
+++ b/contrib/plan9/hgrc.d/factotum.rc
@@ -0,0 +1,4 @@
+# The factotum extension permits factotum(4) to act as an
+# authentication agent for HTTP repositories.
+[extensions]
+factotum =
diff --git a/contrib/plan9/mkfile b/contrib/plan9/mkfile
new file mode 100644
index 0000000..6d8e8c2
--- /dev/null
+++ b/contrib/plan9/mkfile
@@ -0,0 +1,37 @@
+APE=/sys/src/ape
+<$APE/config
+
+PYTHON=python
+PYTHONBIN=/rc/bin
+SH=ape/psh
+
+PURE=--pure
+ROOT=../..
+
+# This is slightly underhanded; Plan 9 does not support GNU gettext nor
+# does it support dynamically loaded extension modules. We work around
+# this by calling build_py and build_scripts directly; this avoids
+# additional platform hacks in setup.py.
+build:VQ:
+ @{
+ cd $ROOT
+ $SH -c '$PYTHON setup.py $PURE build_py build_scripts'
+ }
+
+clean:VQ:
+ @{
+ cd $ROOT
+ $SH -c '$PYTHON setup.py $PURE clean --all'
+ }
+
+install:VQ: build
+ @{
+ cd $ROOT
+ $SH -c '$PYTHON setup.py $PURE install \
+ --install-scripts $PYTHONBIN \
+ --skip-build \
+ --force'
+ }
+ mkdir -p /lib/mercurial/hgrc.d
+ dircp hgrc.d /lib/mercurial/hgrc.d/
+ cp 9diff /rc/bin/
diff --git a/contrib/plan9/proto b/contrib/plan9/proto
new file mode 100644
index 0000000..4355209
--- /dev/null
+++ b/contrib/plan9/proto
@@ -0,0 +1,24 @@
+lib - sys sys
+ mercurial - sys sys
+ hgrc.d - sys sys
+ 9diff.rc - sys sys
+ factotum.rc - sys sys
+rc - sys sys
+ bin - sys sys
+ 9diff - sys sys
+ hg - sys sys
+sys - sys sys
+ lib - sys sys
+ python - sys sys
+ lib - sys sys
+ python2.5 - sys sys
+ site-packages - sys sys
+ hgext - sys sys
+ + - sys sys
+ mercurial - sys sys
+ + - sys sys
+ mercurial-VERSION-py2.5.egg-info - sys sys
+ src - sys sys
+ cmd - sys sys
+ hg - sys sys
+ + - sys sys
diff --git a/contrib/pylintrc b/contrib/pylintrc
new file mode 100644
index 0000000..5e5c237
--- /dev/null
+++ b/contrib/pylintrc
@@ -0,0 +1,313 @@
+# lint Python modules using external checkers.
+#
+# This is the main checker controlling the other ones and the reports
+# generation. It is itself both a raw checker and an astng checker in order
+# to:
+# * handle message activation / deactivation at the module level
+# * handle some basic but necessary stats'data (number of classes, methods...)
+#
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add <file or directory> to the black list. It should be a base name, not a
+# path. You may set this option multiple times.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Set the cache size for astng objects.
+cache-size=500
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable only checker(s) with the given id(s). This option conflicts with the
+# disable-checker option
+#enable-checker=
+
+# Enable all checker(s) except those with the given id(s). This option
+# conflicts with the enable-checker option
+#disable-checker=
+
+# Enable all messages in the listed categories (IRCWEF).
+#enable-msg-cat=
+
+# Disable all messages in the listed categories (IRCWEF).
+disable-msg-cat=I
+
+# Enable the message(s) with the given id(s).
+#enable-msg=
+
+# Disable the message(s) with the given id(s).
+# W0704: except: pass
+# C0111: missing docstring
+# W0403: for the time being absolute imports don't play nice with demandimport
+disable-msg=W0704,C0111,W0403
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Include message's id in output
+include-ids=yes
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (R0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (R0004).
+comment=no
+
+# Enable the report(s) with the given id(s).
+#enable-report=
+
+# Disable the report(s) with the given id(s).
+#disable-report=
+
+
+# try to find bugs in the code using type inference
+#
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+# checks for
+# * unused variables / imports
+# * undefined variables
+# * redefinition of variable from builtins or from an outer scope
+# * use of variable before assignment
+#
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=yes
+
+# A regular expression matching names used for dummy variables (i.e. not used).
+dummy-variables-rgx=dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+# checks for :
+# * doc strings
+# * modules / classes / functions / methods / arguments / variables name
+# * number of arguments, local variables, branches, returns and statements in
+# functions, methods
+# * required module attributes
+# * dangerous default values as arguments
+# * redefinition of function / method / class
+# * uses of the global statement
+#
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[a-zA-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{1,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{0,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{0,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_,ui,c,fn,f,fd,l
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# List of builtins function names that should not be used, separated by a comma
+#bad-functions=map,filter,apply,input
+bad-functions=map,filter,apply,input
+
+
+# checks for
+# * external modules dependencies
+# * relative / wildcard imports
+# * cyclic imports
+# * uses of deprecated modules
+#
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report R0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report R0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report R0402 must
+# not be disabled)
+int-import-graph=
+
+
+# checks for sign of poor/misdesign:
+# * number of methods, attributes, local variables...
+# * size, complexity of functions, methods
+#
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+# checks for :
+# * methods without self as first argument
+# * overridden methods signature
+# * access only to existent members via self
+# * attributes not defined in the __init__ method
+# * supported interfaces implementation
+# * unreachable code
+#
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+
+# checks for :
+# * unauthorized constructions
+# * strict indentation
+# * line length
+# * use of <> instead of !=
+#
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string=' '
+
+
+# checks for:
+# * warning notes in the code like FIXME, XXX
+# * PEP 263: source code with non ascii character but no encoding declaration
+#
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+# checks for similarities and duplicated code. This computation may be
+# memory / CPU intensive, so you should disable it if you experiments some
+# problems.
+#
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
diff --git a/contrib/python-hook-examples.py b/contrib/python-hook-examples.py
new file mode 100644
index 0000000..b38df97
--- /dev/null
+++ b/contrib/python-hook-examples.py
@@ -0,0 +1,22 @@
+'''
+Examples of useful python hooks for Mercurial.
+'''
+from mercurial import patch, util
+
+def diffstat(ui, repo, **kwargs):
+ '''Example usage:
+
+ [hooks]
+ commit.diffstat = python:/path/to/this/file.py:diffstat
+ changegroup.diffstat = python:/path/to/this/file.py:diffstat
+ '''
+ if kwargs.get('parent2'):
+ return
+ node = kwargs['node']
+ first = repo[node].p1().node()
+ if 'url' in kwargs:
+ last = repo['tip'].node()
+ else:
+ last = node
+ diff = patch.diff(repo, first, last)
+ ui.write(patch.diffstat(util.iterlines(diff)))
diff --git a/contrib/sample.hgrc b/contrib/sample.hgrc
new file mode 100644
index 0000000..4493727
--- /dev/null
+++ b/contrib/sample.hgrc
@@ -0,0 +1,133 @@
+### --- User interface
+
+[ui]
+
+### show changed files and be a bit more verbose if True
+
+# verbose = True
+
+### username data to appear in comits
+### it usually takes the form: Joe User <joe.user@host.com>
+
+# username = Joe User <j.user@example.com>
+
+### --- Extensions
+
+[extensions]
+
+### each extension has its own 'extension_name=path' line
+### the default python library path is used when path is left blank
+### the hgext dir is used when 'hgext.extension_name=' is written
+
+### acl - Access control lists
+### hg help acl
+
+# hgext.acl =
+
+### bisect - binary search changesets to detect bugs
+### hg help bisect
+
+# hgext.hbisect =
+
+### bugzilla - update bugzilla bugs when changesets mention them
+### hg help bugzilla
+
+# hgext.bugzilla =
+
+### extdiff - Use external diff application instead of builtin one
+
+# hgext.extdiff =
+
+### gpg - GPG checks and signing
+### hg help gpg
+
+# hgext.gpg =
+
+### graphlog - ASCII graph log
+### hg help glog
+
+# hgext.graphlog =
+
+### hgk - GUI repository browser
+### hg help view
+
+# hgext.hgk =
+
+### mq - Mercurial patch queues
+### hg help mq
+
+# hgext.mq =
+
+### notify - Template driven e-mail notifications
+### hg help notify
+
+# hgext.notify =
+
+### patchbomb - send changesets as a series of patch emails
+### hg help email
+
+# hgext.patchbomb =
+
+### churn - create a graph showing who changed the most lines
+### hg help churn
+
+# hgext.churn = /home/user/hg/hg/contrib/churn.py
+
+### eol - automatic management of line endings
+
+# hgext.eol =
+
+### --- hgk additional configuration
+
+[hgk]
+
+### set executable path
+
+# path = /home/user/hg/hg/contrib/hgk
+
+### --- Hook to Mercurial actions - See hgrc man page for avaliable hooks
+
+[hooks]
+
+### Example notify hooks (load hgext.notify extension before use)
+
+# incoming.notify = python:hgext.notify.hook
+# changegroup.notify = python:hgext.notify.hook
+
+### Email configuration for the notify and patchbomb extensions
+
+[email]
+
+### Your email address
+
+# from = user@example.com
+
+### Method to send email - smtp or /usr/sbin/sendmail or other program name
+
+# method = smtp
+
+### smtp server to send email to
+
+[smtp]
+
+# host = mail
+# port = 25
+# tls = false
+# username = user
+# password = blivet
+# local_hostname = myhost
+
+### --- Email notification hook for server
+
+[notify]
+### multiple sources can be specified as a whitespace or comma separated list
+
+# sources = serve push pull bundle
+
+### set this to False when you're ready for mail to start sending
+
+# test = True
+
+### path to config file with names of subscribers
+
+# config = /path/to/subscription/file
diff --git a/contrib/setup3k.py b/contrib/setup3k.py
new file mode 100644
index 0000000..55cf36f
--- /dev/null
+++ b/contrib/setup3k.py
@@ -0,0 +1,373 @@
+#
+# This is an experimental py3k-enabled mercurial setup script.
+#
+# 'python setup.py install', or
+# 'python setup.py --help' for more options
+
+from distutils.command.build_py import build_py_2to3
+from lib2to3.refactor import get_fixers_from_package as getfixers
+
+import sys
+if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'):
+ raise SystemExit("Mercurial requires Python 2.4 or later.")
+
+if sys.version_info[0] >= 3:
+ def b(s):
+ '''A helper function to emulate 2.6+ bytes literals using string
+ literals.'''
+ return s.encode('latin1')
+else:
+ def b(s):
+ '''A helper function to emulate 2.6+ bytes literals using string
+ literals.'''
+ return s
+
+# Solaris Python packaging brain damage
+try:
+ import hashlib
+ sha = hashlib.sha1()
+except ImportError:
+ try:
+ import sha
+ except ImportError:
+ raise SystemExit(
+ "Couldn't import standard hashlib (incomplete Python install).")
+
+try:
+ import zlib
+except ImportError:
+ raise SystemExit(
+ "Couldn't import standard zlib (incomplete Python install).")
+
+try:
+ import bz2
+except ImportError:
+ raise SystemExit(
+ "Couldn't import standard bz2 (incomplete Python install).")
+
+import os, subprocess, time
+import shutil
+import tempfile
+from distutils import log
+from distutils.core import setup, Extension
+from distutils.dist import Distribution
+from distutils.command.build import build
+from distutils.command.build_ext import build_ext
+from distutils.command.build_py import build_py
+from distutils.spawn import spawn, find_executable
+from distutils.ccompiler import new_compiler
+from distutils.errors import CCompilerError
+
+scripts = ['hg']
+if os.name == 'nt':
+ scripts.append('contrib/win32/hg.bat')
+
+# simplified version of distutils.ccompiler.CCompiler.has_function
+# that actually removes its temporary files.
+def hasfunction(cc, funcname):
+ tmpdir = tempfile.mkdtemp(prefix='hg-install-')
+ devnull = oldstderr = None
+ try:
+ try:
+ fname = os.path.join(tmpdir, 'funcname.c')
+ f = open(fname, 'w')
+ f.write('int main(void) {\n')
+ f.write(' %s();\n' % funcname)
+ f.write('}\n')
+ f.close()
+ # Redirect stderr to /dev/null to hide any error messages
+ # from the compiler.
+ # This will have to be changed if we ever have to check
+ # for a function on Windows.
+ devnull = open('/dev/null', 'w')
+ oldstderr = os.dup(sys.stderr.fileno())
+ os.dup2(devnull.fileno(), sys.stderr.fileno())
+ objects = cc.compile([fname], output_dir=tmpdir)
+ cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
+ except Exception:
+ return False
+ return True
+ finally:
+ if oldstderr is not None:
+ os.dup2(oldstderr, sys.stderr.fileno())
+ if devnull is not None:
+ devnull.close()
+ shutil.rmtree(tmpdir)
+
+# py2exe needs to be installed to work
+try:
+ import py2exe
+ py2exeloaded = True
+
+ # Help py2exe to find win32com.shell
+ try:
+ import modulefinder
+ import win32com
+ for p in win32com.__path__[1:]: # Take the path to win32comext
+ modulefinder.AddPackagePath("win32com", p)
+ pn = "win32com.shell"
+ __import__(pn)
+ m = sys.modules[pn]
+ for p in m.__path__[1:]:
+ modulefinder.AddPackagePath(pn, p)
+ except ImportError:
+ pass
+
+except ImportError:
+ py2exeloaded = False
+ pass
+
+def runcmd(cmd, env):
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, env=env)
+ out, err = p.communicate()
+ # If root is executing setup.py, but the repository is owned by
+ # another user (as in "sudo python setup.py install") we will get
+ # trust warnings since the .hg/hgrc file is untrusted. That is
+ # fine, we don't want to load it anyway. Python may warn about
+ # a missing __init__.py in mercurial/locale, we also ignore that.
+ err = [e for e in err.splitlines()
+ if not e.startswith(b('Not trusting file')) \
+ and not e.startswith(b('warning: Not importing'))]
+ if err:
+ return ''
+ return out
+
+version = ''
+
+if os.path.isdir('.hg'):
+ # Execute hg out of this directory with a custom environment which
+ # includes the pure Python modules in mercurial/pure. We also take
+ # care to not use any hgrc files and do no localization.
+ pypath = ['mercurial', os.path.join('mercurial', 'pure')]
+ env = {'PYTHONPATH': os.pathsep.join(pypath),
+ 'HGRCPATH': '',
+ 'LANGUAGE': 'C'}
+ if 'LD_LIBRARY_PATH' in os.environ:
+ env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
+ if 'SystemRoot' in os.environ:
+ # Copy SystemRoot into the custom environment for Python 2.6
+ # under Windows. Otherwise, the subprocess will fail with
+ # error 0xc0150004. See: http://bugs.python.org/issue3440
+ env['SystemRoot'] = os.environ['SystemRoot']
+ cmd = [sys.executable, 'hg', 'id', '-i', '-t']
+ l = runcmd(cmd, env).split()
+ while len(l) > 1 and l[-1][0].isalpha(): # remove non-numbered tags
+ l.pop()
+ if len(l) > 1: # tag found
+ version = l[-1]
+ if l[0].endswith('+'): # propagate the dirty status to the tag
+ version += '+'
+ elif len(l) == 1: # no tag found
+ cmd = [sys.executable, 'hg', 'parents', '--template',
+ '{latesttag}+{latesttagdistance}-']
+ version = runcmd(cmd, env) + l[0]
+ if version.endswith('+'):
+ version += time.strftime('%Y%m%d')
+elif os.path.exists('.hg_archival.txt'):
+ kw = dict([[t.strip() for t in l.split(':', 1)]
+ for l in open('.hg_archival.txt')])
+ if 'tag' in kw:
+ version = kw['tag']
+ elif 'latesttag' in kw:
+ version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
+ else:
+ version = kw.get('node', '')[:12]
+
+if version:
+ f = open("mercurial/__version__.py", "w")
+ f.write('# this file is autogenerated by setup.py\n')
+ f.write('version = "%s"\n' % version)
+ f.close()
+
+
+try:
+ from mercurial import __version__
+ version = __version__.version
+except ImportError:
+ version = 'unknown'
+
+class hgbuildmo(build):
+
+ description = "build translations (.mo files)"
+
+ def run(self):
+ if not find_executable('msgfmt'):
+ self.warn("could not find msgfmt executable, no translations "
+ "will be built")
+ return
+
+ podir = 'i18n'
+ if not os.path.isdir(podir):
+ self.warn("could not find %s/ directory" % podir)
+ return
+
+ join = os.path.join
+ for po in os.listdir(podir):
+ if not po.endswith('.po'):
+ continue
+ pofile = join(podir, po)
+ modir = join('locale', po[:-3], 'LC_MESSAGES')
+ mofile = join(modir, 'hg.mo')
+ mobuildfile = join('mercurial', mofile)
+ cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
+ if sys.platform != 'sunos5':
+ # msgfmt on Solaris does not know about -c
+ cmd.append('-c')
+ self.mkpath(join('mercurial', modir))
+ self.make_file([pofile], mobuildfile, spawn, (cmd,))
+
+# Insert hgbuildmo first so that files in mercurial/locale/ are found
+# when build_py is run next.
+build.sub_commands.insert(0, ('build_mo', None))
+# We also need build_ext before build_py. Otherwise, when 2to3 is called (in
+# build_py), it will not find osutil & friends, thinking that those modules are
+# global and, consequently, making a mess, now that all module imports are
+# global.
+build.sub_commands.insert(1, ('build_ext', None))
+
+Distribution.pure = 0
+Distribution.global_options.append(('pure', None, "use pure (slow) Python "
+ "code instead of C extensions"))
+
+class hgbuildext(build_ext):
+
+ def build_extension(self, ext):
+ try:
+ build_ext.build_extension(self, ext)
+ except CCompilerError:
+ if getattr(ext, 'optional', False):
+ raise
+ log.warn("Failed to build optional extension '%s' (skipping)",
+ ext.name)
+
+class hgbuildpy(build_py_2to3):
+ fixer_names = sorted(set(getfixers("lib2to3.fixes") +
+ getfixers("hgfixes")))
+
+ def finalize_options(self):
+ build_py.finalize_options(self)
+
+ if self.distribution.pure:
+ if self.py_modules is None:
+ self.py_modules = []
+ for ext in self.distribution.ext_modules:
+ if ext.name.startswith("mercurial."):
+ self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
+ self.distribution.ext_modules = []
+
+ def find_modules(self):
+ modules = build_py.find_modules(self)
+ for module in modules:
+ if module[0] == "mercurial.pure":
+ if module[1] != "__init__":
+ yield ("mercurial", module[1], module[2])
+ else:
+ yield module
+
+ def run(self):
+ # In the build_py_2to3 class, self.updated_files = [], but I couldn't
+ # see when that variable was updated to point to the updated files, as
+ # its names suggests. Thus, I decided to just find_all_modules and feed
+ # them to 2to3. Unfortunately, subsequent calls to setup3k.py will
+ # incur in 2to3 analysis overhead.
+ self.updated_files = [i[2] for i in self.find_all_modules()]
+
+ # Base class code
+ if self.py_modules:
+ self.build_modules()
+ if self.packages:
+ self.build_packages()
+ self.build_package_data()
+
+ # 2to3
+ self.run_2to3(self.updated_files)
+
+ # Remaining base class code
+ self.byte_compile(self.get_outputs(include_bytecode=0))
+
+cmdclass = {'build_mo': hgbuildmo,
+ 'build_ext': hgbuildext,
+ 'build_py': hgbuildpy}
+
+packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert',
+ 'hgext.highlight', 'hgext.zeroconf']
+
+pymodules = []
+
+extmodules = [
+ Extension('mercurial.base85', ['mercurial/base85.c']),
+ Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
+ Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
+ Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
+ Extension('mercurial.parsers', ['mercurial/parsers.c']),
+ ]
+
+# disable osutil.c under windows + python 2.4 (issue1364)
+if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
+ pymodules.append('mercurial.pure.osutil')
+else:
+ extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c']))
+
+if sys.platform.startswith('linux') and os.uname()[2] > '2.6':
+ # The inotify extension is only usable with Linux 2.6 kernels.
+ # You also need a reasonably recent C library.
+ # In any case, if it fails to build the error will be skipped ('optional').
+ cc = new_compiler()
+ if hasfunction(cc, 'inotify_add_watch'):
+ inotify = Extension('hgext.inotify.linux._inotify',
+ ['hgext/inotify/linux/_inotify.c'],
+ ['mercurial'])
+ inotify.optional = True
+ extmodules.append(inotify)
+ packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
+
+packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
+ 'help/*.txt']}
+
+def ordinarypath(p):
+ return p and p[0] != '.' and p[-1] != '~'
+
+for root in ('templates',):
+ for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
+ curdir = curdir.split(os.sep, 1)[1]
+ dirs[:] = filter(ordinarypath, dirs)
+ for f in filter(ordinarypath, files):
+ f = os.path.join(curdir, f)
+ packagedata['mercurial'].append(f)
+
+datafiles = []
+setupversion = version
+extra = {}
+
+if py2exeloaded:
+ extra['console'] = [
+ {'script':'hg',
+ 'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
+ 'product_version':version}]
+
+if os.name == 'nt':
+ # Windows binary file versions for exe/dll files must have the
+ # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
+ setupversion = version.split('+', 1)[0]
+
+setup(name='mercurial',
+ version=setupversion,
+ author='Matt Mackall',
+ author_email='mpm@selenic.com',
+ url='http://mercurial.selenic.com/',
+ description='Scalable distributed SCM',
+ license='GNU GPLv2+',
+ scripts=scripts,
+ packages=packages,
+ py_modules=pymodules,
+ ext_modules=extmodules,
+ data_files=datafiles,
+ package_data=packagedata,
+ cmdclass=cmdclass,
+ options=dict(py2exe=dict(packages=['hgext', 'email']),
+ bdist_mpkg=dict(zipdist=True,
+ license='COPYING',
+ readme='contrib/macosx/Readme.html',
+ welcome='contrib/macosx/Welcome.html')),
+ **extra)
diff --git a/contrib/shrink-revlog.py b/contrib/shrink-revlog.py
new file mode 100644
index 0000000..6bd006d
--- /dev/null
+++ b/contrib/shrink-revlog.py
@@ -0,0 +1,294 @@
+"""reorder a revlog (the manifest by default) to save space
+
+Specifically, this topologically sorts the revisions in the revlog so that
+revisions on the same branch are adjacent as much as possible. This is a
+workaround for the fact that Mercurial computes deltas relative to the
+previous revision rather than relative to a parent revision.
+
+This is *not* safe to run on a changelog.
+"""
+
+# Originally written by Benoit Boissinot <benoit.boissinot at ens-lyon.org>
+# as a patch to rewrite-log. Cleaned up, refactored, documented, and
+# renamed by Greg Ward <greg at gerg.ca>.
+
+# XXX would be nice to have a way to verify the repository after shrinking,
+# e.g. by comparing "before" and "after" states of random changesets
+# (maybe: export before, shrink, export after, diff).
+
+import os, errno
+from mercurial import revlog, transaction, node, util, scmutil
+from mercurial import changegroup
+from mercurial.i18n import _
+
+
+def postorder(start, edges):
+ result = []
+ visit = list(start)
+ finished = set()
+
+ while visit:
+ cur = visit[-1]
+ for p in edges[cur]:
+ # defend against node.nullrev because it's occasionally
+ # possible for a node to have parents (null, something)
+ # rather than (something, null)
+ if p not in finished and p != node.nullrev:
+ visit.append(p)
+ break
+ else:
+ result.append(cur)
+ finished.add(cur)
+ visit.pop()
+
+ return result
+
+def toposort_reversepostorder(ui, rl):
+ # postorder of the reverse directed graph
+
+ # map rev to list of parent revs (p2 first)
+ parents = {}
+ heads = set()
+ ui.status(_('reading revs\n'))
+ try:
+ for rev in rl:
+ ui.progress(_('reading'), rev, total=len(rl))
+ (p1, p2) = rl.parentrevs(rev)
+ if p1 == p2 == node.nullrev:
+ parents[rev] = () # root node
+ elif p1 == p2 or p2 == node.nullrev:
+ parents[rev] = (p1,) # normal node
+ else:
+ parents[rev] = (p2, p1) # merge node
+ heads.add(rev)
+ for p in parents[rev]:
+ heads.discard(p)
+ finally:
+ ui.progress(_('reading'), None)
+
+ heads = list(heads)
+ heads.sort(reverse=True)
+
+ ui.status(_('sorting revs\n'))
+ return postorder(heads, parents)
+
+def toposort_postorderreverse(ui, rl):
+ # reverse-postorder of the reverse directed graph
+
+ children = {}
+ roots = set()
+ ui.status(_('reading revs\n'))
+ try:
+ for rev in rl:
+ ui.progress(_('reading'), rev, total=len(rl))
+ (p1, p2) = rl.parentrevs(rev)
+ if p1 == p2 == node.nullrev:
+ roots.add(rev)
+ children[rev] = []
+ if p1 != node.nullrev:
+ children[p1].append(rev)
+ if p2 != node.nullrev:
+ children[p2].append(rev)
+ finally:
+ ui.progress(_('reading'), None)
+
+ roots = list(roots)
+ roots.sort()
+
+ ui.status(_('sorting revs\n'))
+ result = postorder(roots, children)
+ result.reverse()
+ return result
+
+def writerevs(ui, r1, r2, order, tr):
+
+ ui.status(_('writing revs\n'))
+
+
+ order = [r1.node(r) for r in order]
+
+ # this is a bit ugly, but it works
+ count = [0]
+ def lookup(revl, x):
+ count[0] += 1
+ ui.progress(_('writing'), count[0], total=len(order))
+ return "%020d" % revl.linkrev(revl.rev(x))
+
+ unlookup = lambda x: int(x, 10)
+
+ try:
+ bundler = changegroup.bundle10(lookup)
+ group = util.chunkbuffer(r1.group(order, bundler))
+ group = changegroup.unbundle10(group, "UN")
+ r2.addgroup(group, unlookup, tr)
+ finally:
+ ui.progress(_('writing'), None)
+
+def report(ui, r1, r2):
+ def getsize(r):
+ s = 0
+ for fn in (r.indexfile, r.datafile):
+ try:
+ s += os.stat(fn).st_size
+ except OSError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+ return s
+
+ oldsize = float(getsize(r1))
+ newsize = float(getsize(r2))
+
+ # argh: have to pass an int to %d, because a float >= 2^32
+ # blows up under Python 2.5 or earlier
+ ui.write(_('old file size: %12d bytes (%6.1f MiB)\n')
+ % (int(oldsize), oldsize / 1024 / 1024))
+ ui.write(_('new file size: %12d bytes (%6.1f MiB)\n')
+ % (int(newsize), newsize / 1024 / 1024))
+
+ shrink_percent = (oldsize - newsize) / oldsize * 100
+ shrink_factor = oldsize / newsize
+ ui.write(_('shrinkage: %.1f%% (%.1fx)\n')
+ % (shrink_percent, shrink_factor))
+
+def shrink(ui, repo, **opts):
+ """shrink a revlog by reordering revisions
+
+ Rewrites all the entries in some revlog of the current repository
+ (by default, the manifest log) to save space.
+
+ Different sort algorithms have different performance
+ characteristics. Use ``--sort`` to select a sort algorithm so you
+ can determine which works best for your data.
+ """
+
+ if not repo.local():
+ raise util.Abort(_('not a local repository: %s') % repo.root)
+
+ fn = opts.get('revlog')
+ if not fn:
+ indexfn = repo.sjoin('00manifest.i')
+ else:
+ if not fn.endswith('.i'):
+ raise util.Abort(_('--revlog option must specify the revlog index '
+ 'file (*.i), not %s') % opts.get('revlog'))
+
+ indexfn = os.path.realpath(fn)
+ store = repo.sjoin('')
+ if not indexfn.startswith(store):
+ raise util.Abort(_('--revlog option must specify a revlog in %s, '
+ 'not %s') % (store, indexfn))
+
+ sortname = opts['sort']
+ try:
+ toposort = globals()['toposort_' + sortname]
+ except KeyError:
+ raise util.Abort(_('no such toposort algorithm: %s') % sortname)
+
+ if not os.path.exists(indexfn):
+ raise util.Abort(_('no such file: %s') % indexfn)
+ if '00changelog' in indexfn:
+ raise util.Abort(_('shrinking the changelog '
+ 'will corrupt your repository'))
+
+ ui.write(_('shrinking %s\n') % indexfn)
+ tmpindexfn = util.mktempcopy(indexfn, emptyok=True)
+
+ r1 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), indexfn)
+ r2 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), tmpindexfn)
+
+ datafn, tmpdatafn = r1.datafile, r2.datafile
+
+ oldindexfn = indexfn + '.old'
+ olddatafn = datafn + '.old'
+ if os.path.exists(oldindexfn) or os.path.exists(olddatafn):
+ raise util.Abort(_('one or both of\n'
+ ' %s\n'
+ ' %s\n'
+ 'exists from a previous run; please clean up '
+ 'before running again') % (oldindexfn, olddatafn))
+
+ # Don't use repo.transaction(), because then things get hairy with
+ # paths: some need to be relative to .hg, and some need to be
+ # absolute. Doing it this way keeps things simple: everything is an
+ # absolute path.
+ lock = repo.lock(wait=False)
+ tr = transaction.transaction(ui.warn,
+ open,
+ repo.sjoin('journal'))
+
+ def ignoremissing(func):
+ def f(*args, **kw):
+ try:
+ return func(*args, **kw)
+ except OSError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+ return f
+
+ try:
+ try:
+ order = toposort(ui, r1)
+
+ suboptimal = 0
+ for i in xrange(1, len(order)):
+ parents = [p for p in r1.parentrevs(order[i])
+ if p != node.nullrev]
+ if parents and order[i - 1] not in parents:
+ suboptimal += 1
+ ui.note(_('%d suboptimal nodes\n') % suboptimal)
+
+ writerevs(ui, r1, r2, order, tr)
+ report(ui, r1, r2)
+ tr.close()
+ except: # re-raises
+ # Abort transaction first, so we truncate the files before
+ # deleting them.
+ tr.abort()
+ for fn in (tmpindexfn, tmpdatafn):
+ ignoremissing(os.unlink)(fn)
+ raise
+ if not opts.get('dry_run'):
+ # racy, both files cannot be renamed atomically
+ # copy files
+ util.oslink(indexfn, oldindexfn)
+ ignoremissing(util.oslink)(datafn, olddatafn)
+
+ # rename
+ util.rename(tmpindexfn, indexfn)
+ try:
+ os.chmod(tmpdatafn, os.stat(datafn).st_mode)
+ util.rename(tmpdatafn, datafn)
+ except OSError, inst:
+ if inst.errno != errno.ENOENT:
+ raise
+ ignoremissing(os.unlink)(datafn)
+ else:
+ for fn in (tmpindexfn, tmpdatafn):
+ ignoremissing(os.unlink)(fn)
+ finally:
+ lock.release()
+
+ if not opts.get('dry_run'):
+ ui.write(
+ _('note: old revlog saved in:\n'
+ ' %s\n'
+ ' %s\n'
+ '(You can delete those files when you are satisfied that your\n'
+ 'repository is still sane. '
+ 'Running \'hg verify\' is strongly recommended.)\n')
+ % (oldindexfn, olddatafn))
+
+cmdtable = {
+ 'shrink': (shrink,
+ [('', 'revlog', '',
+ _('the revlog to shrink (.i)')),
+ ('n', 'dry-run', None,
+ _('do not shrink, simulate only')),
+ ('', 'sort', 'reversepostorder',
+ _('name of sort algorithm to use')),
+ ],
+ _('hg shrink [--revlog PATH]'))
+}
+
+if __name__ == "__main__":
+ print "shrink-revlog.py is now an extension (see hg help extensions)"
diff --git a/contrib/simplemerge b/contrib/simplemerge
new file mode 100755
index 0000000..0c54814
--- /dev/null
+++ b/contrib/simplemerge
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+
+from mercurial import demandimport
+demandimport.enable()
+
+import os, sys
+from mercurial.i18n import _
+from mercurial import simplemerge, fancyopts, util, ui
+
+options = [('L', 'label', [], _('labels to use on conflict markers')),
+ ('a', 'text', None, _('treat all files as text')),
+ ('p', 'print', None,
+ _('print results instead of overwriting LOCAL')),
+ ('', 'no-minimal', None,
+ _('do not try to minimize conflict regions')),
+ ('h', 'help', None, _('display help and exit')),
+ ('q', 'quiet', None, _('suppress output'))]
+
+usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
+
+ Simple three-way file merge utility with a minimal feature set.
+
+ Apply to LOCAL the changes necessary to go from BASE to OTHER.
+
+ By default, LOCAL is overwritten with the results of this operation.
+''')
+
+class ParseError(Exception):
+ """Exception raised on errors in parsing the command line."""
+
+def showhelp():
+ sys.stdout.write(usage)
+ sys.stdout.write('\noptions:\n')
+
+ out_opts = []
+ for shortopt, longopt, default, desc in options:
+ out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
+ longopt and ' --%s' % longopt),
+ '%s' % desc))
+ opts_len = max([len(opt[0]) for opt in out_opts])
+ for first, second in out_opts:
+ sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
+
+try:
+ for fp in (sys.stdin, sys.stdout, sys.stderr):
+ util.setbinary(fp)
+
+ opts = {}
+ try:
+ args = fancyopts.fancyopts(sys.argv[1:], options, opts)
+ except fancyopts.getopt.GetoptError, e:
+ raise ParseError(e)
+ if opts['help']:
+ showhelp()
+ sys.exit(0)
+ if len(args) != 3:
+ raise ParseError(_('wrong number of arguments'))
+ sys.exit(simplemerge.simplemerge(ui.ui(), *args, **opts))
+except ParseError, e:
+ sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
+ showhelp()
+ sys.exit(1)
+except util.Abort, e:
+ sys.stderr.write("abort: %s\n" % e)
+ sys.exit(255)
+except KeyboardInterrupt:
+ sys.exit(255)
diff --git a/contrib/tcsh_completion b/contrib/tcsh_completion
new file mode 100644
index 0000000..da96e6e
--- /dev/null
+++ b/contrib/tcsh_completion
@@ -0,0 +1,50 @@
+#
+# tcsh completion for Mercurial
+#
+# This file has been auto-generated by tcsh_completion_build.sh for
+# Mercurial Distributed SCM (version 1.7.5+157-8a220ae0b2ba)
+#
+# Copyright (C) 2005 TK Soh.
+#
+# 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.
+#
+
+complete hg \
+ 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \
+ 'C/-/( -R --repository \
+ --cwd \
+ -y --noninteractive \
+ -q --quiet \
+ -v --verbose \
+ --config \
+ --debug \
+ --debugger \
+ --encoding \
+ --encodingmode \
+ --traceback \
+ --time \
+ --profile \
+ --version \
+ -h --help)/' \
+ 'p/1/(add addremove annotate blame archive \
+ backout bisect bookmarks branch branches \
+ bundle cat clone commit ci \
+ copy cp debugancestor debugbuilddag debugcheckstate \
+ debugcommands debugcomplete debugdag debugdata debugdate \
+ debugfsinfo debugignore debugindex debugindexdot debuginstall \
+ debugpushkey debugrebuildstate debugrename debugrevspec debugsetparents \
+ debugstate debugsub debugwalk diff export \
+ forget grep heads help identify \
+ id import patch incoming in \
+ init locate log history manifest \
+ merge outgoing out parents paths \
+ pull push recover remove rm \
+ rename move mv resolve revert \
+ rollback root serve showconfig debugconfig \
+ status st summary sum tag \
+ tags tip unbundle update up \
+ checkout co verify version)/'
+
diff --git a/contrib/tcsh_completion_build.sh b/contrib/tcsh_completion_build.sh
new file mode 100755
index 0000000..4e29a0e
--- /dev/null
+++ b/contrib/tcsh_completion_build.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+#
+# tcsh_completion_build.sh - script to generate tcsh completion
+#
+#
+# Copyright (C) 2005 TK Soh.
+#
+# 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.
+#
+#
+# Description
+# -----------
+# This script generates a tcsh source file to support completion
+# of Mercurial commands and options.
+#
+# Instruction:
+# -----------
+# Run this script to generate the tcsh source file, and source
+# the file to add command completion support for Mercurial.
+#
+# tcsh% tcsh_completion.sh FILE
+# tcsh% source FILE
+#
+# If FILE is not specified, tcsh_completion will be generated.
+#
+# Bugs:
+# ----
+# 1. command specific options are not supported
+# 2. hg commands must be specified immediately after 'hg'.
+#
+
+tcsh_file=${1-tcsh_completion}
+
+hg_commands=`hg --debug help | \
+ sed -e '1,/^list of commands:/d' \
+ -e '/^enabled extensions:/,$d' \
+ -e '/^additional help topics:/,$d' \
+ -e '/^ [^ ]/!d; s/[,:]//g;' | \
+ xargs -n5 | \
+ sed -e '$!s/$/ \\\\/g; 2,$s/^ */ /g'`
+
+hg_global_options=`hg -v help | \
+ sed -e '1,/global/d;/^ *-/!d; s/ [^- ].*//' | \
+ sed -e 's/ *$//; $!s/$/ \\\\/g; 2,$s/^ */ /g'`
+
+hg_version=`hg version | sed -e '1q'`
+
+script_name=`basename $0`
+
+cat > $tcsh_file <<END
+#
+# tcsh completion for Mercurial
+#
+# This file has been auto-generated by $script_name for
+# $hg_version
+#
+# Copyright (C) 2005 TK Soh.
+#
+# 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.
+#
+
+complete hg \\
+ 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \\
+ 'C/-/($hg_global_options)/' \\
+ 'p/1/($hg_commands)/'
+
+END
diff --git a/contrib/tmplrewrite.py b/contrib/tmplrewrite.py
new file mode 100755
index 0000000..eb16003
--- /dev/null
+++ b/contrib/tmplrewrite.py
@@ -0,0 +1,23 @@
+#!/usr/bin/python
+import sys, os, re
+
+IGNORE = ['.css', '.py']
+oldre = re.compile('#([\w\|%]+)#')
+
+def rewrite(fn):
+ f = open(fn)
+ new = open(fn + '.new', 'wb')
+ for ln in f:
+ new.write(oldre.sub('{\\1}', ln))
+ new.close()
+ f.close()
+ os.rename(new.name, f.name)
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ print 'usage: python tmplrewrite.py [file [file [file]]]'
+ for fn in sys.argv[1:]:
+ if os.path.splitext(fn) in IGNORE:
+ continue
+ print 'rewriting %s...' % fn
+ rewrite(fn)
diff --git a/contrib/undumprevlog b/contrib/undumprevlog
new file mode 100755
index 0000000..3e074d4
--- /dev/null
+++ b/contrib/undumprevlog
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# Undump a dump from dumprevlog
+# $ hg init
+# $ undumprevlog < repo.dump
+
+import sys
+from mercurial import revlog, node, scmutil, util, transaction
+
+for fp in (sys.stdin, sys.stdout, sys.stderr):
+ util.setbinary(fp)
+
+opener = scmutil.opener('.', False)
+tr = transaction.transaction(sys.stderr.write, opener, "undump.journal")
+while 1:
+ l = sys.stdin.readline()
+ if not l:
+ break
+ if l.startswith("file:"):
+ f = l[6:-1]
+ r = revlog.revlog(opener, f)
+ print f
+ elif l.startswith("node:"):
+ n = node.bin(l[6:-1])
+ elif l.startswith("linkrev:"):
+ lr = int(l[9:-1])
+ elif l.startswith("parents:"):
+ p = l[9:-1].split()
+ p1 = node.bin(p[0])
+ p2 = node.bin(p[1])
+ elif l.startswith("length:"):
+ length = int(l[8:-1])
+ sys.stdin.readline() # start marker
+ d = sys.stdin.read(length)
+ sys.stdin.readline() # end marker
+ r.addrevision(d, tr, lr, p1, p2)
+
+tr.close()
diff --git a/contrib/vim/HGAnnotate.vim b/contrib/vim/HGAnnotate.vim
new file mode 100644
index 0000000..5b5ab95
--- /dev/null
+++ b/contrib/vim/HGAnnotate.vim
@@ -0,0 +1,27 @@
+" $Id: CVSAnnotate.vim,v 1.5 2002/10/01 21:34:02 rhiestan Exp $
+" Vim syntax file
+" Language: CVS annotate output
+" Maintainer: Bob Hiestand <bob@hiestandfamily.org>
+" Last Change: $Date: 2002/10/01 21:34:02 $
+" Remark: Used by the cvscommand plugin. Originally written by Mathieu
+" Clabaut
+if version < 600
+ syntax clear
+elseif exists("b:current_syntax")
+ finish
+endif
+
+syn match cvsDate /\S\S\S \S\+ \d\+ \d\+:\d\+:\d\+ \d\+ [+-]\?\d\+/ contained
+syn match cvsName /^\s*\S\+ / contained nextgroup=cvsVer
+syn match cvsVer /\d\+ / contained nextgroup=cvsDate
+syn region cvsHead start="^" end=":" contains=cvsVer,cvsName,cvsDate
+
+if !exists("did_cvsannotate_syntax_inits")
+let did_cvsannotate_syntax_inits = 1
+hi link cvsText String
+hi link cvsDate Comment
+hi link cvsName Type
+hi link cvsVer Statement
+endif
+
+let b:current_syntax="CVSAnnotate"
diff --git a/contrib/vim/hg-menu.vim b/contrib/vim/hg-menu.vim
new file mode 100644
index 0000000..1664ecb
--- /dev/null
+++ b/contrib/vim/hg-menu.vim
@@ -0,0 +1,93 @@
+" vim600: set foldmethod=marker:
+" =============================================================================
+" Name Of File: hg-menu.vim
+" Description: Interface to Mercurial Version Control.
+" Author: Steve Borho (modified Jeff Lanzarotta's RCS script)
+" Date: Wednesday, October 5, 2005
+" Version: 0.1.0
+" Copyright: None.
+" Usage: These command and gui menu displays useful hg functions
+" Configuration: Your hg executable must be in your path.
+" =============================================================================
+
+" Section: Init {{{1
+if exists("loaded_hg_menu")
+ finish
+endif
+let loaded_hg_menu = 1
+
+" Section: Menu Options {{{1
+if has("gui")
+" amenu H&G.Commit\ File<Tab>,ci :!hg commit %<CR>:e!<CR>
+" amenu H&G.Commit\ All<Tab>,call :!hg commit<CR>:e!<CR>
+" amenu H&G.-SEP1- <nul>
+ amenu H&G.Add<Tab>\\add :!hg add %<CR><CR>
+ amenu H&G.Forget\ Add<Tab>\\fgt :!hg forget %<CR><CR>
+ amenu H&G.Show\ Differences<Tab>\\diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
+ amenu H&G.Revert\ to\ Last\ Version<Tab>\\revert :!hg revert %<CR>:e!<CR>
+ amenu H&G.Show\ History<Tab>\\log :call ShowResults("FileLog", "hg\ log")<CR><CR>
+ amenu H&G.Annotate<Tab>\\an :call ShowResults("annotate", "hg\ annotate")<CR><CR>
+ amenu H&G.-SEP1- <nul>
+ amenu H&G.Repo\ Status<Tab>\\stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
+ amenu H&G.Pull<Tab>\\pull :!hg pull<CR>:e!<CR>
+ amenu H&G.Update<Tab>\\upd :!hg update<CR>:e!<CR>
+endif
+
+" Section: Mappings {{{1
+if(v:version >= 600)
+ " The default Leader is \ 'backslash'
+ map <Leader>add :!hg add %<CR><CR>
+ map <Leader>fgt :!hg forget %<CR><CR>
+ map <Leader>diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
+ map <Leader>revert :!hg revert %<CR>:e!<CR>
+ map <Leader>log :call ShowResults("FileLog", "hg\ log")<CR><CR>
+ map <Leader>an :call ShowResults("annotate", "hg\ annotate")<CR><CR>
+ map <Leader>stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
+ map <Leader>upd :!hg update<CR>:e!<CR>
+ map <Leader>pull :!hg pull<CR>:e!<CR>
+else
+ " pre 6.0, the default Leader was a comma
+ map ,add :!hg add %<CR><CR>
+ map ,fgt :!hg forget %<CR><CR>
+ map ,diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR>
+ map ,revert :!hg revert<CR>:e!<CR>
+ map ,log :call ShowResults("FileLog", "hg\ log")<CR><CR>
+ map ,an :call ShowResults("annotate", "hg\ annotate")<CR><CR>
+ map ,stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR>
+ map ,upd :!hg update<CR>:e!<CR>
+ map ,pull :!hg pull<CR>:e!<CR>
+endif
+
+" Section: Functions {{{1
+" Show the log results of the current file with a revision control system.
+function! ShowResults(bufferName, cmdName)
+ " Modify the shortmess option:
+ " A don't give the "ATTENTION" message when an existing swap file is
+ " found.
+ set shortmess+=A
+
+ " Get the name of the current buffer.
+ let currentBuffer = bufname("%")
+
+ " If a buffer with the name rlog exists, delete it.
+ if bufexists(a:bufferName)
+ execute 'bd! ' a:bufferName
+ endif
+
+ " Create a new buffer.
+ execute 'new ' a:bufferName
+
+ " Execute the command.
+ execute 'r!' a:cmdName ' ' currentBuffer
+
+ " Make is so that the file can't be edited.
+ setlocal nomodified
+ setlocal nomodifiable
+ setlocal readonly
+
+ " Go to the beginning of the buffer.
+ execute "normal 1G"
+
+ " Restore the shortmess option.
+ set shortmess-=A
+endfunction
diff --git a/contrib/vim/hgcommand.vim b/contrib/vim/hgcommand.vim
new file mode 100644
index 0000000..9cbb579
--- /dev/null
+++ b/contrib/vim/hgcommand.vim
@@ -0,0 +1,1703 @@
+" vim600: set foldmethod=marker:
+"
+" Vim plugin to assist in working with HG-controlled files.
+"
+" Last Change: 2006/02/22
+" Version: 1.77
+" Maintainer: Mathieu Clabaut <mathieu.clabaut@gmail.com>
+" License: This file is placed in the public domain.
+" Credits:
+" Bob Hiestand <bob.hiestand@gmail.com> for the fabulous
+" cvscommand.vim from which this script was directly created by
+" means of sed commands and minor tweaks.
+" Note:
+" For Vim7 the use of Bob Hiestand's vcscommand.vim
+" <http://www.vim.org/scripts/script.php?script_id=90>
+" in conjunction with Vladmir Marek's Hg backend
+" <http://www.vim.org/scripts/script.php?script_id=1898>
+" is recommended.
+
+"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+"
+" Section: Documentation
+"----------------------------
+"
+" Documentation should be available by ":help hgcommand" command, once the
+" script has been copied in you .vim/plugin directory.
+"
+" You still can read the documentation at the end of this file. Locate it by
+" searching the "hgcommand-contents" string (and set ft=help to have
+" appropriate syntaxic coloration).
+
+" Section: Plugin header {{{1
+
+" loaded_hgcommand is set to 1 when the initialization begins, and 2 when it
+" completes. This allows various actions to only be taken by functions after
+" system initialization.
+
+if exists("g:loaded_hgcommand")
+ finish
+endif
+let g:loaded_hgcommand = 1
+
+" store 'compatible' settings
+let s:save_cpo = &cpo
+set cpo&vim
+
+" run checks
+let s:script_name = expand("<sfile>:t:r")
+
+function! s:HGCleanupOnFailure(err)
+ echohl WarningMsg
+ echomsg s:script_name . ":" a:err "Plugin not loaded"
+ echohl None
+ let g:loaded_hgcommand = "no"
+ unlet s:save_cpo s:script_name
+endfunction
+
+if v:version < 602
+ call <SID>HGCleanupOnFailure("VIM 6.2 or later required.")
+ finish
+endif
+
+if !exists("*system")
+ call <SID>HGCleanupOnFailure("builtin system() function required.")
+ finish
+endif
+
+let s:script_version = "v0.2"
+
+" Section: Event group setup {{{1
+
+augroup HGCommand
+augroup END
+
+" Section: Plugin initialization {{{1
+silent do HGCommand User HGPluginInit
+
+" Section: Script variable initialization {{{1
+
+let s:HGCommandEditFileRunning = 0
+unlet! s:vimDiffRestoreCmd
+unlet! s:vimDiffSourceBuffer
+unlet! s:vimDiffBufferCount
+unlet! s:vimDiffScratchList
+
+" Section: Utility functions {{{1
+
+" Function: s:HGResolveLink() {{{2
+" Fully resolve the given file name to remove shortcuts or symbolic links.
+
+function! s:HGResolveLink(fileName)
+ let resolved = resolve(a:fileName)
+ if resolved != a:fileName
+ let resolved = <SID>HGResolveLink(resolved)
+ endif
+ return resolved
+endfunction
+
+" Function: s:HGChangeToCurrentFileDir() {{{2
+" Go to the directory in which the current HG-controlled file is located.
+" If this is a HG command buffer, first switch to the original file.
+
+function! s:HGChangeToCurrentFileDir(fileName)
+ let oldCwd=getcwd()
+ let fileName=<SID>HGResolveLink(a:fileName)
+ let newCwd=fnamemodify(fileName, ':h')
+ if strlen(newCwd) > 0
+ execute 'cd' escape(newCwd, ' ')
+ endif
+ return oldCwd
+endfunction
+
+" Function: <SID>HGGetOption(name, default) {{{2
+" Grab a user-specified option to override the default provided. Options are
+" searched in the window, buffer, then global spaces.
+
+function! s:HGGetOption(name, default)
+ if exists("s:" . a:name . "Override")
+ execute "return s:".a:name."Override"
+ elseif exists("w:" . a:name)
+ execute "return w:".a:name
+ elseif exists("b:" . a:name)
+ execute "return b:".a:name
+ elseif exists("g:" . a:name)
+ execute "return g:".a:name
+ else
+ return a:default
+ endif
+endfunction
+
+" Function: s:HGEditFile(name, origBuffNR) {{{2
+" Wrapper around the 'edit' command to provide some helpful error text if the
+" current buffer can't be abandoned. If name is provided, it is used;
+" otherwise, a nameless scratch buffer is used.
+" Returns: 0 if successful, -1 if an error occurs.
+
+function! s:HGEditFile(name, origBuffNR)
+ "Name parameter will be pasted into expression.
+ let name = escape(a:name, ' *?\')
+
+ let editCommand = <SID>HGGetOption('HGCommandEdit', 'edit')
+ if editCommand != 'edit'
+ if <SID>HGGetOption('HGCommandSplit', 'horizontal') == 'horizontal'
+ if name == ""
+ let editCommand = 'rightbelow new'
+ else
+ let editCommand = 'rightbelow split ' . name
+ endif
+ else
+ if name == ""
+ let editCommand = 'vert rightbelow new'
+ else
+ let editCommand = 'vert rightbelow split ' . name
+ endif
+ endif
+ else
+ if name == ""
+ let editCommand = 'enew'
+ else
+ let editCommand = 'edit ' . name
+ endif
+ endif
+
+ " Protect against useless buffer set-up
+ let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
+ try
+ execute editCommand
+ finally
+ let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
+ endtry
+
+ let b:HGOrigBuffNR=a:origBuffNR
+ let b:HGCommandEdit='split'
+endfunction
+
+" Function: s:HGCreateCommandBuffer(cmd, cmdName, statusText, filename) {{{2
+" Creates a new scratch buffer and captures the output from execution of the
+" given command. The name of the scratch buffer is returned.
+
+function! s:HGCreateCommandBuffer(cmd, cmdName, statusText, origBuffNR)
+ let fileName=bufname(a:origBuffNR)
+
+ let resultBufferName=''
+
+ if <SID>HGGetOption("HGCommandNameResultBuffers", 0)
+ let nameMarker = <SID>HGGetOption("HGCommandNameMarker", '_')
+ if strlen(a:statusText) > 0
+ let bufName=a:cmdName . ' -- ' . a:statusText
+ else
+ let bufName=a:cmdName
+ endif
+ let bufName=fileName . ' ' . nameMarker . bufName . nameMarker
+ let counter=0
+ let resultBufferName = bufName
+ while buflisted(resultBufferName)
+ let counter=counter + 1
+ let resultBufferName=bufName . ' (' . counter . ')'
+ endwhile
+ endif
+
+ let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " " . a:cmd
+ "echomsg "DBG :".hgCommand
+ let hgOut = system(hgCommand)
+ " HACK: diff command does not return proper error codes
+ if v:shell_error && a:cmdName != 'hgdiff'
+ if strlen(hgOut) == 0
+ echoerr "HG command failed"
+ else
+ echoerr "HG command failed: " . hgOut
+ endif
+ return -1
+ endif
+ if strlen(hgOut) == 0
+ " Handle case of no output. In this case, it is important to check the
+ " file status, especially since hg edit/unedit may change the attributes
+ " of the file with no visible output.
+
+ echomsg "No output from HG command"
+ checktime
+ return -1
+ endif
+
+ if <SID>HGEditFile(resultBufferName, a:origBuffNR) == -1
+ return -1
+ endif
+
+ set buftype=nofile
+ set noswapfile
+ set filetype=
+
+ if <SID>HGGetOption("HGCommandDeleteOnHide", 0)
+ set bufhidden=delete
+ endif
+
+ silent 0put=hgOut
+
+ " The last command left a blank line at the end of the buffer. If the
+ " last line is folded (a side effect of the 'put') then the attempt to
+ " remove the blank line will kill the last fold.
+ "
+ " This could be fixed by explicitly detecting whether the last line is
+ " within a fold, but I prefer to simply unfold the result buffer altogether.
+
+ if has("folding")
+ setlocal nofoldenable
+ endif
+
+ $d
+ 1
+
+ " Define the environment and execute user-defined hooks.
+
+ let b:HGSourceFile=fileName
+ let b:HGCommand=a:cmdName
+ if a:statusText != ""
+ let b:HGStatusText=a:statusText
+ endif
+
+ silent do HGCommand User HGBufferCreated
+ return bufnr("%")
+endfunction
+
+" Function: s:HGBufferCheck(hgBuffer) {{{2
+" Attempts to locate the original file to which HG operations were applied
+" for a given buffer.
+
+function! s:HGBufferCheck(hgBuffer)
+ let origBuffer = getbufvar(a:hgBuffer, "HGOrigBuffNR")
+ if origBuffer
+ if bufexists(origBuffer)
+ return origBuffer
+ else
+ " Original buffer no longer exists.
+ return -1
+ endif
+ else
+ " No original buffer
+ return a:hgBuffer
+ endif
+endfunction
+
+" Function: s:HGCurrentBufferCheck() {{{2
+" Attempts to locate the original file to which HG operations were applied
+" for the current buffer.
+
+function! s:HGCurrentBufferCheck()
+ return <SID>HGBufferCheck(bufnr("%"))
+endfunction
+
+" Function: s:HGToggleDeleteOnHide() {{{2
+" Toggles on and off the delete-on-hide behavior of HG buffers
+
+function! s:HGToggleDeleteOnHide()
+ if exists("g:HGCommandDeleteOnHide")
+ unlet g:HGCommandDeleteOnHide
+ else
+ let g:HGCommandDeleteOnHide=1
+ endif
+endfunction
+
+" Function: s:HGDoCommand(hgcmd, cmdName, statusText) {{{2
+" General skeleton for HG function execution.
+" Returns: name of the new command buffer containing the command results
+
+function! s:HGDoCommand(cmd, cmdName, statusText)
+ let hgBufferCheck=<SID>HGCurrentBufferCheck()
+ if hgBufferCheck == -1
+ echo "Original buffer no longer exists, aborting."
+ return -1
+ endif
+
+ let fileName=bufname(hgBufferCheck)
+ if isdirectory(fileName)
+ let fileName=fileName . "/" . getline(".")
+ endif
+ let realFileName = fnamemodify(<SID>HGResolveLink(fileName), ':t')
+ let oldCwd=<SID>HGChangeToCurrentFileDir(fileName)
+ try
+ " TODO
+ "if !filereadable('HG/Root')
+ "throw fileName . ' is not a HG-controlled file.'
+ "endif
+ let fullCmd = a:cmd . ' "' . realFileName . '"'
+ "echomsg "DEBUG".fullCmd
+ let resultBuffer=<SID>HGCreateCommandBuffer(fullCmd, a:cmdName, a:statusText, hgBufferCheck)
+ return resultBuffer
+ catch
+ echoerr v:exception
+ return -1
+ finally
+ execute 'cd' escape(oldCwd, ' ')
+ endtry
+endfunction
+
+
+" Function: s:HGGetStatusVars(revision, branch, repository) {{{2
+"
+" Obtains a HG revision number and branch name. The 'revisionVar',
+" 'branchVar'and 'repositoryVar' arguments, if non-empty, contain the names of variables to hold
+" the corresponding results.
+"
+" Returns: string to be exec'd that sets the multiple return values.
+
+function! s:HGGetStatusVars(revisionVar, branchVar, repositoryVar)
+ let hgBufferCheck=<SID>HGCurrentBufferCheck()
+ "echomsg "DBG : in HGGetStatusVars"
+ if hgBufferCheck == -1
+ return ""
+ endif
+ let fileName=bufname(hgBufferCheck)
+ let fileNameWithoutLink=<SID>HGResolveLink(fileName)
+ let realFileName = fnamemodify(fileNameWithoutLink, ':t')
+ let oldCwd=<SID>HGChangeToCurrentFileDir(realFileName)
+ try
+ let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " root "
+ let roottext=system(hgCommand)
+ " Suppress ending null char ! Does it work in window ?
+ let roottext=substitute(roottext,'^.*/\([^/\n\r]*\)\n\_.*$','\1','')
+ if match(getcwd()."/".fileNameWithoutLink, roottext) == -1
+ return ""
+ endif
+ let returnExpression = ""
+ if a:repositoryVar != ""
+ let returnExpression=returnExpression . " | let " . a:repositoryVar . "='" . roottext . "'"
+ endif
+ let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " status -mardui " . realFileName
+ let statustext=system(hgCommand)
+ if(v:shell_error)
+ return ""
+ endif
+ if match(statustext, '^[?I]') >= 0
+ let revision="NEW"
+ elseif match(statustext, '^[R]') >= 0
+ let revision="REMOVED"
+ elseif match(statustext, '^[D]') >= 0
+ let revision="DELETED"
+ elseif match(statustext, '^[A]') >= 0
+ let revision="ADDED"
+ else
+ " The file is tracked, we can try to get is revision number
+ let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " parents "
+ let statustext=system(hgCommand)
+ if(v:shell_error)
+ return ""
+ endif
+ let revision=substitute(statustext, '^changeset:\s*\(\d\+\):.*\_$\_.*$', '\1', "")
+
+ if a:branchVar != "" && match(statustext, '^\_.*\_^branch:') >= 0
+ let branch=substitute(statustext, '^\_.*\_^branch:\s*\(\S\+\)\n\_.*$', '\1', "")
+ let returnExpression=returnExpression . " | let " . a:branchVar . "='" . branch . "'"
+ endif
+ endif
+ if (exists('revision'))
+ let returnExpression = "let " . a:revisionVar . "='" . revision . "' " . returnExpression
+ endif
+
+ return returnExpression
+ finally
+ execute 'cd' escape(oldCwd, ' ')
+ endtry
+endfunction
+
+" Function: s:HGSetupBuffer() {{{2
+" Attempts to set the b:HGBranch, b:HGRevision and b:HGRepository variables.
+
+function! s:HGSetupBuffer(...)
+ if (exists("b:HGBufferSetup") && b:HGBufferSetup && !exists('a:1'))
+ " This buffer is already set up.
+ return
+ endif
+
+ if !<SID>HGGetOption("HGCommandEnableBufferSetup", 0)
+ \ || @% == ""
+ \ || s:HGCommandEditFileRunning > 0
+ \ || exists("b:HGOrigBuffNR")
+ unlet! b:HGRevision
+ unlet! b:HGBranch
+ unlet! b:HGRepository
+ return
+ endif
+
+ if !filereadable(expand("%"))
+ return -1
+ endif
+
+ let revision=""
+ let branch=""
+ let repository=""
+
+ exec <SID>HGGetStatusVars('revision', 'branch', 'repository')
+ "echomsg "DBG ".revision."#".branch."#".repository
+ if revision != ""
+ let b:HGRevision=revision
+ else
+ unlet! b:HGRevision
+ endif
+ if branch != ""
+ let b:HGBranch=branch
+ else
+ unlet! b:HGBranch
+ endif
+ if repository != ""
+ let b:HGRepository=repository
+ else
+ unlet! b:HGRepository
+ endif
+ silent do HGCommand User HGBufferSetup
+ let b:HGBufferSetup=1
+endfunction
+
+" Function: s:HGMarkOrigBufferForSetup(hgbuffer) {{{2
+" Resets the buffer setup state of the original buffer for a given HG buffer.
+" Returns: The HG buffer number in a passthrough mode.
+
+function! s:HGMarkOrigBufferForSetup(hgBuffer)
+ checktime
+ if a:hgBuffer != -1
+ let origBuffer = <SID>HGBufferCheck(a:hgBuffer)
+ "This should never not work, but I'm paranoid
+ if origBuffer != a:hgBuffer
+ call setbufvar(origBuffer, "HGBufferSetup", 0)
+ endif
+ else
+ "We are presumably in the original buffer
+ let b:HGBufferSetup = 0
+ "We do the setup now as now event will be triggered allowing it later.
+ call <SID>HGSetupBuffer()
+ endif
+ return a:hgBuffer
+endfunction
+
+" Function: s:HGOverrideOption(option, [value]) {{{2
+" Provides a temporary override for the given HG option. If no value is
+" passed, the override is disabled.
+
+function! s:HGOverrideOption(option, ...)
+ if a:0 == 0
+ unlet! s:{a:option}Override
+ else
+ let s:{a:option}Override = a:1
+ endif
+endfunction
+
+" Function: s:HGWipeoutCommandBuffers() {{{2
+" Clears all current HG buffers of the specified type for a given source.
+
+function! s:HGWipeoutCommandBuffers(originalBuffer, hgCommand)
+ let buffer = 1
+ while buffer <= bufnr('$')
+ if getbufvar(buffer, 'HGOrigBuffNR') == a:originalBuffer
+ if getbufvar(buffer, 'HGCommand') == a:hgCommand
+ execute 'bw' buffer
+ endif
+ endif
+ let buffer = buffer + 1
+ endwhile
+endfunction
+
+" Function: s:HGInstallDocumentation(full_name, revision) {{{2
+" Install help documentation.
+" Arguments:
+" full_name: Full name of this vim plugin script, including path name.
+" revision: Revision of the vim script. #version# mark in the document file
+" will be replaced with this string with 'v' prefix.
+" Return:
+" 1 if new document installed, 0 otherwise.
+" Note: Cleaned and generalized by guo-peng Wen
+"'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+" Helper function to make mkdir as portable as possible
+function! s:HGFlexiMkdir(dir)
+ if exists("*mkdir") " we can use Vim's own mkdir()
+ call mkdir(a:dir)
+ elseif !exists("+shellslash")
+ call system("mkdir -p '".a:dir."'")
+ else " M$
+ let l:ssl = &shellslash
+ try
+ set shellslash
+ " no single quotes?
+ call system('mkdir "'.a:dir.'"')
+ finally
+ let &shellslash = l:ssl
+ endtry
+ endif
+endfunction
+
+function! s:HGInstallDocumentation(full_name)
+ " Figure out document path based on full name of this script:
+ let l:vim_doc_path = fnamemodify(a:full_name, ":h:h") . "/doc"
+ if filewritable(l:vim_doc_path) != 2
+ echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
+ silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
+ if filewritable(l:vim_doc_path) != 2
+ " Try first item in 'runtimepath':
+ let l:vim_doc_path =
+ \ substitute(&runtimepath, '^\([^,]*\).*', '\1/doc', 'e')
+ if filewritable(l:vim_doc_path) != 2
+ echomsg s:script_name . ": Trying to update docs at" l:vim_doc_path
+ silent! call <SID>HGFlexiMkdir(l:vim_doc_path)
+ if filewritable(l:vim_doc_path) != 2
+ " Put a warning:
+ echomsg "Unable to open documentation directory"
+ echomsg " type `:help add-local-help' for more information."
+ return 0
+ endif
+ endif
+ endif
+ endif
+
+ " Full name of documentation file:
+ let l:doc_file =
+ \ l:vim_doc_path . "/" . s:script_name . ".txt"
+ " Bail out if document file is still up to date:
+ if filereadable(l:doc_file) &&
+ \ getftime(a:full_name) < getftime(l:doc_file)
+ return 0
+ endif
+
+ " temporary global settings
+ let l:lz = &lazyredraw
+ let l:hls = &hlsearch
+ set lazyredraw nohlsearch
+ " Create a new buffer & read in the plugin file (me):
+ 1 new
+ setlocal noswapfile modifiable nomodeline
+ if has("folding")
+ setlocal nofoldenable
+ endif
+ silent execute "read" escape(a:full_name, " ")
+ let l:doc_buf = bufnr("%")
+
+ 1
+ " Delete from first line to a line starts with
+ " === START_DOC
+ silent 1,/^=\{3,}\s\+START_DOC\C/ delete _
+ " Delete from a line starts with
+ " === END_DOC
+ " to the end of the documents:
+ silent /^=\{3,}\s\+END_DOC\C/,$ delete _
+
+ " Add modeline for help doc: the modeline string is mangled intentionally
+ " to avoid it be recognized by VIM:
+ call append(line("$"), "")
+ call append(line("$"), " v" . "im:tw=78:ts=8:ft=help:norl:")
+
+ " Replace revision:
+ silent execute "normal :1s/#version#/" . s:script_version . "/\<CR>"
+ " Save the help document and wipe out buffer:
+ silent execute "wq!" escape(l:doc_file, " ") "| bw" l:doc_buf
+ " Build help tags:
+ silent execute "helptags" l:vim_doc_path
+
+ let &hlsearch = l:hls
+ let &lazyredraw = l:lz
+ return 1
+endfunction
+
+" Section: Public functions {{{1
+
+" Function: HGGetRevision() {{{2
+" Global function for retrieving the current buffer's HG revision number.
+" Returns: Revision number or an empty string if an error occurs.
+
+function! HGGetRevision()
+ let revision=""
+ exec <SID>HGGetStatusVars('revision', '', '')
+ return revision
+endfunction
+
+" Function: HGDisableBufferSetup() {{{2
+" Global function for deactivating the buffer autovariables.
+
+function! HGDisableBufferSetup()
+ let g:HGCommandEnableBufferSetup=0
+ silent! augroup! HGCommandPlugin
+endfunction
+
+" Function: HGEnableBufferSetup() {{{2
+" Global function for activating the buffer autovariables.
+
+function! HGEnableBufferSetup()
+ let g:HGCommandEnableBufferSetup=1
+ augroup HGCommandPlugin
+ au!
+ au BufEnter * call <SID>HGSetupBuffer()
+ au BufWritePost * call <SID>HGSetupBuffer()
+ " Force resetting up buffer on external file change (HG update)
+ au FileChangedShell * call <SID>HGSetupBuffer(1)
+ augroup END
+
+ " Only auto-load if the plugin is fully loaded. This gives other plugins a
+ " chance to run.
+ if g:loaded_hgcommand == 2
+ call <SID>HGSetupBuffer()
+ endif
+endfunction
+
+" Function: HGGetStatusLine() {{{2
+" Default (sample) status line entry for HG files. This is only useful if
+" HG-managed buffer mode is on (see the HGCommandEnableBufferSetup variable
+" for how to do this).
+
+function! HGGetStatusLine()
+ if exists('b:HGSourceFile')
+ " This is a result buffer
+ let value='[' . b:HGCommand . ' ' . b:HGSourceFile
+ if exists('b:HGStatusText')
+ let value=value . ' ' . b:HGStatusText
+ endif
+ let value = value . ']'
+ return value
+ endif
+
+ if exists('b:HGRevision')
+ \ && b:HGRevision != ''
+ \ && exists('b:HGRepository')
+ \ && b:HGRepository != ''
+ \ && exists('g:HGCommandEnableBufferSetup')
+ \ && g:HGCommandEnableBufferSetup
+ if !exists('b:HGBranch')
+ let l:branch=''
+ else
+ let l:branch=b:HGBranch
+ endif
+ return '[HG ' . b:HGRepository . '/' . l:branch .'/' . b:HGRevision . ']'
+ else
+ return ''
+ endif
+endfunction
+
+" Section: HG command functions {{{1
+
+" Function: s:HGAdd() {{{2
+function! s:HGAdd()
+ return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('add', 'hgadd', ''))
+endfunction
+
+" Function: s:HGAnnotate(...) {{{2
+function! s:HGAnnotate(...)
+ if a:0 == 0
+ if &filetype == "HGAnnotate"
+ " This is a HGAnnotate buffer. Perform annotation of the version
+ " indicated by the current line.
+ let revision = substitute(getline("."),'\(^[0-9]*\):.*','\1','')
+ if <SID>HGGetOption('HGCommandAnnotateParent', 0) != 0 && revision > 0
+ let revision = revision - 1
+ endif
+ else
+ let revision=HGGetRevision()
+ if revision == ""
+ echoerr "Unable to obtain HG version information."
+ return -1
+ endif
+ endif
+ else
+ let revision=a:1
+ endif
+
+ if revision == "NEW"
+ echo "No annotatation available for new file."
+ return -1
+ endif
+
+ let resultBuffer=<SID>HGDoCommand('annotate -ndu -r ' . revision, 'hgannotate', revision)
+ "echomsg "DBG: ".resultBuffer
+ if resultBuffer != -1
+ set filetype=HGAnnotate
+ endif
+
+ return resultBuffer
+endfunction
+
+" Function: s:HGCommit() {{{2
+function! s:HGCommit(...)
+ " Handle the commit message being specified. If a message is supplied, it
+ " is used; if bang is supplied, an empty message is used; otherwise, the
+ " user is provided a buffer from which to edit the commit message.
+ if a:2 != "" || a:1 == "!"
+ return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', ''))
+ endif
+
+ let hgBufferCheck=<SID>HGCurrentBufferCheck()
+ if hgBufferCheck == -1
+ echo "Original buffer no longer exists, aborting."
+ return -1
+ endif
+
+ " Protect against windows' backslashes in paths. They confuse exec'd
+ " commands.
+
+ let shellSlashBak = &shellslash
+ try
+ set shellslash
+
+ let messageFileName = tempname()
+
+ let fileName=bufname(hgBufferCheck)
+ let realFilePath=<SID>HGResolveLink(fileName)
+ let newCwd=fnamemodify(realFilePath, ':h')
+ if strlen(newCwd) == 0
+ " Account for autochdir being in effect, which will make this blank, but
+ " we know we'll be in the current directory for the original file.
+ let newCwd = getcwd()
+ endif
+
+ let realFileName=fnamemodify(realFilePath, ':t')
+
+ if <SID>HGEditFile(messageFileName, hgBufferCheck) == -1
+ return
+ endif
+
+ " Protect against case and backslash issues in Windows.
+ let autoPattern = '\c' . messageFileName
+
+ " Ensure existance of group
+ augroup HGCommit
+ augroup END
+
+ execute 'au HGCommit BufDelete' autoPattern 'call delete("' . messageFileName . '")'
+ execute 'au HGCommit BufDelete' autoPattern 'au! HGCommit * ' autoPattern
+
+ " Create a commit mapping. The mapping must clear all autocommands in case
+ " it is invoked when HGCommandCommitOnWrite is active, as well as to not
+ " invoke the buffer deletion autocommand.
+
+ execute 'nnoremap <silent> <buffer> <Plug>HGCommit '.
+ \ ':au! HGCommit * ' . autoPattern . '<CR>'.
+ \ ':g/^HG:/d<CR>'.
+ \ ':update<CR>'.
+ \ ':call <SID>HGFinishCommit("' . messageFileName . '",' .
+ \ '"' . newCwd . '",' .
+ \ '"' . realFileName . '",' .
+ \ hgBufferCheck . ')<CR>'
+
+ silent 0put ='HG: ----------------------------------------------------------------------'
+ silent put =\"HG: Enter Log. Lines beginning with `HG:' are removed automatically\"
+ silent put ='HG: Type <leader>cc (or your own <Plug>HGCommit mapping)'
+
+ if <SID>HGGetOption('HGCommandCommitOnWrite', 1) == 1
+ execute 'au HGCommit BufWritePre' autoPattern 'g/^HG:/d'
+ execute 'au HGCommit BufWritePost' autoPattern 'call <SID>HGFinishCommit("' . messageFileName . '", "' . newCwd . '", "' . realFileName . '", ' . hgBufferCheck . ') | au! * ' autoPattern
+ silent put ='HG: or write this buffer'
+ endif
+
+ silent put ='HG: to finish this commit operation'
+ silent put ='HG: ----------------------------------------------------------------------'
+ $
+ let b:HGSourceFile=fileName
+ let b:HGCommand='HGCommit'
+ set filetype=hg
+ finally
+ let &shellslash = shellSlashBak
+ endtry
+
+endfunction
+
+" Function: s:HGDiff(...) {{{2
+function! s:HGDiff(...)
+ if a:0 == 1
+ let revOptions = '-r' . a:1
+ let caption = a:1 . ' -> current'
+ elseif a:0 == 2
+ let revOptions = '-r' . a:1 . ' -r' . a:2
+ let caption = a:1 . ' -> ' . a:2
+ else
+ let revOptions = ''
+ let caption = ''
+ endif
+
+ let hgdiffopt=<SID>HGGetOption('HGCommandDiffOpt', 'w')
+
+ if hgdiffopt == ""
+ let diffoptionstring=""
+ else
+ let diffoptionstring=" -" . hgdiffopt . " "
+ endif
+
+ let resultBuffer = <SID>HGDoCommand('diff ' . diffoptionstring . revOptions , 'hgdiff', caption)
+ if resultBuffer != -1
+ set filetype=diff
+ endif
+ return resultBuffer
+endfunction
+
+
+" Function: s:HGGotoOriginal(["!]) {{{2
+function! s:HGGotoOriginal(...)
+ let origBuffNR = <SID>HGCurrentBufferCheck()
+ if origBuffNR > 0
+ let origWinNR = bufwinnr(origBuffNR)
+ if origWinNR == -1
+ execute 'buffer' origBuffNR
+ else
+ execute origWinNR . 'wincmd w'
+ endif
+ if a:0 == 1
+ if a:1 == "!"
+ let buffnr = 1
+ let buffmaxnr = bufnr("$")
+ while buffnr <= buffmaxnr
+ if getbufvar(buffnr, "HGOrigBuffNR") == origBuffNR
+ execute "bw" buffnr
+ endif
+ let buffnr = buffnr + 1
+ endwhile
+ endif
+ endif
+ endif
+endfunction
+
+" Function: s:HGFinishCommit(messageFile, targetDir, targetFile) {{{2
+function! s:HGFinishCommit(messageFile, targetDir, targetFile, origBuffNR)
+ if filereadable(a:messageFile)
+ let oldCwd=getcwd()
+ if strlen(a:targetDir) > 0
+ execute 'cd' escape(a:targetDir, ' ')
+ endif
+ let resultBuffer=<SID>HGCreateCommandBuffer('commit -l "' . a:messageFile . '" "'. a:targetFile . '"', 'hgcommit', '', a:origBuffNR)
+ execute 'cd' escape(oldCwd, ' ')
+ execute 'bw' escape(a:messageFile, ' *?\')
+ silent execute 'call delete("' . a:messageFile . '")'
+ return <SID>HGMarkOrigBufferForSetup(resultBuffer)
+ else
+ echoerr "Can't read message file; no commit is possible."
+ return -1
+ endif
+endfunction
+
+" Function: s:HGLog() {{{2
+function! s:HGLog(...)
+ if a:0 == 0
+ let versionOption = ""
+ let caption = ''
+ else
+ let versionOption=" -r" . a:1
+ let caption = a:1
+ endif
+
+ let resultBuffer=<SID>HGDoCommand('log' . versionOption, 'hglog', caption)
+ if resultBuffer != ""
+ set filetype=rcslog
+ endif
+ return resultBuffer
+endfunction
+
+" Function: s:HGRevert() {{{2
+function! s:HGRevert()
+ return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('revert', 'hgrevert', ''))
+endfunction
+
+" Function: s:HGReview(...) {{{2
+function! s:HGReview(...)
+ if a:0 == 0
+ let versiontag=""
+ if <SID>HGGetOption('HGCommandInteractive', 0)
+ let versiontag=input('Revision: ')
+ endif
+ if versiontag == ""
+ let versiontag="(current)"
+ let versionOption=""
+ else
+ let versionOption=" -r " . versiontag . " "
+ endif
+ else
+ let versiontag=a:1
+ let versionOption=" -r " . versiontag . " "
+ endif
+
+ let resultBuffer = <SID>HGDoCommand('cat' . versionOption, 'hgreview', versiontag)
+ if resultBuffer > 0
+ let &filetype=getbufvar(b:HGOrigBuffNR, '&filetype')
+ endif
+
+ return resultBuffer
+endfunction
+
+" Function: s:HGStatus() {{{2
+function! s:HGStatus()
+ return <SID>HGDoCommand('status', 'hgstatus', '')
+endfunction
+
+
+" Function: s:HGUpdate() {{{2
+function! s:HGUpdate()
+ return <SID>HGMarkOrigBufferForSetup(<SID>HGDoCommand('update', 'update', ''))
+endfunction
+
+" Function: s:HGVimDiff(...) {{{2
+function! s:HGVimDiff(...)
+ let originalBuffer = <SID>HGCurrentBufferCheck()
+ let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
+ try
+ " If there's already a VimDiff'ed window, restore it.
+ " There may only be one HGVimDiff original window at a time.
+
+ if exists("s:vimDiffSourceBuffer") && s:vimDiffSourceBuffer != originalBuffer
+ " Clear the existing vimdiff setup by removing the result buffers.
+ call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
+ endif
+
+ " Split and diff
+ if(a:0 == 2)
+ " Reset the vimdiff system, as 2 explicit versions were provided.
+ if exists('s:vimDiffSourceBuffer')
+ call <SID>HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff')
+ endif
+ let resultBuffer = <SID>HGReview(a:1)
+ if resultBuffer < 0
+ echomsg "Can't open HG revision " . a:1
+ return resultBuffer
+ endif
+ let b:HGCommand = 'vimdiff'
+ diffthis
+ let s:vimDiffBufferCount = 1
+ let s:vimDiffScratchList = '{'. resultBuffer . '}'
+ " If no split method is defined, cheat, and set it to vertical.
+ try
+ call <SID>HGOverrideOption('HGCommandSplit', <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
+ let resultBuffer=<SID>HGReview(a:2)
+ finally
+ call <SID>HGOverrideOption('HGCommandSplit')
+ endtry
+ if resultBuffer < 0
+ echomsg "Can't open HG revision " . a:1
+ return resultBuffer
+ endif
+ let b:HGCommand = 'vimdiff'
+ diffthis
+ let s:vimDiffBufferCount = 2
+ let s:vimDiffScratchList = s:vimDiffScratchList . '{'. resultBuffer . '}'
+ else
+ " Add new buffer
+ try
+ " Force splitting behavior, otherwise why use vimdiff?
+ call <SID>HGOverrideOption("HGCommandEdit", "split")
+ call <SID>HGOverrideOption("HGCommandSplit", <SID>HGGetOption('HGCommandDiffSplit', <SID>HGGetOption('HGCommandSplit', 'vertical')))
+ if(a:0 == 0)
+ let resultBuffer=<SID>HGReview()
+ else
+ let resultBuffer=<SID>HGReview(a:1)
+ endif
+ finally
+ call <SID>HGOverrideOption("HGCommandEdit")
+ call <SID>HGOverrideOption("HGCommandSplit")
+ endtry
+ if resultBuffer < 0
+ echomsg "Can't open current HG revision"
+ return resultBuffer
+ endif
+ let b:HGCommand = 'vimdiff'
+ diffthis
+
+ if !exists('s:vimDiffBufferCount')
+ " New instance of vimdiff.
+ let s:vimDiffBufferCount = 2
+ let s:vimDiffScratchList = '{' . resultBuffer . '}'
+
+ " This could have been invoked on a HG result buffer, not the
+ " original buffer.
+ wincmd W
+ execute 'buffer' originalBuffer
+ " Store info for later original buffer restore
+ let s:vimDiffRestoreCmd =
+ \ "call setbufvar(".originalBuffer.", \"&diff\", ".getbufvar(originalBuffer, '&diff').")"
+ \ . "|call setbufvar(".originalBuffer.", \"&foldcolumn\", ".getbufvar(originalBuffer, '&foldcolumn').")"
+ \ . "|call setbufvar(".originalBuffer.", \"&foldenable\", ".getbufvar(originalBuffer, '&foldenable').")"
+ \ . "|call setbufvar(".originalBuffer.", \"&foldmethod\", '".getbufvar(originalBuffer, '&foldmethod')."')"
+ \ . "|call setbufvar(".originalBuffer.", \"&scrollbind\", ".getbufvar(originalBuffer, '&scrollbind').")"
+ \ . "|call setbufvar(".originalBuffer.", \"&wrap\", ".getbufvar(originalBuffer, '&wrap').")"
+ \ . "|if &foldmethod=='manual'|execute 'normal! zE'|endif"
+ diffthis
+ wincmd w
+ else
+ " Adding a window to an existing vimdiff
+ let s:vimDiffBufferCount = s:vimDiffBufferCount + 1
+ let s:vimDiffScratchList = s:vimDiffScratchList . '{' . resultBuffer . '}'
+ endif
+ endif
+
+ let s:vimDiffSourceBuffer = originalBuffer
+
+ " Avoid executing the modeline in the current buffer after the autocommand.
+
+ let currentBuffer = bufnr('%')
+ let saveModeline = getbufvar(currentBuffer, '&modeline')
+ try
+ call setbufvar(currentBuffer, '&modeline', 0)
+ silent do HGCommand User HGVimDiffFinish
+ finally
+ call setbufvar(currentBuffer, '&modeline', saveModeline)
+ endtry
+ return resultBuffer
+ finally
+ let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
+ endtry
+endfunction
+
+" Section: Command definitions {{{1
+" Section: Primary commands {{{2
+com! HGAdd call <SID>HGAdd()
+com! -nargs=? HGAnnotate call <SID>HGAnnotate(<f-args>)
+com! -bang -nargs=? HGCommit call <SID>HGCommit(<q-bang>, <q-args>)
+com! -nargs=* HGDiff call <SID>HGDiff(<f-args>)
+com! -bang HGGotoOriginal call <SID>HGGotoOriginal(<q-bang>)
+com! -nargs=? HGLog call <SID>HGLog(<f-args>)
+com! HGRevert call <SID>HGRevert()
+com! -nargs=? HGReview call <SID>HGReview(<f-args>)
+com! HGStatus call <SID>HGStatus()
+com! HGUpdate call <SID>HGUpdate()
+com! -nargs=* HGVimDiff call <SID>HGVimDiff(<f-args>)
+
+" Section: HG buffer management commands {{{2
+com! HGDisableBufferSetup call HGDisableBufferSetup()
+com! HGEnableBufferSetup call HGEnableBufferSetup()
+
+" Allow reloading hgcommand.vim
+com! HGReload unlet! g:loaded_hgcommand | runtime plugin/hgcommand.vim
+
+" Section: Plugin command mappings {{{1
+nnoremap <silent> <Plug>HGAdd :HGAdd<CR>
+nnoremap <silent> <Plug>HGAnnotate :HGAnnotate<CR>
+nnoremap <silent> <Plug>HGCommit :HGCommit<CR>
+nnoremap <silent> <Plug>HGDiff :HGDiff<CR>
+nnoremap <silent> <Plug>HGGotoOriginal :HGGotoOriginal<CR>
+nnoremap <silent> <Plug>HGClearAndGotoOriginal :HGGotoOriginal!<CR>
+nnoremap <silent> <Plug>HGLog :HGLog<CR>
+nnoremap <silent> <Plug>HGRevert :HGRevert<CR>
+nnoremap <silent> <Plug>HGReview :HGReview<CR>
+nnoremap <silent> <Plug>HGStatus :HGStatus<CR>
+nnoremap <silent> <Plug>HGUpdate :HGUpdate<CR>
+nnoremap <silent> <Plug>HGVimDiff :HGVimDiff<CR>
+
+" Section: Default mappings {{{1
+if !hasmapto('<Plug>HGAdd')
+ nmap <unique> <Leader>hga <Plug>HGAdd
+endif
+if !hasmapto('<Plug>HGAnnotate')
+ nmap <unique> <Leader>hgn <Plug>HGAnnotate
+endif
+if !hasmapto('<Plug>HGClearAndGotoOriginal')
+ nmap <unique> <Leader>hgG <Plug>HGClearAndGotoOriginal
+endif
+if !hasmapto('<Plug>HGCommit')
+ nmap <unique> <Leader>hgc <Plug>HGCommit
+endif
+if !hasmapto('<Plug>HGDiff')
+ nmap <unique> <Leader>hgd <Plug>HGDiff
+endif
+if !hasmapto('<Plug>HGGotoOriginal')
+ nmap <unique> <Leader>hgg <Plug>HGGotoOriginal
+endif
+if !hasmapto('<Plug>HGLog')
+ nmap <unique> <Leader>hgl <Plug>HGLog
+endif
+if !hasmapto('<Plug>HGRevert')
+ nmap <unique> <Leader>hgq <Plug>HGRevert
+endif
+if !hasmapto('<Plug>HGReview')
+ nmap <unique> <Leader>hgr <Plug>HGReview
+endif
+if !hasmapto('<Plug>HGStatus')
+ nmap <unique> <Leader>hgs <Plug>HGStatus
+endif
+if !hasmapto('<Plug>HGUpdate')
+ nmap <unique> <Leader>hgu <Plug>HGUpdate
+endif
+if !hasmapto('<Plug>HGVimDiff')
+ nmap <unique> <Leader>hgv <Plug>HGVimDiff
+endif
+
+" Section: Menu items {{{1
+silent! aunmenu Plugin.HG
+amenu <silent> &Plugin.HG.&Add <Plug>HGAdd
+amenu <silent> &Plugin.HG.A&nnotate <Plug>HGAnnotate
+amenu <silent> &Plugin.HG.&Commit <Plug>HGCommit
+amenu <silent> &Plugin.HG.&Diff <Plug>HGDiff
+amenu <silent> &Plugin.HG.&Log <Plug>HGLog
+amenu <silent> &Plugin.HG.Revert <Plug>HGRevert
+amenu <silent> &Plugin.HG.&Review <Plug>HGReview
+amenu <silent> &Plugin.HG.&Status <Plug>HGStatus
+amenu <silent> &Plugin.HG.&Update <Plug>HGUpdate
+amenu <silent> &Plugin.HG.&VimDiff <Plug>HGVimDiff
+
+" Section: Autocommands to restore vimdiff state {{{1
+function! s:HGVimDiffRestore(vimDiffBuff)
+ let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning + 1
+ try
+ if exists("s:vimDiffSourceBuffer")
+ if a:vimDiffBuff == s:vimDiffSourceBuffer
+ " Original file is being removed.
+ unlet! s:vimDiffSourceBuffer
+ unlet! s:vimDiffBufferCount
+ unlet! s:vimDiffRestoreCmd
+ unlet! s:vimDiffScratchList
+ elseif match(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}') >= 0
+ let s:vimDiffScratchList = substitute(s:vimDiffScratchList, '{' . a:vimDiffBuff . '}', '', '')
+ let s:vimDiffBufferCount = s:vimDiffBufferCount - 1
+ if s:vimDiffBufferCount == 1 && exists('s:vimDiffRestoreCmd')
+ " All scratch buffers are gone, reset the original.
+ " Only restore if the source buffer is still in Diff mode
+
+ let sourceWinNR=bufwinnr(s:vimDiffSourceBuffer)
+ if sourceWinNR != -1
+ " The buffer is visible in at least one window
+ let currentWinNR = winnr()
+ while winbufnr(sourceWinNR) != -1
+ if winbufnr(sourceWinNR) == s:vimDiffSourceBuffer
+ execute sourceWinNR . 'wincmd w'
+ if getwinvar('', "&diff")
+ execute s:vimDiffRestoreCmd
+ endif
+ endif
+ let sourceWinNR = sourceWinNR + 1
+ endwhile
+ execute currentWinNR . 'wincmd w'
+ else
+ " The buffer is hidden. It must be visible in order to set the
+ " diff option.
+ let currentBufNR = bufnr('')
+ execute "hide buffer" s:vimDiffSourceBuffer
+ if getwinvar('', "&diff")
+ execute s:vimDiffRestoreCmd
+ endif
+ execute "hide buffer" currentBufNR
+ endif
+
+ unlet s:vimDiffRestoreCmd
+ unlet s:vimDiffSourceBuffer
+ unlet s:vimDiffBufferCount
+ unlet s:vimDiffScratchList
+ elseif s:vimDiffBufferCount == 0
+ " All buffers are gone.
+ unlet s:vimDiffSourceBuffer
+ unlet s:vimDiffBufferCount
+ unlet s:vimDiffScratchList
+ endif
+ endif
+ endif
+ finally
+ let s:HGCommandEditFileRunning = s:HGCommandEditFileRunning - 1
+ endtry
+endfunction
+
+augroup HGVimDiffRestore
+ au!
+ au BufUnload * call <SID>HGVimDiffRestore(expand("<abuf>"))
+augroup END
+
+" Section: Optional activation of buffer management {{{1
+
+if s:HGGetOption('HGCommandEnableBufferSetup', 1)
+ call HGEnableBufferSetup()
+endif
+
+" Section: Doc installation {{{1
+
+if <SID>HGInstallDocumentation(expand("<sfile>:p"))
+ echomsg s:script_name s:script_version . ": updated documentation"
+endif
+
+" Section: Plugin completion {{{1
+
+" delete one-time vars and functions
+delfunction <SID>HGInstallDocumentation
+delfunction <SID>HGFlexiMkdir
+delfunction <SID>HGCleanupOnFailure
+unlet s:script_version s:script_name
+
+let g:loaded_hgcommand=2
+silent do HGCommand User HGPluginFinish
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
+" vim:se expandtab sts=2 sw=2:
+finish
+
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+" Section: Documentation content {{{1
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+=== START_DOC
+*hgcommand.txt* Mercurial vim integration #version#
+
+
+ HGCOMMAND REFERENCE MANUAL~
+
+
+Author: Mathieu Clabaut <mathieu.clabaut@gmail.com>
+Credits: Bob Hiestand <bob.hiestand@gmail.com>
+Mercurial: http://mercurial.selenic.com/
+ Mercurial (noted Hg) is a fast, lightweight Source Control Management
+ system designed for efficient handling of very large distributed projects.
+
+==============================================================================
+1. Contents *hgcommand-contents*
+
+ Installation : |hgcommand-install|
+ HGCommand Intro : |hgcommand|
+ HGCommand Manual : |hgcommand-manual|
+ Customization : |hgcommand-customize|
+ Bugs : |hgcommand-bugs|
+
+==============================================================================
+2. HGCommand Installation *hgcommand-install*
+
+ In order to install the plugin, place the hgcommand.vim file into a plugin'
+ directory in your runtime path (please see |add-global-plugin| and
+ |'runtimepath'|.
+
+ HGCommand may be customized by setting variables, creating maps, and
+ specifying event handlers. Please see |hgcommand-customize| for more
+ details.
+
+ *hgcommand-auto-help*
+ The help file is automagically generated when the |hgcommand| script is
+ loaded for the first time.
+
+==============================================================================
+
+3. HGCommand Intro *hgcommand*
+ *hgcommand-intro*
+
+ The HGCommand plugin provides global ex commands for manipulating
+ HG-controlled source files. In general, each command operates on the
+ current buffer and accomplishes a separate hg function, such as update,
+ commit, log, and others (please see |hgcommand-commands| for a list of all
+ available commands). The results of each operation are displayed in a
+ scratch buffer. Several buffer variables are defined for those scratch
+ buffers (please see |hgcommand-buffer-variables|).
+
+ The notion of "current file" means either the current buffer, or, in the
+ case of a directory buffer, the file on the current line within the buffer.
+
+ For convenience, any HGCommand invoked on a HGCommand scratch buffer acts
+ as though it was invoked on the original file and splits the screen so that
+ the output appears in a new window.
+
+ Many of the commands accept revisions as arguments. By default, most
+ operate on the most recent revision on the current branch if no revision is
+ specified (though see |HGCommandInteractive| to prompt instead).
+
+ Each HGCommand is mapped to a key sequence starting with the <Leader>
+ keystroke. The default mappings may be overridden by supplying different
+ mappings before the plugin is loaded, such as in the vimrc, in the standard
+ fashion for plugin mappings. For examples, please see
+ |hgcommand-mappings-override|.
+
+ The HGCommand plugin may be configured in several ways. For more details,
+ please see |hgcommand-customize|.
+
+==============================================================================
+4. HGCommand Manual *hgcommand-manual*
+
+4.1 HGCommand commands *hgcommand-commands*
+
+ HGCommand defines the following commands:
+
+ |:HGAdd|
+ |:HGAnnotate|
+ |:HGCommit|
+ |:HGDiff|
+ |:HGGotoOriginal|
+ |:HGLog|
+ |:HGRevert|
+ |:HGReview|
+ |:HGStatus|
+ |:HGUpdate|
+ |:HGVimDiff|
+
+:HGAdd *:HGAdd*
+
+ This command performs "hg add" on the current file. Please note, this does
+ not commit the newly-added file.
+
+:HGAnnotate *:HGAnnotate*
+
+ This command performs "hg annotate" on the current file. If an argument is
+ given, the argument is used as a revision number to display. If not given
+ an argument, it uses the most recent version of the file on the current
+ branch. Additionally, if the current buffer is a HGAnnotate buffer
+ already, the version number on the current line is used.
+
+ If the |HGCommandAnnotateParent| variable is set to a non-zero value, the
+ version previous to the one on the current line is used instead. This
+ allows one to navigate back to examine the previous version of a line.
+
+ The filetype of the HGCommand scratch buffer is set to 'HGAnnotate', to
+ take advantage of the bundled syntax file.
+
+
+:HGCommit[!] *:HGCommit*
+
+ If called with arguments, this performs "hg commit" using the arguments as
+ the log message.
+
+ If '!' is used with no arguments, an empty log message is committed.
+
+ If called with no arguments, this is a two-step command. The first step
+ opens a buffer to accept a log message. When that buffer is written, it is
+ automatically closed and the file is committed using the information from
+ that log message. The commit can be abandoned if the log message buffer is
+ deleted or wiped before being written.
+
+ Alternatively, the mapping that is used to invoke :HGCommit (by default
+ <Leader>hgc) can be used in the log message buffer to immediately commit.
+ This is useful if the |HGCommandCommitOnWrite| variable is set to 0 to
+ disable the normal commit-on-write behavior.
+
+:HGDiff *:HGDiff*
+
+ With no arguments, this performs "hg diff" on the current file against the
+ current repository version.
+
+ With one argument, "hg diff" is performed on the current file against the
+ specified revision.
+
+ With two arguments, hg diff is performed between the specified revisions of
+ the current file.
+
+ This command uses the 'HGCommandDiffOpt' variable to specify diff options.
+ If that variable does not exist, then 'wbBc' is assumed. If you wish to
+ have no options, then set it to the empty string.
+
+
+:HGGotoOriginal *:HGGotoOriginal*
+
+ This command returns the current window to the source buffer, if the
+ current buffer is a HG command output buffer.
+
+:HGGotoOriginal!
+
+ Like ":HGGotoOriginal" but also executes :bufwipeout on all HG command
+ output buffers for the source buffer.
+
+:HGLog *:HGLog*
+
+ Performs "hg log" on the current file.
+
+ If an argument is given, it is passed as an argument to the "-r" option of
+ "hg log".
+
+:HGRevert *:HGRevert*
+
+ Replaces the current file with the most recent version from the repository
+ in order to wipe out any undesired changes.
+
+:HGReview *:HGReview*
+
+ Retrieves a particular version of the current file. If no argument is
+ given, the most recent version of the file on the current branch is
+ retrieved. Otherwise, the specified version is retrieved.
+
+:HGStatus *:HGStatus*
+
+ Performs "hg status" on the current file.
+
+:HGUpdate *:HGUpdate*
+
+ Performs "hg update" on the current file. This intentionally does not
+ automatically reload the current buffer, though vim should prompt the user
+ to do so if the underlying file is altered by this command.
+
+:HGVimDiff *:HGVimDiff*
+
+ With no arguments, this prompts the user for a revision and then uses
+ vimdiff to display the differences between the current file and the
+ specified revision. If no revision is specified, the most recent version
+ of the file on the current branch is used.
+
+ With one argument, that argument is used as the revision as above. With
+ two arguments, the differences between the two revisions is displayed using
+ vimdiff.
+
+ With either zero or one argument, the original buffer is used to perform
+ the vimdiff. When the other buffer is closed, the original buffer will be
+ returned to normal mode.
+
+ Once vimdiff mode is started using the above methods, additional vimdiff
+ buffers may be added by passing a single version argument to the command.
+ There may be up to 4 vimdiff buffers total.
+
+ Using the 2-argument form of the command resets the vimdiff to only those 2
+ versions. Additionally, invoking the command on a different file will
+ close the previous vimdiff buffers.
+
+
+4.2 Mappings *hgcommand-mappings*
+
+ By default, a mapping is defined for each command. These mappings execute
+ the default (no-argument) form of each command.
+
+ <Leader>hga HGAdd
+ <Leader>hgn HGAnnotate
+ <Leader>hgc HGCommit
+ <Leader>hgd HGDiff
+ <Leader>hgg HGGotoOriginal
+ <Leader>hgG HGGotoOriginal!
+ <Leader>hgl HGLog
+ <Leader>hgr HGReview
+ <Leader>hgs HGStatus
+ <Leader>hgu HGUpdate
+ <Leader>hgv HGVimDiff
+
+ *hgcommand-mappings-override*
+
+ The default mappings can be overriden by user-provided instead by mapping
+ to <Plug>CommandName. This is especially useful when these mappings
+ collide with other existing mappings (vim will warn of this during plugin
+ initialization, but will not clobber the existing mappings).
+
+ For instance, to override the default mapping for :HGAdd to set it to
+ '\add', add the following to the vimrc: >
+
+ nmap \add <Plug>HGAdd
+<
+4.3 Automatic buffer variables *hgcommand-buffer-variables*
+
+ Several buffer variables are defined in each HGCommand result buffer.
+ These may be useful for additional customization in callbacks defined in
+ the event handlers (please see |hgcommand-events|).
+
+ The following variables are automatically defined:
+
+b:hgOrigBuffNR *b:hgOrigBuffNR*
+
+ This variable is set to the buffer number of the source file.
+
+b:hgcmd *b:hgcmd*
+
+ This variable is set to the name of the hg command that created the result
+ buffer.
+==============================================================================
+
+5. Configuration and customization *hgcommand-customize*
+ *hgcommand-config*
+
+ The HGCommand plugin can be configured in two ways: by setting
+ configuration variables (see |hgcommand-options|) or by defining HGCommand
+ event handlers (see |hgcommand-events|). Additionally, the HGCommand
+ plugin provides several option for naming the HG result buffers (see
+ |hgcommand-naming|) and supported a customized status line (see
+ |hgcommand-statusline| and |hgcommand-buffer-management|).
+
+5.1 HGCommand configuration variables *hgcommand-options*
+
+ Several variables affect the plugin's behavior. These variables are
+ checked at time of execution, and may be defined at the window, buffer, or
+ global level and are checked in that order of precedence.
+
+
+ The following variables are available:
+
+ |HGCommandAnnotateParent|
+ |HGCommandCommitOnWrite|
+ |HGCommandHGExec|
+ |HGCommandDeleteOnHide|
+ |HGCommandDiffOpt|
+ |HGCommandDiffSplit|
+ |HGCommandEdit|
+ |HGCommandEnableBufferSetup|
+ |HGCommandInteractive|
+ |HGCommandNameMarker|
+ |HGCommandNameResultBuffers|
+ |HGCommandSplit|
+
+HGCommandAnnotateParent *HGCommandAnnotateParent*
+
+ This variable, if set to a non-zero value, causes the zero-argument form of
+ HGAnnotate when invoked on a HGAnnotate buffer to go to the version
+ previous to that displayed on the current line. If not set, it defaults to
+ 0.
+
+HGCommandCommitOnWrite *HGCommandCommitOnWrite*
+
+ This variable, if set to a non-zero value, causes the pending hg commit to
+ take place immediately as soon as the log message buffer is written. If
+ set to zero, only the HGCommit mapping will cause the pending commit to
+ occur. If not set, it defaults to 1.
+
+HGCommandHGExec *HGCommandHGExec*
+
+ This variable controls the executable used for all HG commands. If not
+ set, it defaults to "hg".
+
+HGCommandDeleteOnHide *HGCommandDeleteOnHide*
+
+ This variable, if set to a non-zero value, causes the temporary HG result
+ buffers to automatically delete themselves when hidden.
+
+HGCommandDiffOpt *HGCommandDiffOpt*
+
+ This variable, if set, determines the options passed to the diff command of
+ HG. If not set, it defaults to 'w'.
+
+HGCommandDiffSplit *HGCommandDiffSplit*
+
+ This variable overrides the |HGCommandSplit| variable, but only for buffers
+ created with |:HGVimDiff|.
+
+HGCommandEdit *HGCommandEdit*
+
+ This variable controls whether the original buffer is replaced ('edit') or
+ split ('split'). If not set, it defaults to 'edit'.
+
+HGCommandEnableBufferSetup *HGCommandEnableBufferSetup*
+
+ This variable, if set to a non-zero value, activates HG buffer management
+ mode see (|hgcommand-buffer-management|). This mode means that three
+ buffer variables, 'HGRepository', 'HGRevision' and 'HGBranch', are set if
+ the file is HG-controlled. This is useful for displaying version
+ information in the status bar.
+
+HGCommandInteractive *HGCommandInteractive*
+
+ This variable, if set to a non-zero value, causes appropriate commands (for
+ the moment, only |:HGReview|) to query the user for a revision to use
+ instead of the current revision if none is specified.
+
+HGCommandNameMarker *HGCommandNameMarker*
+
+ This variable, if set, configures the special attention-getting characters
+ that appear on either side of the hg buffer type in the buffer name. This
+ has no effect unless |HGCommandNameResultBuffers| is set to a true value.
+ If not set, it defaults to '_'.
+
+HGCommandNameResultBuffers *HGCommandNameResultBuffers*
+
+ This variable, if set to a true value, causes the hg result buffers to be
+ named in the old way ('<source file name> _<hg command>_'). If not set or
+ set to a false value, the result buffer is nameless.
+
+HGCommandSplit *HGCommandSplit*
+
+ This variable controls the orientation of the various window splits that
+ may occur (such as with HGVimDiff, when using a HG command on a HG command
+ buffer, or when the |HGCommandEdit| variable is set to 'split'. If set to
+ 'horizontal', the resulting windows will be on stacked on top of one
+ another. If set to 'vertical', the resulting windows will be side-by-side.
+ If not set, it defaults to 'horizontal' for all but HGVimDiff windows.
+
+5.2 HGCommand events *hgcommand-events*
+
+ For additional customization, HGCommand can trigger user-defined events.
+ Event handlers are provided by defining User event autocommands (see
+ |autocommand|, |User|) in the HGCommand group with patterns matching the
+ event name.
+
+ For instance, the following could be added to the vimrc to provide a 'q'
+ mapping to quit a HGCommand scratch buffer: >
+
+ augroup HGCommand
+ au HGCommand User HGBufferCreated silent! nmap <unique> <buffer> q:
+ bwipeout<cr>
+ augroup END
+<
+
+ The following hooks are available:
+
+HGBufferCreated This event is fired just after a hg command result
+ buffer is created and filled with the result of a hg
+ command. It is executed within the context of the HG
+ command buffer. The HGCommand buffer variables may be
+ useful for handlers of this event (please see
+ |hgcommand-buffer-variables|).
+
+HGBufferSetup This event is fired just after HG buffer setup occurs,
+ if enabled.
+
+HGPluginInit This event is fired when the HGCommand plugin first
+ loads.
+
+HGPluginFinish This event is fired just after the HGCommand plugin
+ loads.
+
+HGVimDiffFinish This event is fired just after the HGVimDiff command
+ executes to allow customization of, for instance,
+ window placement and focus.
+
+5.3 HGCommand buffer naming *hgcommand-naming*
+
+ By default, the buffers containing the result of HG commands are nameless
+ scratch buffers. It is intended that buffer variables of those buffers be
+ used to customize the statusline option so that the user may fully control
+ the display of result buffers.
+
+ If the old-style naming is desired, please enable the
+ |HGCommandNameResultBuffers| variable. Then, each result buffer will
+ receive a unique name that includes the source file name, the HG command,
+ and any extra data (such as revision numbers) that were part of the
+ command.
+
+5.4 HGCommand status line support *hgcommand-statusline*
+
+ It is intended that the user will customize the |'statusline'| option to
+ include HG result buffer attributes. A sample function that may be used in
+ the |'statusline'| option is provided by the plugin, HGGetStatusLine(). In
+ order to use that function in the status line, do something like the
+ following: >
+
+ set statusline=%<%f\ %{HGGetStatusLine()}\ %h%m%r%=%l,%c%V\ %P
+<
+ of which %{HGGetStatusLine()} is the relevant portion.
+
+ The sample HGGetStatusLine() function handles both HG result buffers and
+ HG-managed files if HGCommand buffer management is enabled (please see
+ |hgcommand-buffer-management|).
+
+5.5 HGCommand buffer management *hgcommand-buffer-management*
+
+ The HGCommand plugin can operate in buffer management mode, which means
+ that it attempts to set two buffer variables ('HGRevision' and 'HGBranch')
+ upon entry into a buffer. This is rather slow because it means that 'hg
+ status' will be invoked at each entry into a buffer (during the |BufEnter|
+ autocommand).
+
+ This mode is enabled by default. In order to disable it, set the
+ |HGCommandEnableBufferSetup| variable to a false (zero) value. Enabling
+ this mode simply provides the buffer variables mentioned above. The user
+ must explicitly include those in the |'statusline'| option if they are to
+ appear in the status line (but see |hgcommand-statusline| for a simple way
+ to do that).
+
+==============================================================================
+9. Tips *hgcommand-tips*
+
+9.1 Split window annotation, by Michael Anderson >
+
+ :nmap <Leader>hgN :vs<CR><C-w>h<Leader>hgn:vertical res 40<CR>
+ \ggdddd:set scb<CR>:set nowrap<CR><C-w>lgg:set scb<CR>
+ \:set nowrap<CR>
+<
+
+ This splits the buffer vertically, puts an annotation on the left (minus
+ the header) with the width set to 40. An editable/normal copy is placed on
+ the right. The two versions are scroll locked so they move as one. and
+ wrapping is turned off so that the lines line up correctly. The advantages
+ are...
+
+ 1) You get a versioning on the right.
+ 2) You can still edit your own code.
+ 3) Your own code still has syntax highlighting.
+
+==============================================================================
+
+8. Known bugs *hgcommand-bugs*
+
+ Please let me know if you run across any.
+
+ HGVimDiff, when using the original (real) source buffer as one of the diff
+ buffers, uses some hacks to try to restore the state of the original buffer
+ when the scratch buffer containing the other version is destroyed. There
+ may still be bugs in here, depending on many configuration details.
+
+==============================================================================
+
+9. TODO *hgcommand-todo*
+
+ Integrate symlink tracking once HG will support them.
+==============================================================================
+=== END_DOC
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+" v im:tw=78:ts=8:ft=help:norl:
+" vim600: set foldmethod=marker tabstop=8 shiftwidth=2 softtabstop=2 smartindent smarttab :
+"fileencoding=iso-8859-15
diff --git a/contrib/vim/hgtest.vim b/contrib/vim/hgtest.vim
new file mode 100644
index 0000000..8ed24c0
--- /dev/null
+++ b/contrib/vim/hgtest.vim
@@ -0,0 +1,41 @@
+" Vim syntax file
+" Language: Mercurial unified tests
+" Author: Steve Losh (steve@stevelosh.com)
+"
+" Add the following line to your ~/.vimrc to enable:
+" au BufNewFile,BufRead *.t set filetype=hgtest
+"
+" If you want folding you'll need the following line as well:
+" let hgtest_fold=1
+"
+" You might also want to set the starting foldlevel for hgtest files:
+" autocmd Syntax hgtest setlocal foldlevel=1
+
+if exists("b:current_syntax")
+ finish
+endif
+
+syn include @Shell syntax/sh.vim
+
+syn match hgtestComment /^[^ ].*$/
+syn region hgtestOutput start=/^ [^$>]/ start=/^ $/ end=/\v.(\n\n*[^ ])\@=/me=s end=/^ [$>]/me=e-3 end=/^$/ fold containedin=hgtestBlock
+syn match hgtestCommandStart /^ \$ / containedin=hgtestCommand
+syn region hgtestCommand start=/^ \$ /hs=s+4,rs=s+4 end=/^ [^>]/me=e-3 end=/^ $/me=e-2 containedin=hgtestBlock contains=@Shell keepend
+syn region hgtestBlock start=/^ /ms=e-2 end=/\v.(\n\n*[^ ])\@=/me=s end=/^$/me=e-1 fold keepend
+
+hi link hgtestCommandStart Keyword
+hi link hgtestComment Normal
+hi link hgtestOutput Comment
+
+if exists("hgtest_fold")
+ setlocal foldmethod=syntax
+endif
+
+syn sync match hgtestSync grouphere NONE "^$"
+syn sync maxlines=200
+
+" It's okay to set tab settings here, because an indent of two spaces is specified
+" by the file format.
+setlocal tabstop=2 softtabstop=2 shiftwidth=2 expandtab
+
+let b:current_syntax = "hgtest"
diff --git a/contrib/vim/patchreview.txt b/contrib/vim/patchreview.txt
new file mode 100644
index 0000000..78511f9
--- /dev/null
+++ b/contrib/vim/patchreview.txt
@@ -0,0 +1,97 @@
+*patchreview.txt* Vim global plugin for doing single, multi-patch or diff code reviews
+ Version v0.2.2 (for Vim version 7.0 or higher)
+
+ Author: Manpreet Singh < junkblocker@yahoo.com >
+ Copyright (C) 2006-2010 by Manpreet Singh
+ License : This file is placed in the public domain.
+
+=============================================================================
+
+CONTENTS *patchreview* *diffreview* *patchreview-contents*
+
+ 1. Contents.........................................: |patchreview-contents|
+ 2. Introduction.....................................: |patchreview-intro|
+ 3. PatchReview options..............................: |patchreview-options|
+ 4. PatchReview Usage................................: |patchreview-usage|
+ 4.1 DiffReview Usage.............................: |:DiffReview|
+ 4.2 PatchReview Usage............................: |:PatchReview|
+
+=============================================================================
+
+PatchReview Introduction *patchreview-intro*
+
+The Patch Review plugin allows easy single or multipatch code or diff reviews.
+
+It opens each affected file in the patch or in a workspace diff in a diff view
+in a separate tab.
+
+VIM provides the |:diffpatch| and related commands to do single file reviews
+but can not handle patch files containing multiple patches as is common with
+software development projects. This plugin provides that missing
+functionality.
+
+It also improves on |:diffpatch|'s behaviour of creating the patched files in
+the same directory as original file which can lead to project workspace
+pollution.
+
+It does automatic diff generation for various version control systems by
+running their diff command.
+
+=============================================================================
+
+PatchReview Options *patchreview-options*
+
+ g:patchreview_patch = {string}
+ Optional path to patch binary. PatchReview tries to locate patch on
+ system path automatically. If the binary is not on system path, this
+ option tell PatchReview the full path to the binary. This option, if
+ specified, overrides the default patch binary on the path.
+
+ examples:
+ (On Windows with Cygwin) >
+ let g:patchreview_patch = 'c:\\cygwin\\bin\\patch.exe'
+<
+ (On *nix systems) >
+ let g:patchreview_patch = '/usr/bin/gpatch'
+<
+
+ g:patchreview_filterdiff = {string}
+ Optional path to filterdiff binary. PatchReview tries to locate
+ filterdiff on system path automatically. If the binary is not on system
+ path, this option tell PatchReview the full path to the binary. This
+ option, if specified, overrides the default filterdiff binary on the
+ path.
+
+ examples:
+ (On Windows with Cygwin)
+>
+ let g:patchreview_filterdiff = 'c:\\cygwin\\bin\\filterdiff.exe'
+<
+ (On *nix systems)
+>
+ let g:patchreview_filterdiff = '/usr/bin/filterdiff'
+<
+=============================================================================
+
+PatchReview Usage *patchreview-usage*
+ *:DiffReview*
+
+ :DiffReview
+
+ Perform a diff review in the current directory under version control.
+ Currently supports Mercurial (hg), Subversion (svn), CVS, Bazaar (bzr) and
+ Monotone.
+
+ *:PatchReview*
+
+ :PatchReview patchfile_path [optional_source_directory]
+
+ Perform a patch review in the current directory based on the supplied
+ patchfile_path. If optional_source_directory is specified, patchreview is
+ done on that directory. Otherwise, the current directory is assumed to be
+ the source directory.
+
+ Only supports context or unified format patches.
+
+------------------------------------------------------------------------------
+ vim: ft=help:ts=2:sts=2:sw=2:tw=78:norl:
diff --git a/contrib/vim/patchreview.vim b/contrib/vim/patchreview.vim
new file mode 100644
index 0000000..e655889
--- /dev/null
+++ b/contrib/vim/patchreview.vim
@@ -0,0 +1,868 @@
+" VIM plugin for doing single, multi-patch or diff code reviews {{{
+" Home: http://www.vim.org/scripts/script.php?script_id=1563
+
+" Version : 0.2.2 "{{{
+" Author : Manpreet Singh < junkblocker@yahoo.com >
+" Copyright : 2006-2010 by Manpreet Singh
+" License : This file is placed in the public domain.
+" No warranties express or implied. Use at your own risk.
+"
+" Changelog :
+"
+" 0.2.2 - Security fixes by removing custom tempfile creation
+" - Removed need for DiffReviewCleanup/PatchReviewCleanup
+" - Better command execution error detection and display
+" - Improved diff view and folding by ignoring modelines
+" - Improved tab labels display
+"
+" 0.2.1 - Minor temp directory autodetection logic and cleanup
+"
+" 0.2 - Removed the need for filterdiff by implemeting it in pure vim script
+" - Added DiffReview command for reverse (changed repository to
+" pristine state) reviews.
+" (PatchReview does pristine repository to patch review)
+" - DiffReview does automatic detection and generation of diffs for
+" various Source Control systems
+" - Skip load if VIM 7.0 or higher unavailable
+"
+" 0.1 - First released
+"}}}
+
+" Documentation: "{{{
+" ===========================================================================
+" This plugin allows single or multiple, patch or diff based code reviews to
+" be easily done in VIM. VIM has :diffpatch command to do single file reviews
+" but a) can not handle patch files containing multiple patches or b) do
+" automated diff generation for various version control systems. This plugin
+" attempts to provide those functionalities. It opens each changed / added or
+" removed file diff in new tabs.
+"
+" Installing:
+"
+" For a quick start, unzip patchreview.zip into your ~/.vim directory and
+" restart Vim.
+"
+" Details:
+"
+" Requirements:
+"
+" 1) VIM 7.0 or higher built with +diff option.
+"
+" 2) A gnu compatible patch command installed. This is the standard patch
+" command on Linux, Mac OS X, *BSD, Cygwin or /usr/bin/gpatch on newer
+" Solaris.
+"
+" 3) Optional (but recommended for speed)
+"
+" Install patchutils ( http://cyberelk.net/tim/patchutils/ ) for your
+" OS. For windows it is availble from Cygwin
+"
+" http://www.cygwin.com
+"
+" or GnuWin32
+"
+" http://gnuwin32.sourceforge.net/
+"
+" Install:
+"
+" 1) Extract the zip in your $HOME/.vim or $VIM/vimfiles directory and
+" restart vim. The directory location relevant to your platform can be
+" seen by running :help add-global-plugin in vim.
+"
+" 2) Restart vim.
+"
+" Configuration:
+"
+" Optionally, specify the locations to these filterdiff and patch commands
+" and location of a temporary directory to use in your .vimrc.
+"
+" let g:patchreview_patch = '/path/to/gnu/patch'
+"
+" " If you are using filterdiff
+" let g:patchreview_filterdiff = '/path/to/filterdiff'
+"
+"
+" Usage:
+"
+" Please see :help patchreview or :help diffreview for details.
+"
+""}}}
+
+" Enabled only during development
+" unlet! g:loaded_patchreview " DEBUG
+" unlet! g:patchreview_patch " DEBUG
+" unlet! g:patchreview_filterdiff " DEBUG
+" let g:patchreview_patch = 'patch' " DEBUG
+
+if v:version < 700
+ finish
+endif
+if ! has('diff')
+ call confirm('patchreview.vim plugin needs (G)VIM built with +diff support to work.')
+ finish
+endif
+
+" load only once
+if (! exists('g:patchreview_debug') && exists('g:loaded_patchreview')) || &compatible
+ finish
+endif
+let g:loaded_patchreview="0.2.2"
+
+let s:msgbufname = '-PatchReviewMessages-'
+
+function! <SID>Debug(str) "{{{
+ if exists('g:patchreview_debug')
+ Pecho 'DEBUG: ' . a:str
+ endif
+endfunction
+command! -nargs=+ -complete=expression Debug call s:Debug(<args>)
+"}}}
+
+function! <SID>PR_wipeMsgBuf() "{{{
+ let winnum = bufwinnr(s:msgbufname)
+ if winnum != -1 " If the window is already open, jump to it
+ let cur_winnr = winnr()
+ if winnr() != winnum
+ exe winnum . 'wincmd w'
+ exe 'bw'
+ exe cur_winnr . 'wincmd w'
+ endif
+ endif
+endfunction
+"}}}
+
+function! <SID>Pecho(...) "{{{
+ " Usage: Pecho(msg, [return_to_original_window_flag])
+ " default return_to_original_window_flag = 0
+ "
+ let cur_winnr = winnr()
+ let winnum = bufwinnr(s:msgbufname)
+ if winnum != -1 " If the window is already open, jump to it
+ if winnr() != winnum
+ exe winnum . 'wincmd w'
+ endif
+ else
+ let bufnum = bufnr(s:msgbufname)
+ if bufnum == -1
+ let wcmd = s:msgbufname
+ else
+ let wcmd = '+buffer' . bufnum
+ endif
+ exe 'silent! botright 5split ' . wcmd
+ endif
+ setlocal modifiable
+ setlocal buftype=nofile
+ setlocal bufhidden=delete
+ setlocal noswapfile
+ setlocal nowrap
+ setlocal nobuflisted
+ if a:0 != 0
+ silent! $put =a:1
+ endif
+ exe ':$'
+ setlocal nomodifiable
+ if a:0 > 1 && a:2
+ exe cur_winnr . 'wincmd w'
+ endif
+endfunction
+
+command! -nargs=+ -complete=expression Pecho call s:Pecho(<args>)
+"}}}
+
+function! <SID>PR_checkBinary(BinaryName) "{{{
+ " Verify that BinaryName is specified or available
+ if ! exists('g:patchreview_' . a:BinaryName)
+ if executable(a:BinaryName)
+ let g:patchreview_{a:BinaryName} = a:BinaryName
+ return 1
+ else
+ Pecho 'g:patchreview_' . a:BinaryName . ' is not defined and ' . a:BinaryName . ' command could not be found on path.'
+ Pecho 'Please define it in your .vimrc.'
+ return 0
+ endif
+ elseif ! executable(g:patchreview_{a:BinaryName})
+ Pecho 'Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a:BinaryName} . '] is not executable.'
+ return 0
+ else
+ return 1
+ endif
+endfunction
+"}}}
+
+function! <SID>ExtractDiffsNative(...) "{{{
+ " Sets g:patches = {'reason':'', 'patch':[
+ " {
+ " 'filename': filepath
+ " 'type' : '+' | '-' | '!'
+ " 'content' : patch text for this file
+ " },
+ " ...
+ " ]}
+ let g:patches = {'reason' : '', 'patch' : []}
+ " TODO : User pointers into lines list rather then use collect
+ if a:0 == 0
+ let g:patches['reason'] = "ExtractDiffsNative expects at least a patchfile argument"
+ return
+ endif
+ let patchfile = expand(a:1, ':p')
+ if a:0 > 1
+ let patch = a:2
+ endif
+ if ! filereadable(patchfile)
+ let g:patches['reason'] = "File " . patchfile . " is not readable"
+ return
+ endif
+ unlet! filterdiffcmd
+ let filterdiffcmd = '' . g:patchreview_filterdiff . ' --list -s ' . patchfile
+ let fileslist = split(system(filterdiffcmd), '[\r\n]')
+ for filewithchangetype in fileslist
+ if filewithchangetype !~ '^[!+-] '
+ Pecho '*** Skipping review generation due to unknown change for [' . filewithchangetype . ']'
+ continue
+ endif
+
+ unlet! this_patch
+ let this_patch = {}
+
+ unlet! relpath
+ let relpath = substitute(filewithchangetype, '^. ', '', '')
+
+ let this_patch['filename'] = relpath
+
+ if filewithchangetype =~ '^! '
+ let this_patch['type'] = '!'
+ elseif filewithchangetype =~ '^+ '
+ let this_patch['type'] = '+'
+ elseif filewithchangetype =~ '^- '
+ let this_patch['type'] = '-'
+ endif
+
+ unlet! filterdiffcmd
+ let filterdiffcmd = '' . g:patchreview_filterdiff . ' -i ' . relpath . ' ' . patchfile
+ let this_patch['content'] = split(system(filterdiffcmd), '[\n\r]')
+ let g:patches['patch'] += [this_patch]
+ Debug "Patch collected for " . relpath
+ endfor
+endfunction
+"}}}
+
+function! <SID>ExtractDiffsPureVim(...) "{{{
+ " Sets g:patches = {'reason':'', 'patch':[
+ " {
+ " 'filename': filepath
+ " 'type' : '+' | '-' | '!'
+ " 'content' : patch text for this file
+ " },
+ " ...
+ " ]}
+ let g:patches = {'reason' : '', 'patch' : []}
+ " TODO : User pointers into lines list rather then use collect
+ if a:0 == 0
+ let g:patches['reason'] = "ExtractDiffsPureVim expects at least a patchfile argument"
+ return
+ endif
+ let patchfile = expand(a:1, ':p')
+ if a:0 > 1
+ let patch = a:2
+ endif
+ if ! filereadable(patchfile)
+ let g:patches['reason'] = "File " . patchfile . " is not readable"
+ return
+ endif
+ call s:PR_wipeMsgBuf()
+ let collect = []
+ let linum = 0
+ let lines = readfile(patchfile)
+ let linescount = len(lines)
+ State 'START'
+ while linum < linescount
+ let line = lines[linum]
+ let linum += 1
+ if State() == 'START'
+ let mat = matchlist(line, '^--- \([^\t]\+\).*$')
+ if ! empty(mat) && mat[1] != ''
+ State 'MAYBE_UNIFIED_DIFF'
+ let p_first_file = mat[1]
+ let collect = [line]
+ Debug line . State()
+ continue
+ endif
+ let mat = matchlist(line, '^\*\*\* \([^\t]\+\).*$')
+ if ! empty(mat) && mat[1] != ''
+ State 'MAYBE_CONTEXT_DIFF'
+ let p_first_file = mat[1]
+ let collect = [line]
+ Debug line . State()
+ continue
+ endif
+ continue
+ elseif State() == 'MAYBE_CONTEXT_DIFF'
+ let mat = matchlist(line, '^--- \([^\t]\+\).*$')
+ if empty(mat) || mat[1] == ''
+ State 'START'
+ let linum -= 1
+ continue
+ Debug 'Back to square one ' . line()
+ endif
+ let p_second_file = mat[1]
+ if p_first_file == '/dev/null'
+ if p_second_file == '/dev/null'
+ let g:patches['reason'] = "Malformed diff found at line " . linum
+ return
+ endif
+ let p_type = '+'
+ let filepath = p_second_file
+ else
+ if p_second_file == '/dev/null'
+ let p_type = '-'
+ let filepath = p_first_file
+ else
+ let p_type = '!'
+ let filepath = p_first_file
+ endif
+ endif
+ State 'EXPECT_15_STARS'
+ let collect += [line]
+ Debug line . State()
+ elseif State() == 'EXPECT_15_STARS'
+ if line !~ '^*\{15}$'
+ State 'START'
+ let linum -= 1
+ Debug line . State()
+ continue
+ endif
+ State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
+ let collect += [line]
+ Debug line . State()
+ elseif State() == 'EXPECT_CONTEXT_CHUNK_HEADER_1'
+ let mat = matchlist(line, '^\*\*\* \(\d\+,\)\?\(\d\+\) \*\*\*\*$')
+ if empty(mat) || mat[1] == ''
+ State 'START'
+ let linum -= 1
+ Debug line . State()
+ continue
+ endif
+ let collect += [line]
+ State 'SKIP_CONTEXT_STUFF_1'
+ Debug line . State()
+ continue
+ elseif State() == 'SKIP_CONTEXT_STUFF_1'
+ if line !~ '^[ !+].*$'
+ let mat = matchlist(line, '^--- \(\d\+\),\(\d\+\) ----$')
+ if ! empty(mat) && mat[1] != '' && mat[2] != ''
+ let goal_count = mat[2] - mat[1] + 1
+ let c_count = 0
+ State 'READ_CONTEXT_CHUNK'
+ let collect += [line]
+ Debug line . State() . " Goal count set to " . goal_count
+ continue
+ endif
+ State 'START'
+ let linum -= 1
+ Debug line . State()
+ continue
+ endif
+ let collect += [line]
+ continue
+ elseif State() == 'READ_CONTEXT_CHUNK'
+ let c_count += 1
+ if c_count == goal_count
+ let collect += [line]
+ State 'BACKSLASH_OR_CRANGE_EOF'
+ continue
+ else " goal not met yet
+ let mat = matchlist(line, '^\([\\!+ ]\).*$')
+ if empty(mat) || mat[1] == ''
+ let linum -= 1
+ State 'START'
+ Debug line . State()
+ continue
+ endif
+ let collect += [line]
+ continue
+ endif
+ elseif State() == 'BACKSLASH_OR_CRANGE_EOF'
+ if line =~ '^\\ No newline.*$' " XXX: Can we go to another chunk from here??
+ let collect += [line]
+ let this_patch = {}
+ let this_patch['filename'] = filepath
+ let this_patch['type'] = p_type
+ let this_patch['content'] = collect
+ let g:patches['patch'] += [this_patch]
+ Debug "Patch collected for " . filepath
+ State 'START'
+ continue
+ endif
+ if line =~ '^\*\{15}$'
+ let collect += [line]
+ State 'EXPECT_CONTEXT_CHUNK_HEADER_1'
+ Debug line . State()
+ continue
+ endif
+ let this_patch = {}
+ let this_patch['filename'] = filepath
+ let this_patch['type'] = p_type
+ let this_patch['content'] = collect
+ let g:patches['patch'] += [this_patch]
+ let linum -= 1
+ State 'START'
+ Debug "Patch collected for " . filepath
+ Debug line . State()
+ continue
+ elseif State() == 'MAYBE_UNIFIED_DIFF'
+ let mat = matchlist(line, '^+++ \([^\t]\+\).*$')
+ if empty(mat) || mat[1] == ''
+ State 'START'
+ let linum -= 1
+ Debug line . State()
+ continue
+ endif
+ let p_second_file = mat[1]
+ if p_first_file == '/dev/null'
+ if p_second_file == '/dev/null'
+ let g:patches['reason'] = "Malformed diff found at line " . linum
+ return
+ endif
+ let p_type = '+'
+ let filepath = p_second_file
+ else
+ if p_second_file == '/dev/null'
+ let p_type = '-'
+ let filepath = p_first_file
+ else
+ let p_type = '!'
+ let filepath = p_first_file
+ endif
+ endif
+ State 'EXPECT_UNIFIED_RANGE_CHUNK'
+ let collect += [line]
+ Debug line . State()
+ continue
+ elseif State() == 'EXPECT_UNIFIED_RANGE_CHUNK'
+ let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
+ if ! empty(mat)
+ let old_goal_count = mat[2]
+ let new_goal_count = mat[4]
+ let o_count = 0
+ let n_count = 0
+ Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
+ State 'READ_UNIFIED_CHUNK'
+ let collect += [line]
+ Debug line . State()
+ continue
+ endif
+ State 'START'
+ Debug line . State()
+ continue
+ elseif State() == 'READ_UNIFIED_CHUNK'
+ if o_count == old_goal_count && n_count == new_goal_count
+ if line =~ '^\\.*$' " XXX: Can we go to another chunk from here??
+ let collect += [line]
+ let this_patch = {}
+ let this_patch['filename'] = filepath
+ let this_patch['type'] = p_type
+ let this_patch['content'] = collect
+ let g:patches['patch'] += [this_patch]
+ Debug "Patch collected for " . filepath
+ State 'START'
+ continue
+ endif
+ let mat = matchlist(line, '^@@ -\(\d\+,\)\?\(\d\+\) +\(\d\+,\)\?\(\d\+\) @@$')
+ if ! empty(mat)
+ let old_goal_count = mat[2]
+ let new_goal_count = mat[4]
+ let o_count = 0
+ let n_count = 0
+ Debug "Goal count set to " . old_goal_count . ', ' . new_goal_count
+ let collect += [line]
+ Debug line . State()
+ continue
+ endif
+ let this_patch = {}
+ let this_patch['filename'] = filepath
+ let this_patch['type'] = p_type
+ let this_patch['content'] = collect
+ let g:patches['patch'] += [this_patch]
+ Debug "Patch collected for " . filepath
+ let linum -= 1
+ State 'START'
+ Debug line . State()
+ continue
+ else " goal not met yet
+ let mat = matchlist(line, '^\([\\+ -]\).*$')
+ if empty(mat) || mat[1] == ''
+ let linum -= 1
+ State 'START'
+ continue
+ endif
+ let chr = mat[1]
+ if chr == '+'
+ let n_count += 1
+ endif
+ if chr == ' '
+ let o_count += 1
+ let n_count += 1
+ endif
+ if chr == '-'
+ let o_count += 1
+ endif
+ let collect += [line]
+ Debug line . State()
+ continue
+ endif
+ else
+ let g:patches['reason'] = "Internal error: Do not use the plugin anymore and if possible please send the diff or patch file you tried it with to Manpreet Singh <junkblocker@yahoo.com>"
+ return
+ endif
+ endwhile
+ "Pecho State()
+ if (State() == 'READ_CONTEXT_CHUNK' && c_count == goal_count) || (State() == 'READ_UNIFIED_CHUNK' && n_count == new_goal_count && o_count == old_goal_count)
+ let this_patch = {}
+ let this_patch['filename'] = filepath
+ let this_patch['type'] = p_type
+ let this_patch['content'] = collect
+ let g:patches['patch'] += [this_patch]
+ Debug "Patch collected for " . filepath
+ endif
+ return
+endfunction
+"}}}
+
+function! State(...) " For easy manipulation of diff extraction state "{{{
+ if a:0 != 0
+ let s:STATE = a:1
+ else
+ if ! exists('s:STATE')
+ let s:STATE = 'START'
+ endif
+ return s:STATE
+ endif
+endfunction
+com! -nargs=+ -complete=expression State call State(<args>)
+"}}}
+
+function! <SID>PatchReview(...) "{{{
+ let s:save_shortmess = &shortmess
+ let s:save_aw = &autowrite
+ let s:save_awa = &autowriteall
+ set shortmess=aW
+ call s:PR_wipeMsgBuf()
+ let s:reviewmode = 'patch'
+ call s:_GenericReview(a:000)
+ let &autowriteall = s:save_awa
+ let &autowrite = s:save_aw
+ let &shortmess = s:save_shortmess
+endfunction
+"}}}
+
+function! <SID>_GenericReview(argslist) "{{{
+ " diff mode:
+ " arg1 = patchfile
+ " arg2 = strip count
+ " patch mode:
+ " arg1 = patchfile
+ " arg2 = strip count
+ " arg3 = directory
+
+ " VIM 7+ required
+ if version < 700
+ Pecho 'This plugin needs VIM 7 or higher'
+ return
+ endif
+
+ " +diff required
+ if ! has('diff')
+ Pecho 'This plugin needs VIM built with +diff feature.'
+ return
+ endif
+
+
+ if s:reviewmode == 'diff'
+ let patch_R_option = ' -t -R '
+ elseif s:reviewmode == 'patch'
+ let patch_R_option = ''
+ else
+ Pecho 'Fatal internal error in patchreview.vim plugin'
+ return
+ endif
+
+ " Check passed arguments
+ if len(a:argslist) == 0
+ Pecho 'PatchReview command needs at least one argument specifying a patchfile path.'
+ return
+ endif
+ let StripCount = 0
+ if len(a:argslist) >= 1 && ((s:reviewmode == 'patch' && len(a:argslist) <= 3) || (s:reviewmode == 'diff' && len(a:argslist) == 2))
+ let PatchFilePath = expand(a:argslist[0], ':p')
+ if ! filereadable(PatchFilePath)
+ Pecho 'File [' . PatchFilePath . '] is not accessible.'
+ return
+ endif
+ if len(a:argslist) >= 2 && s:reviewmode == 'patch'
+ let s:SrcDirectory = expand(a:argslist[1], ':p')
+ if ! isdirectory(s:SrcDirectory)
+ Pecho '[' . s:SrcDirectory . '] is not a directory'
+ return
+ endif
+ try
+ " Command line has already escaped the path
+ exe 'cd ' . s:SrcDirectory
+ catch /^.*E344.*/
+ Pecho 'Could not change to directory [' . s:SrcDirectory . ']'
+ return
+ endtry
+ endif
+ if s:reviewmode == 'diff'
+ " passed in by default
+ let StripCount = eval(a:argslist[1])
+ elseif s:reviewmode == 'patch'
+ let StripCount = 1
+ " optional strip count
+ if len(a:argslist) == 3
+ let StripCount = eval(a:argslist[2])
+ endif
+ endif
+ else
+ if s:reviewmode == 'patch'
+ Pecho 'PatchReview command needs at most three arguments: patchfile path, optional source directory path and optional strip count.'
+ elseif s:reviewmode == 'diff'
+ Pecho 'DiffReview command accepts no arguments.'
+ endif
+ return
+ endif
+
+ " Verify that patch command and temporary directory are available or specified
+ if ! s:PR_checkBinary('patch')
+ return
+ endif
+
+ " Requirements met, now execute
+ let PatchFilePath = fnamemodify(PatchFilePath, ':p')
+ if s:reviewmode == 'patch'
+ Pecho 'Patch file : ' . PatchFilePath
+ endif
+ Pecho 'Source directory: ' . getcwd()
+ Pecho '------------------'
+ if s:PR_checkBinary('filterdiff')
+ Debug "Using filterdiff"
+ call s:ExtractDiffsNative(PatchFilePath)
+ else
+ Debug "Using own diff extraction (slower)"
+ call s:ExtractDiffsPureVim(PatchFilePath)
+ endif
+ for patch in g:patches['patch']
+ if patch.type !~ '^[!+-]$'
+ Pecho '*** Skipping review generation due to unknown change [' . patch.type . ']', 1
+ continue
+ endif
+ unlet! relpath
+ let relpath = patch.filename
+ " XXX: svn diff and hg diff produce different kind of outputs, one requires
+ " XXX: stripping but the other doesn't. We need to take care of that
+ let stripmore = StripCount
+ let StrippedRelativeFilePath = relpath
+ while stripmore > 0
+ " strip one
+ let StrippedRelativeFilePath = substitute(StrippedRelativeFilePath, '^[^\\\/]\+[^\\\/]*[\\\/]' , '' , '')
+ let stripmore -= 1
+ endwhile
+ if patch.type == '!'
+ if s:reviewmode == 'patch'
+ let msgtype = 'Patch modifies file: '
+ elseif s:reviewmode == 'diff'
+ let msgtype = 'File has changes: '
+ endif
+ elseif patch.type == '+'
+ if s:reviewmode == 'patch'
+ let msgtype = 'Patch adds file : '
+ elseif s:reviewmode == 'diff'
+ let msgtype = 'New file : '
+ endif
+ elseif patch.type == '-'
+ if s:reviewmode == 'patch'
+ let msgtype = 'Patch removes file : '
+ elseif s:reviewmode == 'diff'
+ let msgtype = 'Removed file : '
+ endif
+ endif
+ let bufnum = bufnr(relpath)
+ if buflisted(bufnum) && getbufvar(bufnum, '&mod')
+ Pecho 'Old buffer for file [' . relpath . '] exists in modified state. Skipping review.', 1
+ continue
+ endif
+ let tmpname = tempname()
+
+ " write patch for patch.filename into tmpname
+ call writefile(patch.content, tmpname)
+ if patch.type == '+' && s:reviewmode == 'patch'
+ let inputfile = ''
+ let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
+ elseif patch.type == '+' && s:reviewmode == 'diff'
+ let inputfile = ''
+ unlet! patchcmd
+ else
+ let inputfile = expand(StrippedRelativeFilePath, ':p')
+ let patchcmd = '!' . g:patchreview_patch . patch_R_option . ' -o "' . tmpname . '.file" "' . inputfile . '" < "' . tmpname . '"'
+ endif
+ if exists('patchcmd')
+ let v:errmsg = ''
+ Debug patchcmd
+ silent exe patchcmd
+ if v:errmsg != '' || v:shell_error
+ Pecho 'ERROR: Could not execute patch command.'
+ Pecho 'ERROR: ' . patchcmd
+ Pecho 'ERROR: ' . v:errmsg
+ Pecho 'ERROR: Diff skipped.'
+ continue
+ endif
+ endif
+ call delete(tmpname)
+ let s:origtabpagenr = tabpagenr()
+ silent! exe 'tabedit ' . StrippedRelativeFilePath
+ if exists('patchcmd')
+ " modelines in loaded files mess with diff comparision
+ let s:keep_modeline=&modeline
+ let &modeline=0
+ silent! exe 'vert diffsplit ' . tmpname . '.file'
+ setlocal buftype=nofile
+ setlocal noswapfile
+ setlocal syntax=none
+ setlocal bufhidden=delete
+ setlocal nobuflisted
+ setlocal modifiable
+ setlocal nowrap
+ " Remove buffer name
+ silent! 0f
+ " Switch to original to get a nice tab title
+ silent! wincmd p
+ let &modeline=s:keep_modeline
+ else
+ silent! exe 'vnew'
+ endif
+ if filereadable(tmpname . '.file.rej')
+ silent! exe 'topleft 5split ' . tmpname . '.file.rej'
+ Pecho msgtype . '*** REJECTED *** ' . relpath, 1
+ else
+ Pecho msgtype . ' ' . relpath, 1
+ endif
+ silent! exe 'tabn ' . s:origtabpagenr
+ endfor
+ Pecho '-----'
+ Pecho 'Done.'
+
+endfunction
+"}}}
+
+function! <SID>DiffReview(...) "{{{
+ let s:save_shortmess = &shortmess
+ set shortmess=aW
+ call s:PR_wipeMsgBuf()
+
+ let vcsdict = {
+ \'Mercurial' : {'dir' : '.hg', 'binary' : 'hg', 'diffargs' : 'diff' , 'strip' : 1},
+ \'Bazaar-NG' : {'dir' : '.bzr', 'binary' : 'bzr', 'diffargs' : 'diff' , 'strip' : 0},
+ \'monotone' : {'dir' : '_MTN', 'binary' : 'mtn', 'diffargs' : 'diff --unified', 'strip' : 0},
+ \'Subversion' : {'dir' : '.svn', 'binary' : 'svn', 'diffargs' : 'diff' , 'strip' : 0},
+ \'cvs' : {'dir' : 'CVS', 'binary' : 'cvs', 'diffargs' : '-q diff -u' , 'strip' : 0},
+ \}
+
+ unlet! s:theDiffCmd
+ unlet! l:vcs
+ if ! exists('g:patchreview_diffcmd')
+ for key in keys(vcsdict)
+ if isdirectory(vcsdict[key]['dir'])
+ if ! s:PR_checkBinary(vcsdict[key]['binary'])
+ Pecho 'Current directory looks like a ' . vcsdict[key] . ' repository but ' . vcsdist[key]['binary'] . ' command was not found on path.'
+ let &shortmess = s:save_shortmess
+ return
+ else
+ let s:theDiffCmd = vcsdict[key]['binary'] . ' ' . vcsdict[key]['diffargs']
+ let strip = vcsdict[key]['strip']
+
+ Pecho 'Using [' . s:theDiffCmd . '] to generate diffs for this ' . key . ' review.'
+ let &shortmess = s:save_shortmess
+ let l:vcs = vcsdict[key]['binary']
+ break
+ endif
+ else
+ continue
+ endif
+ endfor
+ else
+ let s:theDiffCmd = g:patchreview_diffcmd
+ let strip = 0
+ endif
+ if ! exists('s:theDiffCmd')
+ Pecho 'Please define g:patchreview_diffcmd and make sure you are in a VCS controlled top directory.'
+ let &shortmess = s:save_shortmess
+ return
+ endif
+
+ let outfile = tempname()
+ let cmd = s:theDiffCmd . ' > "' . outfile . '"'
+ let v:errmsg = ''
+ let cout = system(cmd)
+ if v:errmsg == '' && exists('l:vcs') && l:vcs == 'cvs' && v:shell_error == 1
+ " Ignoring CVS non-error
+ elseif v:errmsg != '' || v:shell_error
+ Pecho v:errmsg
+ Pecho 'Could not execute [' . s:theDiffCmd . ']'
+ Pecho 'Error code: ' . v:shell_error
+ Pecho cout
+ Pecho 'Diff review aborted.'
+ let &shortmess = s:save_shortmess
+ return
+ endif
+ let s:reviewmode = 'diff'
+ call s:_GenericReview([outfile, strip])
+ let &shortmess = s:save_shortmess
+endfunction
+"}}}
+
+" End user commands "{{{
+"============================================================================
+" :PatchReview
+command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>)
+
+" :DiffReview
+command! -nargs=0 DiffReview call s:DiffReview()
+"}}}
+
+" Development "{{{
+if exists('g:patchreview_debug')
+ " Tests
+ function! <SID>PRExtractTestNative(...)
+ "let patchfiles = glob(expand(a:1) . '/?*')
+ "for fname in split(patchfiles)
+ call s:PR_wipeMsgBuf()
+ let fname = a:1
+ call s:ExtractDiffsNative(fname)
+ for patch in g:patches['patch']
+ for line in patch.content
+ Pecho line
+ endfor
+ endfor
+ "endfor
+ endfunction
+
+ function! <SID>PRExtractTestVim(...)
+ "let patchfiles = glob(expand(a:1) . '/?*')
+ "for fname in split(patchfiles)
+ call s:PR_wipeMsgBuf()
+ let fname = a:1
+ call s:ExtractDiffsPureVim(fname)
+ for patch in g:patches['patch']
+ for line in patch.content
+ Pecho line
+ endfor
+ endfor
+ "endfor
+ endfunction
+
+ command! -nargs=+ -complete=file PRTestVim call s:PRExtractTestVim(<f-args>)
+ command! -nargs=+ -complete=file PRTestNative call s:PRExtractTestNative(<f-args>)
+endif
+"}}}
+
+" modeline
+" vim: set et fdl=0 fdm=marker fenc=latin ff=unix ft=vim sw=2 sts=0 ts=2 textwidth=78 nowrap :
diff --git a/contrib/win32/ReadMe.html b/contrib/win32/ReadMe.html
new file mode 100644
index 0000000..d5d30cc
--- /dev/null
+++ b/contrib/win32/ReadMe.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <title>Mercurial for Windows</title>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
+ <style type="text/css">
+ <!--
+ html {
+ font-family: sans-serif;
+ margin: 1em 2em;
+ }
+
+ p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ }
+
+ pre {
+ margin: 0.25em 0em;
+ padding: 0.5em;
+ background-color: #EEE;
+ border: thin solid #CCC;
+ }
+
+ .indented {
+ padding-left: 10pt;
+ }
+ -->
+ </style>
+ </head>
+
+ <body>
+ <h1>Mercurial for Windows</h1>
+
+ <p>Welcome to Mercurial for Windows!</p>
+
+ <p>
+ Mercurial is a command-line application. You must run it from
+ the Windows command prompt (or if you're hard core, a <a
+ href="http://www.mingw.org/">MinGW</a> shell).
+ </p>
+
+ <p class="indented">
+ <i>Note: the standard <a href="http://www.mingw.org/">MinGW</a>
+ msys startup script uses rxvt which has problems setting up
+ standard input and output. Running bash directly works
+ correctly.</i>
+ </p>
+
+ <p>
+ For documentation, please visit the <a
+ href="http://mercurial.selenic.com/">Mercurial web site</a>.
+ You can also download a free book, <a
+ href="http://hgbook.red-bean.com/">Mercurial: The Definitive
+ Guide</a>.
+ </p>
+
+ <p>
+ By default, Mercurial installs to <tt>C:\Program
+ Files\Mercurial</tt>. The Mercurial command is called
+ <tt>hg.exe</tt>.
+ </p>
+
+ <h1>Testing Mercurial after you've installed it</h1>
+
+ <p>
+ The easiest way to check that Mercurial is installed properly is
+ to just type the following at the command prompt:
+ </p>
+
+ <pre>
+hg
+</pre>
+
+ <p>
+ This command should print a useful help message. If it does,
+ other Mercurial commands should work fine for you.
+ </p>
+
+ <h1>Configuration notes</h1>
+ <h4>Default editor</h4>
+ <p>
+ The default editor for commit messages is 'notepad'. You can set
+ the <tt>EDITOR</tt> (or <tt>HGEDITOR</tt>) environment variable
+ to specify your preference or set it in <tt>mercurial.ini</tt>:
+ </p>
+ <pre>
+[ui]
+editor = whatever
+</pre>
+
+ <h4>Configuring a Merge program</h4>
+ <p>
+ It should be emphasized that Mercurial by itself doesn't attempt
+ to do a Merge at the file level, neither does it make any
+ attempt to Resolve the conflicts.
+ </p>
+
+ <p>
+ By default, Mercurial will use the merge program defined by the
+ <tt>HGMERGE</tt> environment variable, or uses the one defined
+ in the <tt>mercurial.ini</tt> file. (see <a
+ href="http://mercurial.selenic.com/wiki/MergeProgram">MergeProgram</a>
+ on the Mercurial Wiki for more information)
+ </p>
+
+ <h1>Reporting problems</h1>
+
+ <p>
+ Before you report any problems, please consult the <a
+ href="http://mercurial.selenic.com/">Mercurial web site</a>
+ and see if your question is already in our list of <a
+ href="http://mercurial.selenic.com/wiki/FAQ">Frequently
+ Answered Questions</a> (the "FAQ").
+ </p>
+
+ <p>
+ If you cannot find an answer to your question, please feel free
+ to send mail to the Mercurial mailing list, at <a
+ href="mailto:mercurial@selenic.com">mercurial@selenic.com</a>.
+ <b>Remember</b>, the more useful information you include in your
+ report, the easier it will be for us to help you!
+ </p>
+
+ <p>
+ If you are IRC-savvy, that's usually the fastest way to get
+ help. Go to <tt>#mercurial</tt> on <tt>irc.freenode.net</tt>.
+ </p>
+
+ <h1>Author and copyright information</h1>
+
+ <p>
+ Mercurial was written by <a href="http://www.selenic.com">Matt
+ Mackall</a>, and is maintained by Matt and a team of volunteers.
+ </p>
+
+ <p>
+ The Windows installer was written by <a
+ href="http://www.serpentine.com/blog">Bryan O'Sullivan</a>.
+ </p>
+
+ <p>
+ Mercurial is Copyright 2005-2012 Matt Mackall and others. See
+ the <tt>Contributors.txt</tt> file for a list of contributors.
+ </p>
+
+ <p>
+ Mercurial is free software; you can redistribute it and/or
+ modify it under the terms of the <a
+ href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt">GNU
+ General Public License version 2</a> or any later version.
+ </p>
+
+ <p>
+ Mercurial is distributed in the hope that it will be useful, but
+ <b>without any warranty</b>; without even the implied warranty
+ of <b>merchantability</b> or <b>fitness for a particular
+ purpose</b>. See the GNU General Public License for more
+ details.
+ </p>
+ </body>
+</html>
diff --git a/contrib/win32/buildlocal.bat b/contrib/win32/buildlocal.bat
new file mode 100644
index 0000000..b161d3e
--- /dev/null
+++ b/contrib/win32/buildlocal.bat
@@ -0,0 +1,9 @@
+@echo off
+rem Double-click this file to (re)build Mercurial for Windows in place.
+rem Useful for testing and development.
+cd ..\..
+del /Q mercurial\*.pyd
+del /Q mercurial\*.pyc
+rmdir /Q /S mercurial\locale
+python setup.py build_py -c -d . build_ext -i build_mo
+pause
diff --git a/contrib/win32/hg.bat b/contrib/win32/hg.bat
new file mode 100644
index 0000000..511a4e7
--- /dev/null
+++ b/contrib/win32/hg.bat
@@ -0,0 +1,12 @@
+@echo off
+rem Windows Driver script for Mercurial
+
+setlocal
+set HG=%~f0
+
+rem Use a full path to Python (relative to this script) as the standard Python
+rem install does not put python.exe on the PATH...
+rem %~dp0 is the directory of this script
+
+"%~dp0..\python" "%~dp0hg" %*
+endlocal
diff --git a/contrib/win32/hgwebdir_wsgi.py b/contrib/win32/hgwebdir_wsgi.py
new file mode 100644
index 0000000..efa866e
--- /dev/null
+++ b/contrib/win32/hgwebdir_wsgi.py
@@ -0,0 +1,95 @@
+# An example WSGI script for IIS/isapi-wsgi to export multiple hgweb repos
+# Copyright 2010 Sune Foldager <cryo@cyanite.org>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+#
+# Requirements:
+# - Python 2.6
+# - PyWin32 build 214 or newer
+# - Mercurial installed from source (python setup.py install)
+# - IIS 7
+#
+# Earlier versions will in general work as well, but the PyWin32 version is
+# necessary for win32traceutil to work correctly.
+#
+#
+# Installation and use:
+#
+# - Download the isapi-wsgi source and run python setup.py install:
+# http://code.google.com/p/isapi-wsgi/
+#
+# - Run this script (i.e. python hgwebdir_wsgi.py) to get a shim dll. The
+# shim is identical for all scripts, so you can just copy and rename one
+# from an earlier run, if you wish.
+#
+# - Setup an IIS application where your hgwebdir is to be served from.
+# On 64-bit systems, make sure it's assigned a 32-bit app pool.
+#
+# - In the application, setup a wildcard script handler mapping of type
+# IpsapiModule with the shim dll as its executable. This file MUST reside
+# in the same directory as the shim. Remove all other handlers, if you wish.
+#
+# - Make sure the ISAPI and CGI restrictions (configured globally on the
+# web server) includes the shim dll, to allow it to run.
+#
+# - Adjust the configuration variables below to match your needs.
+#
+
+# Configuration file location
+hgweb_config = r'c:\src\iis\hg\hgweb.config'
+
+# Global settings for IIS path translation
+path_strip = 0 # Strip this many path elements off (when using url rewrite)
+path_prefix = 1 # This many path elements are prefixes (depends on the
+ # virtual path of the IIS application).
+
+import sys
+
+# Adjust python path if this is not a system-wide install
+#sys.path.insert(0, r'c:\path\to\python\lib')
+
+# Enable tracing. Run 'python -m win32traceutil' to debug
+if getattr(sys, 'isapidllhandle', None) is not None:
+ import win32traceutil
+
+# To serve pages in local charset instead of UTF-8, remove the two lines below
+import os
+os.environ['HGENCODING'] = 'UTF-8'
+
+
+import isapi_wsgi
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb.hgwebdir_mod import hgwebdir
+
+# Example tweak: Replace isapi_wsgi's handler to provide better error message
+# Other stuff could also be done here, like logging errors etc.
+class WsgiHandler(isapi_wsgi.IsapiWsgiHandler):
+ error_status = '500 Internal Server Error' # less silly error message
+
+isapi_wsgi.IsapiWsgiHandler = WsgiHandler
+
+# Only create the hgwebdir instance once
+application = hgwebdir(hgweb_config)
+
+def handler(environ, start_response):
+
+ # Translate IIS's weird URLs
+ url = environ['SCRIPT_NAME'] + environ['PATH_INFO']
+ paths = url[1:].split('/')[path_strip:]
+ script_name = '/' + '/'.join(paths[:path_prefix])
+ path_info = '/'.join(paths[path_prefix:])
+ if path_info:
+ path_info = '/' + path_info
+ environ['SCRIPT_NAME'] = script_name
+ environ['PATH_INFO'] = path_info
+
+ return application(environ, start_response)
+
+def __ExtensionFactory__():
+ return isapi_wsgi.ISAPISimpleHandler(handler)
+
+if __name__=='__main__':
+ from isapi.install import *
+ params = ISAPIParameters()
+ HandleCommandLine(params)
diff --git a/contrib/win32/mercurial.ico b/contrib/win32/mercurial.ico
new file mode 100644
index 0000000..046808d
--- /dev/null
+++ b/contrib/win32/mercurial.ico
Binary files differ
diff --git a/contrib/win32/mercurial.ini b/contrib/win32/mercurial.ini
new file mode 100644
index 0000000..b8eac56
--- /dev/null
+++ b/contrib/win32/mercurial.ini
@@ -0,0 +1,97 @@
+; System-wide Mercurial config file.
+;
+; !!! Do Not Edit This File !!!
+;
+; This file will be replaced by the installer on every upgrade.
+; Editing this file can cause strange side effects on Vista.
+;
+; http://bitbucket.org/tortoisehg/stable/issue/135
+;
+; To change settings you see in this file, override (or enable) them in
+; your user Mercurial.ini file, where USERNAME is your Windows user name:
+;
+; XP or older - C:\Documents and Settings\USERNAME\Mercurial.ini
+; Vista or later - C:\Users\USERNAME\Mercurial.ini
+
+
+[ui]
+; editor used to enter commit logs, etc. Most text editors will work.
+editor = notepad
+; show changed files and be a bit more verbose if True
+; verbose = True
+
+; username data to appear in commits
+; it usually takes the form: Joe User <joe.user@host.com>
+; username = Joe User <j.user@example.com>
+
+; In order to push/pull over ssh you must specify an ssh tool
+;ssh = "C:\Progra~1\TortoiseSVN\bin\TortoisePlink.exe" -ssh -2
+;ssh = C:\cygwin\bin\ssh
+
+;
+; For more information about mercurial extensions, start here
+; http://www.selenic.com/mercurial/wiki/index.cgi/UsingExtensions
+;
+; Extensions shipped with Mercurial
+;
+[extensions]
+;acl =
+;bugzilla =
+;children =
+;churn =
+;color =
+;convert =
+;eol =
+;extdiff =
+;fetch =
+;gpg =
+;graphlog =
+;hgcia =
+;hgk =
+;highlight =
+;histedit =
+;interhg =
+;largefiles =
+;keyword =
+;mq =
+;notify =
+;pager =
+;patchbomb =
+;progress =
+;purge =
+;rebase =
+;record =
+;relink =
+;schemes =
+;share =
+;transplant =
+;win32mbcs =
+;zeroconf =
+
+;
+; Define external diff commands
+;
+[extdiff]
+;cmd.bc3diff = C:\Program Files\Beyond Compare 3\BCompare.exe
+;cmd.vdiff = C:\Progra~1\TortoiseSVN\bin\TortoiseMerge.exe
+;cmd.vimdiff = gvim.exe
+;opts.vimdiff = -f "+next" "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
+
+
+[hgk]
+; Replace the following with your path to hgk, uncomment it and
+; install ActiveTcl (or another win32 port like tclkit)
+; path="C:\Program Files\Mercurial\Contrib\hgk.tcl"
+; vdiff=vdiff
+
+
+;
+; The git extended diff format can represent binary files, file
+; permission changes, and rename information that the normal patch format
+; cannot describe. However it is also not compatible with tools which
+; expect normal patches. so enable git patches at your own risk.
+;
+[diff]
+;git = false
+;nodates = false
+
diff --git a/contrib/win32/mercurial.iss b/contrib/win32/mercurial.iss
new file mode 100644
index 0000000..855218a
--- /dev/null
+++ b/contrib/win32/mercurial.iss
@@ -0,0 +1,148 @@
+; Script generated by the Inno Setup Script Wizard.
+; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
+
+#ifndef VERSION
+#define FileHandle
+#define FileLine
+#define VERSION = "unknown"
+#if FileHandle = FileOpen(SourcePath + "\..\..\mercurial\__version__.py")
+ #expr FileLine = FileRead(FileHandle)
+ #expr FileLine = FileRead(FileHandle)
+ #define VERSION = Copy(FileLine, Pos('"', FileLine)+1, Len(FileLine)-Pos('"', FileLine)-1)
+#endif
+#if FileHandle
+ #expr FileClose(FileHandle)
+#endif
+#pragma message "Detected Version: " + VERSION
+#endif
+
+#ifndef ARCH
+#define ARCH = "x86"
+#endif
+
+[Setup]
+AppCopyright=Copyright 2005-2010 Matt Mackall and others
+AppName=Mercurial
+#if ARCH == "x64"
+AppVerName=Mercurial {#VERSION} (64-bit)
+OutputBaseFilename=Mercurial-{#VERSION}-x64
+ArchitecturesAllowed=x64
+ArchitecturesInstallIn64BitMode=x64
+#else
+AppVerName=Mercurial {#VERSION}
+OutputBaseFilename=Mercurial-{#VERSION}
+#endif
+InfoAfterFile=contrib/win32/postinstall.txt
+LicenseFile=COPYING
+ShowLanguageDialog=yes
+AppPublisher=Matt Mackall and others
+AppPublisherURL=http://mercurial.selenic.com/
+AppSupportURL=http://mercurial.selenic.com/
+AppUpdatesURL=http://mercurial.selenic.com/
+AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
+AppContact=mercurial@selenic.com
+DefaultDirName={pf}\Mercurial
+SourceDir=..\..
+VersionInfoDescription=Mercurial distributed SCM (version {#VERSION})
+VersionInfoCopyright=Copyright 2005-2010 Matt Mackall and others
+VersionInfoCompany=Matt Mackall and others
+InternalCompressLevel=max
+SolidCompression=true
+SetupIconFile=contrib\win32\mercurial.ico
+AllowNoIcons=true
+DefaultGroupName=Mercurial
+PrivilegesRequired=none
+
+[Files]
+Source: contrib\mercurial.el; DestDir: {app}/Contrib
+Source: contrib\vim\*.*; DestDir: {app}/Contrib/Vim
+Source: contrib\zsh_completion; DestDir: {app}/Contrib
+Source: contrib\bash_completion; DestDir: {app}/Contrib
+Source: contrib\tcsh_completion; DestDir: {app}/Contrib
+Source: contrib\tcsh_completion_build.sh; DestDir: {app}/Contrib
+Source: contrib\hgk; DestDir: {app}/Contrib; DestName: hgk.tcl
+Source: contrib\xml.rnc; DestDir: {app}/Contrib
+Source: contrib\shrink-revlog.py; DestDir: {app}/Contrib
+Source: contrib\mercurial.el; DestDir: {app}/Contrib
+Source: contrib\mq.el; DestDir: {app}/Contrib
+Source: contrib\hgweb.fcgi; DestDir: {app}/Contrib
+Source: contrib\hgweb.wsgi; DestDir: {app}/Contrib
+Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
+Source: contrib\mergetools.hgrc; DestDir: {tmp};
+Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Check: CheckFile; AfterInstall: ConcatenateFiles;
+Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
+Source: dist\hg.exe; DestDir: {app}; AfterInstall: Touch('{app}\hg.exe.local')
+#if ARCH == "x64"
+Source: dist\*.dll; Destdir: {app}
+Source: dist\*.pyd; Destdir: {app}
+#else
+Source: dist\python*.dll; Destdir: {app}; Flags: skipifsourcedoesntexist
+Source: dist\msvc*.dll; DestDir: {app}; Flags: skipifsourcedoesntexist
+Source: dist\w9xpopen.exe; DestDir: {app}
+#endif
+Source: dist\Microsoft.VC*.CRT.manifest; DestDir: {app}; Flags: skipifsourcedoesntexist
+Source: dist\library.zip; DestDir: {app}
+Source: dist\add_path.exe; DestDir: {app}
+Source: dist\cacert.pem; Destdir: {app}
+Source: doc\*.html; DestDir: {app}\Docs
+Source: doc\style.css; DestDir: {app}\Docs
+Source: mercurial\help\*.txt; DestDir: {app}\help
+Source: mercurial\locale\*.*; DestDir: {app}\locale; Flags: recursesubdirs createallsubdirs skipifsourcedoesntexist
+Source: mercurial\templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
+Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
+Source: COPYING; DestDir: {app}; DestName: Copying.txt
+
+[INI]
+Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: http://mercurial.selenic.com/
+Filename: {app}\Mercurial.ini; Section: web; Key: cacerts; String: {app}\cacert.pem
+
+[UninstallDelete]
+Type: files; Name: {app}\Mercurial.url
+Type: files; Name: {app}\Contrib\shrink-revlog.pyc
+
+[Icons]
+Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
+Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.html
+Name: {group}\Mercurial Configuration Files; Filename: {app}\Docs\hgrc.5.html
+Name: {group}\Mercurial Ignore Files; Filename: {app}\Docs\hgignore.5.html
+Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
+
+[Run]
+Filename: "{app}\add_path.exe"; Parameters: "{app}"; Flags: postinstall; Description: "Add the installation path to the search path"
+
+[UninstallRun]
+Filename: "{app}\add_path.exe"; Parameters: "/del {app}"
+
+[UninstallDelete]
+Type: files; Name: "{app}\hg.exe.local"
+
+[Code]
+var
+ WriteFile: Boolean;
+ CheckDone: Boolean;
+
+function CheckFile(): Boolean;
+begin
+ if not CheckDone then begin
+ WriteFile := True;
+ if FileExists(ExpandConstant(CurrentFileName)) then begin
+ WriteFile := MsgBox('' + ExpandConstant(CurrentFileName) + '' #13#13 'The file already exists.' #13#13 'Would you like Setup to overwrite it?', mbConfirmation, MB_YESNO) = idYes;
+ end;
+ CheckDone := True;
+ end;
+ Result := WriteFile;
+end;
+
+procedure ConcatenateFiles();
+var
+ MergeConfigs: TArrayOfString;
+begin
+ if LoadStringsFromFile(ExpandConstant('{tmp}\mergetools.hgrc'),MergeConfigs) then begin
+ SaveStringsToFile(ExpandConstant(CurrentFileName),MergeConfigs,True);
+ end;
+end;
+
+procedure Touch(fn: String);
+begin
+ SaveStringToFile(ExpandConstant(fn), '', False);
+end;
diff --git a/contrib/win32/postinstall.txt b/contrib/win32/postinstall.txt
new file mode 100644
index 0000000..786dc48
--- /dev/null
+++ b/contrib/win32/postinstall.txt
@@ -0,0 +1,9 @@
+Welcome to Mercurial for Windows!
+---------------------------------
+
+For configuration and usage directions, please read the ReadMe.html
+file that comes with this package.
+
+Also check the release notes at:
+
+ http://mercurial.selenic.com/wiki/WhatsNew
diff --git a/contrib/win32/win32-build.txt b/contrib/win32/win32-build.txt
new file mode 100644
index 0000000..e0f2143
--- /dev/null
+++ b/contrib/win32/win32-build.txt
@@ -0,0 +1,133 @@
+The standalone Windows installer for Mercurial is built in a somewhat
+jury-rigged fashion.
+
+It has the following prerequisites. Ensure to take the packages
+matching the mercurial version you want to build (32-bit or 64-bit).
+
+ Python 2.6 for Windows
+ http://www.python.org/download/releases/
+
+ A compiler:
+ either MinGW
+ http://www.mingw.org/
+ or Microsoft Visual C++ 2008 SP1 Express Edition
+ http://www.microsoft.com/express/Downloads/Download-2008.aspx
+
+ Python for Windows Extensions
+ http://sourceforge.net/projects/pywin32/
+
+ mfc71.dll (just download, don't install; not needed for Python 2.6)
+ http://starship.python.net/crew/mhammond/win32/
+
+ Visual C++ 2008 redistributable package (needed for >= Python 2.6 or if you compile with MSVC)
+ for 32-bit:
+ http://www.microsoft.com/downloads/details.aspx?FamilyID=9b2da534-3e03-4391-8a4d-074b9f2bc1bf
+ for 64-bit:
+ http://www.microsoft.com/downloads/details.aspx?familyid=bd2a6171-e2d6-4230-b809-9a8d7548c1b6
+
+ The py2exe distutils extension
+ http://sourceforge.net/projects/py2exe/
+
+ GnuWin32 gettext utility (if you want to build translations)
+ http://gnuwin32.sourceforge.net/packages/gettext.htm
+
+ Inno Setup
+ http://www.jrsoftware.org/isdl.php#qsp
+
+ Get and install ispack-5.3.10.exe or later (includes Inno Setup Processor),
+ which is necessary to package Mercurial.
+
+ ISTool - optional
+ http://www.istool.org/default.aspx/
+
+ add_path (you need only add_path.exe in the zip file)
+ http://www.barisione.org/apps.html#add_path
+
+ Docutils
+ http://docutils.sourceforge.net/
+
+ CA Certs file
+ http://curl.haxx.se/ca/cacert.pem
+
+And, of course, Mercurial itself.
+
+Once you have all this installed and built, clone a copy of the
+Mercurial repository you want to package, and name the repo
+C:\hg\hg-release.
+
+In a shell, build a standalone copy of the hg.exe program.
+
+Building instructions for MinGW:
+ python setup.py build -c mingw32
+ python setup.py py2exe -b 2
+Note: the previously suggested combined command of "python setup.py build -c
+mingw32 py2exe -b 2" doesn't work correctly anymore as it doesn't include the
+extensions in the mercurial subdirectory.
+If you want to create a file named setup.cfg with the contents:
+[build]
+compiler=mingw32
+you can skip the first build step.
+
+Building instructions with MSVC 2008 Express Edition:
+ for 32-bit:
+ "C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86
+ python setup.py py2exe -b 2
+ for 64-bit:
+ "C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86_amd64
+ python setup.py py2exe -b 3
+
+Copy add_path.exe and cacert.pem files into the dist directory that just got created.
+
+If you are using Python up to version 2.5.4, copy mfc71.dll into the dist
+directory that just got created.
+
+If you are using Python 2.6 or later, or if you are using MSVC 2008 to compile
+mercurial, you must include the C runtime libraries in the installer. To do so,
+install the Visual C++ 2008 redistributable package. Then in your windows\winsxs
+folder, locate the folder containing the dlls version 9.0.21022.8.
+For x86, it should be named like x86_Microsoft.VC90.CRT_(...)_9.0.21022.8(...).
+For x64, it should be named like amd64_Microsoft.VC90.CRT_(...)_9.0.21022.8(...).
+Copy the files named msvcm90.dll, msvcp90.dll and msvcr90.dll into the dist
+directory.
+Then in the windows\winsxs\manifests folder, locate the corresponding manifest
+file (x86_Microsoft.VC90.CRT_(...)_9.0.21022.8(...).manifest for x86,
+amd64_Microsoft.VC90.CRT_(...)_9.0.21022.8(...).manifest for x64), copy it in the
+dist directory and rename it to Microsoft.VC90.CRT.manifest.
+
+Before building the installer, you have to build Mercurial HTML documentation
+(or fix mercurial.iss to not reference the doc directory):
+
+ cd doc
+ mingw32-make html
+ cd ..
+
+If you use ISTool, you open the C:\hg\hg-release\contrib\win32\mercurial.iss
+file and type Ctrl-F9 to compile the installer file.
+
+Otherwise you run the Inno Setup compiler. Assuming it's in the path
+you should execute:
+
+ iscc contrib\win32\mercurial.iss /dVERSION=foo
+
+Where 'foo' is the version number you would like to see in the
+'Add/Remove Applications' tool. The installer will be placed into
+a directory named Output/ at the root of your repository.
+If the /dVERSION=foo parameter is not given in the command line, the
+installer will retrieve the version information from the __version__.py file.
+
+If you want to build an installer for a 64-bit mercurial, add /dARCH=x64 to
+your command line:
+ iscc contrib\win32\mercurial.iss /dARCH=x64
+
+To automate the steps above you may want to create a batchfile based on the
+following (MinGW build chain):
+
+ echo [build] > setup.cfg
+ echo compiler=mingw32 >> setup.cfg
+ python setup.py py2exe -b 2
+ cd doc
+ mingw32-make html
+ cd ..
+ iscc contrib\win32\mercurial.iss /dVERSION=snapshot
+
+and run it from the root of the hg repository (c:\hg\hg-release).
diff --git a/contrib/wix/COPYING.rtf b/contrib/wix/COPYING.rtf
new file mode 100644
index 0000000..3174226
--- /dev/null
+++ b/contrib/wix/COPYING.rtf
Binary files differ
diff --git a/contrib/wix/README.txt b/contrib/wix/README.txt
new file mode 100644
index 0000000..8d9ec37
--- /dev/null
+++ b/contrib/wix/README.txt
@@ -0,0 +1,31 @@
+WiX installer source files
+==========================
+
+The files in this folder are used by the thg-winbuild [1] package
+building architecture to create a Mercurial MSI installer. These files
+are versioned within the Mercurial source tree because the WXS files
+must kept up to date with distribution changes within their branch. In
+other words, the default branch WXS files are expected to diverge from
+the stable branch WXS files. Storing them within the same repository is
+the only sane way to keep the source tree and the installer in sync.
+
+The MSI installer builder uses only the mercurial.ini file from the
+contrib/win32 folder, the contents of which have been historically used
+to create an InnoSetup based installer. The rest of the files there are
+ignored.
+
+The MSI packages built by thg-winbuild require elevated (admin)
+privileges to be installed due to the installation of MSVC CRT libraries
+under the C:\WINDOWS\WinSxS folder. Thus the InnoSetup installers may
+still be useful to some users.
+
+To build your own MSI packages, clone the thg-winbuild [1] repository
+and follow the README.txt [2] instructions closely. There are fewer
+prerequisites for a WiX [3] installer than an InnoSetup installer, but
+they are more specific.
+
+Direct questions or comments to Steve Borho <steve@borho.org>
+
+[1] http://bitbucket.org/tortoisehg/thg-winbuild
+[2] http://bitbucket.org/tortoisehg/thg-winbuild/src/tip/README.txt
+[3] http://wix.sourceforge.net/
diff --git a/contrib/wix/contrib.wxs b/contrib/wix/contrib.wxs
new file mode 100644
index 0000000..9dde6c4
--- /dev/null
+++ b/contrib/wix/contrib.wxs
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+ <?include guids.wxi ?>
+ <?include defines.wxi ?>
+
+ <Fragment>
+ <ComponentGroup Id="contribFolder">
+ <ComponentRef Id="contrib" />
+ <ComponentRef Id="contrib.vim" />
+ </ComponentGroup>
+ </Fragment>
+
+ <Fragment>
+ <DirectoryRef Id="INSTALLDIR">
+ <Directory Id="contribdir" Name="contrib" FileSource="$(var.SourceDir)">
+ <Component Id="contrib" Guid="$(var.contrib.guid)" Win64='$(var.IsX64)'>
+ <File Name="bash_completion" KeyPath="yes" />
+ <File Name="hgk" />
+ <File Name="hgweb.fcgi" />
+ <File Name="hgweb.wsgi" />
+ <File Name="logo-droplets.svg" />
+ <File Name="mercurial.el" />
+ <File Name="sample.hgrc" />
+ <File Name="tcsh_completion" />
+ <File Name="tcsh_completion_build.sh" />
+ <File Name="xml.rnc" />
+ <File Name="zsh_completion" />
+ </Component>
+ <Directory Id="vimdir" Name="vim">
+ <Component Id="contrib.vim" Guid="$(var.contrib.vim.guid)" Win64='$(var.IsX64)'>
+ <File Name="hg-menu.vim" KeyPath="yes" />
+ <File Name="HGAnnotate.vim" />
+ <File Name="hgcommand.vim" />
+ <File Name="patchreview.txt" />
+ <File Name="patchreview.vim" />
+ <File Name="hgtest.vim" />
+ </Component>
+ </Directory>
+ </Directory>
+ </DirectoryRef>
+ </Fragment>
+
+</Wix>
diff --git a/contrib/wix/defines.wxi b/contrib/wix/defines.wxi
new file mode 100644
index 0000000..b0a7860
--- /dev/null
+++ b/contrib/wix/defines.wxi
@@ -0,0 +1,9 @@
+<Include>
+
+ <?if $(var.Platform) = "x64" ?>
+ <?define IsX64 = yes ?>
+ <?else?>
+ <?define IsX64 = no ?>
+ <?endif?>
+
+</Include>
diff --git a/contrib/wix/dist.wxs b/contrib/wix/dist.wxs
new file mode 100644
index 0000000..6aa9ef7
--- /dev/null
+++ b/contrib/wix/dist.wxs
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+ <?include guids.wxi ?>
+ <?include defines.wxi ?>
+
+ <Fragment>
+ <DirectoryRef Id="INSTALLDIR" FileSource="$(var.SourceDir)">
+ <Component Id="distOutput" Guid="$(var.dist.guid)" Win64='$(var.IsX64)'>
+ <File Name="library.zip" KeyPath="yes" />
+ <File Name="mercurial.base85.pyd" />
+ <File Name="mercurial.bdiff.pyd" />
+ <File Name="mercurial.diffhelpers.pyd" />
+ <File Name="mercurial.mpatch.pyd" />
+ <File Name="mercurial.osutil.pyd" />
+ <File Name="mercurial.parsers.pyd" />
+ <File Name="pyexpat.pyd" />
+ <File Name="python26.dll" />
+ <File Name="bz2.pyd" />
+ <File Name="select.pyd" />
+ <File Name="unicodedata.pyd" />
+ <File Name="_ctypes.pyd" />
+ <File Name="_elementtree.pyd" />
+ <File Name="_hashlib.pyd" />
+ <File Name="_socket.pyd" />
+ <File Name="_ssl.pyd" />
+ </Component>
+ </DirectoryRef>
+ </Fragment>
+
+</Wix>
diff --git a/contrib/wix/doc.wxs b/contrib/wix/doc.wxs
new file mode 100644
index 0000000..c8bcdd0
--- /dev/null
+++ b/contrib/wix/doc.wxs
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+ <?include guids.wxi ?>
+ <?include defines.wxi ?>
+
+ <Fragment>
+ <ComponentGroup Id="docFolder">
+ <ComponentRef Id="doc.hg.1.html" />
+ <ComponentRef Id="doc.hgignore.5.html" />
+ <ComponentRef Id="doc.hgrc.5.html" />
+ <ComponentRef Id="doc.style.css" />
+ </ComponentGroup>
+ </Fragment>
+
+ <Fragment>
+ <DirectoryRef Id="INSTALLDIR">
+ <Directory Id="docdir" Name="doc" FileSource="$(var.SourceDir)">
+ <Component Id="doc.hg.1.html" Guid="$(var.doc.hg.1.html.guid)" Win64='$(var.IsX64)'>
+ <File Name="hg.1.html" KeyPath="yes">
+ <Shortcut Id="hg1StartMenu" Directory="ProgramMenuDir"
+ Name="Mercurial Command Reference"
+ Icon="hgIcon.ico" IconIndex="0" Advertise="yes"
+ />
+ </File>
+ </Component>
+ <Component Id="doc.hgignore.5.html" Guid="$(var.doc.hgignore.5.html.guid)" Win64='$(var.IsX64)'>
+ <File Name="hgignore.5.html" KeyPath="yes">
+ <Shortcut Id="hgignore5StartMenu" Directory="ProgramMenuDir"
+ Name="Mercurial Ignore Files"
+ Icon="hgIcon.ico" IconIndex="0" Advertise="yes"
+ />
+ </File>
+ </Component>
+ <Component Id="doc.hgrc.5.html" Guid="$(var.doc.hgrc.5.html)" Win64='$(var.IsX64)'>
+ <File Name="hgrc.5.html" KeyPath="yes">
+ <Shortcut Id="hgrc5StartMenu" Directory="ProgramMenuDir"
+ Name="Mercurial Configuration Files"
+ Icon="hgIcon.ico" IconIndex="0" Advertise="yes"
+ />
+ </File>
+ </Component>
+ <Component Id="doc.style.css" Guid="$(var.doc.style.css)" Win64='$(var.IsX64)'>
+ <File Name="style.css" KeyPath="yes" />
+ </Component>
+ </Directory>
+ </DirectoryRef>
+ </Fragment>
+
+</Wix>
diff --git a/contrib/wix/guids.wxi b/contrib/wix/guids.wxi
new file mode 100644
index 0000000..d4360a4
--- /dev/null
+++ b/contrib/wix/guids.wxi
@@ -0,0 +1,51 @@
+<Include>
+ <!-- These are component GUIDs used for Mercurial installers.
+ YOU MUST CHANGE ALL GUIDs below when copying this file
+ and replace 'Mercurial' in this notice with the name of
+ your project. Component GUIDs have global namespace! -->
+
+ <!-- contrib.wxs -->
+ <?define contrib.guid = {F17D27B7-4A6B-4cd2-AE72-FED3CFAA585E} ?>
+ <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
+
+ <!-- dist.wxs -->
+ <?define dist.guid = {C3B634A4-1B05-4A40-94A9-38EE853CF693} ?>
+
+ <!-- doc.wxs -->
+ <?define doc.hg.1.html.guid = {AAAA3FDA-EDC5-4220-B59D-D342722358A2} ?>
+ <?define doc.hgignore.5.html.guid = {AA9118C4-F3A0-4429-A5F4-5A1906B2D67F} ?>
+ <?define doc.hgrc.5.html = {E0CEA1EB-FA01-408c-844B-EE5965165BAE} ?>
+ <?define doc.style.css = {172F8262-98E0-4711-BD39-4DAE0D77EF05} ?>
+
+ <!-- help.wxs -->
+ <?define helpFolder.guid = {9FA957DB-6DFE-44f2-AD03-293B2791CF17} ?>
+
+ <!-- i18n.wxs -->
+ <?define i18nFolder.guid = {1BF8026D-CF7C-4174-AEE6-D6B7BF119248} ?>
+
+ <!-- templates.wxs -->
+ <?define templates.root.guid = {8DF97574-33E9-412F-8414-65B48BB18783} ?>
+ <?define templates.atom.guid = {AB5D2908-BC95-44BE-9D79-069EF43D93E2} ?>
+ <?define templates.coal.guid = {B63CCAAB-4EAF-43b4-901E-4BD13F5B78FC} ?>
+ <?define templates.gitweb.guid = {6A33D168-F84E-45AA-912C-23CAC2D66BCA} ?>
+ <?define templates.monoblue.guid = {D27AA750-9394-4DAC-84FC-A546CE8F347A} ?>
+ <?define templates.paper.guid = {D2591E56-709E-49F9-8A5F-1359E1CCD7E0} ?>
+ <?define templates.raw.guid = {04DE03A2-FBFD-4c5f-8DEA-5436DDF4689D} ?>
+ <?define templates.rss.guid = {36069748-1E2A-472B-A212-506CB656A9C1} ?>
+ <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?>
+ <?define templates.static.guid = {B27D7311-050A-4A96-9971-B674A0EA21D0} ?>
+
+ <!-- mercurial.wxs -->
+ <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
+
+ <?define ComponentMainExecutableGUID = {D102B8FA-059B-4ACC-9FA3-8C78C3B58EEF} ?>
+
+ <?define ReadMe.guid = {56A8E372-991D-4DCA-B91D-93D775974CF5} ?>
+ <?define COPYING.guid = {B7801DBA-1C49-4BF4-91AD-33C65F5C7895} ?>
+ <?define mercurial.rc.guid = {1D5FAEEE-7E6E-43B1-9F7F-802714316B15} ?>
+ <?define mergetools.rc.guid = {E8A1DC29-FF40-4B5F-BD12-80B9F7BF0CCD} ?>
+ <?define paths.rc.guid = {F9ADF21D-5F0B-4934-8CD9-14BE63664721} ?>
+ <?define cacert.pem.guid = {EC1B2630-FE21-46E6-915B-A6545AF703D4} ?>
+ <?define ProgramMenuDir.guid = {D5A63320-1238-489B-B68B-CF053E9577CA} ?>
+
+</Include>
diff --git a/contrib/wix/help.wxs b/contrib/wix/help.wxs
new file mode 100644
index 0000000..601ecdc
--- /dev/null
+++ b/contrib/wix/help.wxs
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+ <?include guids.wxi ?>
+ <?include defines.wxi ?>
+
+ <Fragment>
+ <DirectoryRef Id="INSTALLDIR">
+ <Directory Id="helpdir" Name="help" FileSource="$(var.SourceDir)">
+ <Component Id="helpFolder" Guid="$(var.helpFolder.guid)" Win64='$(var.IsX64)'>
+ <File Name="config.txt" KeyPath="yes" />
+ <File Name="dates.txt" />
+ <File Name="diffs.txt" />
+ <File Name="environment.txt" />
+ <File Name="extensions.txt" />
+ <File Name="filesets.txt" />
+ <File Name="glossary.txt" />
+ <File Name="hgignore.txt" />
+ <File Name="hgweb.txt" />
+ <File Name="merge-tools.txt" />
+ <File Name="multirevs.txt" />
+ <File Name="patterns.txt" />
+ <File Name="phases.txt" />
+ <File Name="revisions.txt" />
+ <File Name="revsets.txt" />
+ <File Name="subrepos.txt" />
+ <File Name="templates.txt" />
+ <File Name="urls.txt" />
+ </Component>
+ </Directory>
+ </DirectoryRef>
+ </Fragment>
+
+</Wix>
diff --git a/contrib/wix/hg.cmd b/contrib/wix/hg.cmd
new file mode 100644
index 0000000..7e4c897
--- /dev/null
+++ b/contrib/wix/hg.cmd
@@ -0,0 +1,3 @@
+@echo off
+rem launch hg.exe from parent folder
+"%~dp0\..\hg.exe" %*
diff --git a/contrib/wix/i18n.wxs b/contrib/wix/i18n.wxs
new file mode 100644
index 0000000..4c97ca4
--- /dev/null
+++ b/contrib/wix/i18n.wxs
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+ <?include guids.wxi ?>
+ <?include defines.wxi ?>
+
+ <?define hg_po_langs =
+ da;de;el;fr;it;ja;pt_BR;ro;ru;sv;zh_CN;zh_TW
+ ?>
+
+ <Fragment>
+ <DirectoryRef Id="INSTALLDIR">
+ <Directory Id="i18ndir" Name="i18n" FileSource="$(var.SourceDir)">
+ <Component Id="i18nFolder" Guid="$(var.i18nFolder.guid)" Win64='$(var.IsX64)'>
+ <File Name="hggettext" KeyPath="yes" />
+ <?foreach LANG in $(var.hg_po_langs) ?>
+ <File Id="hg.$(var.LANG).po"
+ Name="$(var.LANG).po"
+ />
+ <?endforeach?>
+ </Component>
+ </Directory>
+ </DirectoryRef>
+ </Fragment>
+
+</Wix>
diff --git a/contrib/wix/locale.wxs b/contrib/wix/locale.wxs
new file mode 100644
index 0000000..52b66d4
--- /dev/null
+++ b/contrib/wix/locale.wxs
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+ <?include defines.wxi ?>
+
+ <?define hglocales =
+ da;de;el;fr;it;ja;pt_BR;sv;zh_CN;zh_TW
+ ?>
+
+ <Fragment>
+ <ComponentGroup Id="localeFolder">
+ <?foreach LOC in $(var.hglocales) ?>
+ <ComponentRef Id="hg.locale.$(var.LOC)"/>
+ <?endforeach?>
+ </ComponentGroup>
+ </Fragment>
+
+ <Fragment>
+ <DirectoryRef Id="INSTALLDIR">
+ <Directory Id="localedir" Name="locale" FileSource="$(var.SourceDir)">
+ <?foreach LOC in $(var.hglocales) ?>
+ <Directory Id="hg.locale.$(var.LOC)" Name="$(var.LOC)">
+ <Directory Id="hg.locale.$(var.LOC).LC_MESSAGES" Name="LC_MESSAGES">
+ <Component Id="hg.locale.$(var.LOC)" Guid="*" Win64='$(var.IsX64)'>
+ <File Id="hg.mo.$(var.LOC)" Name="hg.mo" KeyPath="yes" />
+ </Component>
+ </Directory>
+ </Directory>
+ <?endforeach?>
+ </Directory>
+ </DirectoryRef>
+ </Fragment>
+
+</Wix>
diff --git a/contrib/wix/mercurial.wxs b/contrib/wix/mercurial.wxs
new file mode 100644
index 0000000..fdd47e0
--- /dev/null
+++ b/contrib/wix/mercurial.wxs
@@ -0,0 +1,172 @@
+<?xml version='1.0' encoding='windows-1252'?>
+<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
+
+ <!-- Copyright 2010 Steve Borho <steve@borho.org>
+
+ This software may be used and distributed according to the terms of the
+ GNU General Public License version 2 or any later version. -->
+
+ <?include guids.wxi ?>
+ <?include defines.wxi ?>
+
+ <?if $(var.Platform) = "x64" ?>
+ <?define PFolder = ProgramFiles64Folder ?>
+ <?else?>
+ <?define PFolder = ProgramFilesFolder ?>
+ <?endif?>
+
+ <Product Id='*'
+ Name='Mercurial $(var.Version) ($(var.Platform))'
+ UpgradeCode='$(var.ProductUpgradeCode)'
+ Language='1033' Codepage='1252' Version='$(var.Version)'
+ Manufacturer='Matt Mackall and others'>
+
+ <Package Id='*'
+ Keywords='Installer'
+ Description="Mercurial distributed SCM (version $(var.Version))"
+ Comments='$(var.Comments)'
+ Platform='$(var.Platform)'
+ Manufacturer='Matt Mackall and others'
+ InstallerVersion='300' Languages='1033' Compressed='yes' SummaryCodepage='1252' />
+
+ <Media Id='1' Cabinet='mercurial.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'
+ CompressionLevel='high' />
+ <Property Id='DiskPrompt' Value="Mercurial $(var.Version) Installation [1]" />
+
+ <Condition Message='Mercurial MSI installers require Windows XP or higher'>
+ VersionNT >= 501
+ </Condition>
+
+ <Property Id="INSTALLDIR">
+ <ComponentSearch Id='SearchForMainExecutableComponent'
+ Guid='$(var.ComponentMainExecutableGUID)' />
+ </Property>
+
+ <!--Property Id='ARPCOMMENTS'>any comments</Property-->
+ <Property Id='ARPCONTACT'>mercurial@selenic.com</Property>
+ <Property Id='ARPHELPLINK'>http://mercurial.selenic.com/wiki/</Property>
+ <Property Id='ARPURLINFOABOUT'>http://mercurial.selenic.com/about/</Property>
+ <Property Id='ARPURLUPDATEINFO'>http://mercurial.selenic.com/downloads/</Property>
+ <Property Id='ARPHELPTELEPHONE'>http://mercurial.selenic.com/wiki/Support</Property>
+ <Property Id='ARPPRODUCTICON'>hgIcon.ico</Property>
+
+ <Property Id='INSTALLEDMERCURIALPRODUCTS' Secure='yes'></Property>
+ <Property Id='REINSTALLMODE'>amus</Property>
+
+ <!--Auto-accept the license page-->
+ <Property Id='LicenseAccepted'>1</Property>
+
+ <Directory Id='TARGETDIR' Name='SourceDir'>
+ <Directory Id='$(var.PFolder)' Name='PFiles'>
+ <Directory Id='INSTALLDIR' Name='Mercurial'>
+ <Component Id='MainExecutable' Guid='$(var.ComponentMainExecutableGUID)' Win64='$(var.IsX64)'>
+ <File Id='hgEXE' Name='hg.exe' Source='dist\hg.exe' KeyPath='yes' />
+ <Environment Id="Environment" Name="PATH" Part="last" System="yes"
+ Permanent="no" Value="[INSTALLDIR]" Action="set" />
+ </Component>
+ <Component Id='ReadMe' Guid='$(var.ReadMe.guid)' Win64='$(var.IsX64)'>
+ <File Id='ReadMe' Name='ReadMe.html' Source='contrib\win32\ReadMe.html'
+ KeyPath='yes'/>
+ </Component>
+ <Component Id='COPYING' Guid='$(var.COPYING.guid)' Win64='$(var.IsX64)'>
+ <File Id='COPYING' Name='COPYING.rtf' Source='contrib\wix\COPYING.rtf'
+ KeyPath='yes'/>
+ </Component>
+
+ <Directory Id='HGRCD' Name='hgrc.d'>
+ <Component Id='mercurial.rc' Guid='$(var.mercurial.rc.guid)' Win64='$(var.IsX64)'>
+ <File Id='mercurial.rc' Name='Mercurial.rc' Source='contrib\win32\mercurial.ini'
+ ReadOnly='yes' KeyPath='yes'/>
+ </Component>
+ <Component Id='mergetools.rc' Guid='$(var.mergetools.rc.guid)' Win64='$(var.IsX64)'>
+ <File Id='mergetools.rc' Name='MergeTools.rc' Source='contrib\mergetools.hgrc'
+ ReadOnly='yes' KeyPath='yes'/>
+ </Component>
+ <Component Id='paths.rc' Guid='$(var.paths.rc.guid)' Win64='$(var.IsX64)'>
+ <CreateFolder/>
+ <IniFile Id="ini0" Action="createLine" Directory="HGRCD" Name="Paths.rc"
+ Section="web" Key="cacerts" Value="[INSTALLDIR]hgrc.d\cacert.pem" />
+ </Component>
+ <Component Id='cacert.pem' Guid='$(var.cacert.pem.guid)' Win64='$(var.IsX64)'>
+ <File Id='cacert.pem' Name='cacert.pem' Source='..\misc\cacert.pem'
+ ReadOnly='yes' KeyPath='yes'/>
+ </Component>
+ </Directory>
+
+ </Directory>
+ </Directory>
+
+ <Directory Id="ProgramMenuFolder" Name="Programs">
+ <Directory Id="ProgramMenuDir" Name="Mercurial $(var.Version)">
+ <Component Id="ProgramMenuDir" Guid="$(var.ProgramMenuDir.guid)" Win64='$(var.IsX64)'>
+ <RemoveFolder Id='ProgramMenuDir' On='uninstall' />
+ <RegistryValue Root='HKCU' Key='Software\Mercurial\InstallDir' Type='string'
+ Value='[INSTALLDIR]' KeyPath='yes' />
+ <Shortcut Id='UrlShortcut' Directory='ProgramMenuDir' Name='Mercurial Web Site'
+ Target='[ARPHELPLINK]' Icon="hgIcon.ico" IconIndex='0' />
+ </Component>
+ </Directory>
+ </Directory>
+
+ <?if $(var.Platform) = "x86" ?>
+ <Merge Id='VCRuntime' DiskId='1' Language='1033'
+ SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x86_msm.msm' />
+ <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033'
+ SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x86_msm.msm' />
+ <?else?>
+ <Merge Id='VCRuntime' DiskId='1' Language='1033'
+ SourceFile='$(var.VCRedistSrcDir)\microsoft.vcxx.crt.x64_msm.msm' />
+ <Merge Id='VCRuntimePolicy' DiskId='1' Language='1033'
+ SourceFile='$(var.VCRedistSrcDir)\policy.x.xx.microsoft.vcxx.crt.x64_msm.msm' />
+ <?endif?>
+ </Directory>
+
+ <Feature Id='Complete' Title='Mercurial' Description='The complete package'
+ Display='expand' Level='1' ConfigurableDirectory='INSTALLDIR' >
+ <Feature Id='MainProgram' Title='Program' Description='Mercurial command line app'
+ Level='1' Absent='disallow' >
+ <ComponentRef Id='MainExecutable' />
+ <ComponentRef Id='distOutput' />
+ <ComponentRef Id='ProgramMenuDir' />
+ <ComponentRef Id='ReadMe' />
+ <ComponentRef Id='COPYING' />
+ <ComponentRef Id='mercurial.rc' />
+ <ComponentRef Id='mergetools.rc' />
+ <ComponentRef Id='paths.rc' />
+ <ComponentRef Id='cacert.pem' />
+ <ComponentRef Id='helpFolder' />
+ <ComponentGroupRef Id='templatesFolder' />
+ <MergeRef Id='VCRuntime' />
+ <MergeRef Id='VCRuntimePolicy' />
+ </Feature>
+ <Feature Id='Locales' Title='Translations' Description='Translations' Level='1'>
+ <ComponentGroupRef Id='localeFolder' />
+ <ComponentRef Id='i18nFolder' />
+ </Feature>
+ <Feature Id='Documentation' Title='Documentation' Description='HTML man pages' Level='1'>
+ <ComponentGroupRef Id='docFolder' />
+ </Feature>
+ <Feature Id='Misc' Title='Miscellaneous' Description='Contributed scripts' Level='1'>
+ <ComponentGroupRef Id='contribFolder' />
+ </Feature>
+ </Feature>
+
+ <UIRef Id="WixUI_FeatureTree" />
+ <UIRef Id="WixUI_ErrorProgressText" />
+
+ <WixVariable Id="WixUILicenseRtf" Value="contrib\wix\COPYING.rtf" />
+
+ <Icon Id="hgIcon.ico" SourceFile="contrib/win32/mercurial.ico" />
+
+ <Upgrade Id='$(var.ProductUpgradeCode)'>
+ <UpgradeVersion
+ IncludeMinimum='yes' Minimum='0.0.0' IncludeMaximum='no' OnlyDetect='no'
+ Property='INSTALLEDMERCURIALPRODUCTS' />
+ </Upgrade>
+
+ <InstallExecuteSequence>
+ <RemoveExistingProducts After='InstallInitialize'/>
+ </InstallExecuteSequence>
+
+ </Product>
+</Wix>
diff --git a/contrib/wix/templates.wxs b/contrib/wix/templates.wxs
new file mode 100644
index 0000000..ac2eacc
--- /dev/null
+++ b/contrib/wix/templates.wxs
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+
+ <?include guids.wxi ?>
+ <?include defines.wxi ?>
+
+ <Fragment>
+ <ComponentGroup Id="templatesFolder">
+
+ <ComponentRef Id="templates.root" />
+
+ <ComponentRef Id="templates.atom" />
+ <ComponentRef Id="templates.coal" />
+ <ComponentRef Id="templates.gitweb" />
+ <ComponentRef Id="templates.monoblue" />
+ <ComponentRef Id="templates.paper" />
+ <ComponentRef Id="templates.raw" />
+ <ComponentRef Id="templates.rss" />
+ <ComponentRef Id="templates.spartan" />
+ <ComponentRef Id="templates.static" />
+
+ </ComponentGroup>
+ </Fragment>
+
+ <Fragment>
+ <DirectoryRef Id="INSTALLDIR">
+
+ <Directory Id="templatesdir" Name="templates" FileSource="$(var.SourceDir)">
+
+ <Component Id="templates.root" Guid="$(var.templates.root.guid)" Win64='$(var.IsX64)'>
+ <File Name="map-cmdline.changelog" KeyPath="yes" />
+ <File Name="map-cmdline.compact" />
+ <File Name="map-cmdline.default" />
+ <File Name="map-cmdline.bisect" />
+ <File Name="map-cmdline.xml" />
+ <File Name="template-vars.txt" />
+ </Component>
+
+ <Directory Id="templates.atomdir" Name="atom">
+ <Component Id="templates.atom" Guid="$(var.templates.atom.guid)" Win64='$(var.IsX64)'>
+ <File Id="atom.changelog.tmpl" Name="changelog.tmpl" KeyPath="yes" />
+ <File Id="atom.changelogentry.tmpl" Name="changelogentry.tmpl" />
+ <File Id="atom.error.tmpl" Name="error.tmpl" />
+ <File Id="atom.filelog.tmpl" Name="filelog.tmpl" />
+ <File Id="atom.header.tmpl" Name="header.tmpl" />
+ <File Id="atom.map" Name="map" />
+ <File Id="atom.tagentry.tmpl" Name="tagentry.tmpl" />
+ <File Id="atom.tags.tmpl" Name="tags.tmpl" />
+ <File Id="atom.bookmarks.tmpl" Name="bookmarks.tmpl" />
+ <File Id="atom.bookmarkentry.tmpl" Name="bookmarkentry.tmpl" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.coaldir" Name="coal">
+ <Component Id="templates.coal" Guid="$(var.templates.coal.guid)" Win64='$(var.IsX64)'>
+ <File Id="coal.header.tmpl" Name="header.tmpl" KeyPath="yes" />
+ <File Id="coal.map" Name="map" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.gitwebdir" Name="gitweb">
+ <Component Id="templates.gitweb" Guid="$(var.templates.gitweb.guid)" Win64='$(var.IsX64)'>
+ <File Id="gitweb.branches.tmpl" Name="branches.tmpl" KeyPath="yes" />
+ <File Id="gitweb.bookmarks.tmpl" Name="bookmarks.tmpl" />
+ <File Id="gitweb.changelog.tmpl" Name="changelog.tmpl" />
+ <File Id="gitweb.changelogentry.tmpl" Name="changelogentry.tmpl" />
+ <File Id="gitweb.changeset.tmpl" Name="changeset.tmpl" />
+ <File Id="gitweb.error.tmpl" Name="error.tmpl" />
+ <File Id="gitweb.fileannotate.tmpl" Name="fileannotate.tmpl" />
+ <File Id="gitweb.filediff.tmpl" Name="filediff.tmpl" />
+ <File Id="gitweb.filelog.tmpl" Name="filelog.tmpl" />
+ <File Id="gitweb.filerevision.tmpl" Name="filerevision.tmpl" />
+ <File Id="gitweb.footer.tmpl" Name="footer.tmpl" />
+ <File Id="gitweb.graph.tmpl" Name="graph.tmpl" />
+ <File Id="gitweb.header.tmpl" Name="header.tmpl" />
+ <File Id="gitweb.index.tmpl" Name="index.tmpl" />
+ <File Id="gitweb.manifest.tmpl" Name="manifest.tmpl" />
+ <File Id="gitweb.map" Name="map" />
+ <File Id="gitweb.notfound.tmpl" Name="notfound.tmpl" />
+ <File Id="gitweb.search.tmpl" Name="search.tmpl" />
+ <File Id="gitweb.shortlog.tmpl" Name="shortlog.tmpl" />
+ <File Id="gitweb.summary.tmpl" Name="summary.tmpl" />
+ <File Id="gitweb.tags.tmpl" Name="tags.tmpl" />
+ <File Id="gitweb.help.tmpl" Name="help.tmpl" />
+ <File Id="gitweb.helptopics.tmpl" Name="helptopics.tmpl" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.monobluedir" Name="monoblue">
+ <Component Id="templates.monoblue" Guid="$(var.templates.monoblue.guid)" Win64='$(var.IsX64)'>
+ <File Id="monoblue.branches.tmpl" Name="branches.tmpl" KeyPath="yes" />
+ <File Id="monoblue.bookmarks.tmpl" Name="bookmarks.tmpl" />
+ <File Id="monoblue.changelog.tmpl" Name="changelog.tmpl" />
+ <File Id="monoblue.changelogentry.tmpl" Name="changelogentry.tmpl" />
+ <File Id="monoblue.changeset.tmpl" Name="changeset.tmpl" />
+ <File Id="monoblue.error.tmpl" Name="error.tmpl" />
+ <File Id="monoblue.fileannotate.tmpl" Name="fileannotate.tmpl" />
+ <File Id="monoblue.filediff.tmpl" Name="filediff.tmpl" />
+ <File Id="monoblue.filelog.tmpl" Name="filelog.tmpl" />
+ <File Id="monoblue.filerevision.tmpl" Name="filerevision.tmpl" />
+ <File Id="monoblue.footer.tmpl" Name="footer.tmpl" />
+ <File Id="monoblue.graph.tmpl" Name="graph.tmpl" />
+ <File Id="monoblue.header.tmpl" Name="header.tmpl" />
+ <File Id="monoblue.index.tmpl" Name="index.tmpl" />
+ <File Id="monoblue.manifest.tmpl" Name="manifest.tmpl" />
+ <File Id="monoblue.map" Name="map" />
+ <File Id="monoblue.notfound.tmpl" Name="notfound.tmpl" />
+ <File Id="monoblue.search.tmpl" Name="search.tmpl" />
+ <File Id="monoblue.shortlog.tmpl" Name="shortlog.tmpl" />
+ <File Id="monoblue.summary.tmpl" Name="summary.tmpl" />
+ <File Id="monoblue.tags.tmpl" Name="tags.tmpl" />
+ <File Id="monoblue.help.tmpl" Name="help.tmpl" />
+ <File Id="monoblue.helptopics.tmpl" Name="helptopics.tmpl" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.paperdir" Name="paper">
+ <Component Id="templates.paper" Guid="$(var.templates.paper.guid)" Win64='$(var.IsX64)'>
+ <File Id="paper.branches.tmpl" Name="branches.tmpl" KeyPath="yes" />
+ <File Id="paper.bookmarks.tmpl" Name="bookmarks.tmpl" />
+ <File Id="paper.changeset.tmpl" Name="changeset.tmpl" />
+ <File Id="paper.diffstat.tmpl" Name="diffstat.tmpl" />
+ <File Id="paper.error.tmpl" Name="error.tmpl" />
+ <File Id="paper.fileannotate.tmpl" Name="fileannotate.tmpl" />
+ <File Id="paper.filediff.tmpl" Name="filediff.tmpl" />
+ <File Id="paper.filelog.tmpl" Name="filelog.tmpl" />
+ <File Id="paper.filelogentry.tmpl" Name="filelogentry.tmpl" />
+ <File Id="paper.filerevision.tmpl" Name="filerevision.tmpl" />
+ <File Id="paper.footer.tmpl" Name="footer.tmpl" />
+ <File Id="paper.graph.tmpl" Name="graph.tmpl" />
+ <File Id="paper.header.tmpl" Name="header.tmpl" />
+ <File Id="paper.index.tmpl" Name="index.tmpl" />
+ <File Id="paper.manifest.tmpl" Name="manifest.tmpl" />
+ <File Id="paper.map" Name="map" />
+ <File Id="paper.notfound.tmpl" Name="notfound.tmpl" />
+ <File Id="paper.search.tmpl" Name="search.tmpl" />
+ <File Id="paper.shortlog.tmpl" Name="shortlog.tmpl" />
+ <File Id="paper.shortlogentry.tmpl" Name="shortlogentry.tmpl" />
+ <File Id="paper.tags.tmpl" Name="tags.tmpl" />
+ <File Id="paper.help.tmpl" Name="help.tmpl" />
+ <File Id="paper.helptopics.tmpl" Name="helptopics.tmpl" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.rawdir" Name="raw">
+ <Component Id="templates.raw" Guid="$(var.templates.raw.guid)" Win64='$(var.IsX64)'>
+ <File Id="raw.changeset.tmpl" Name="changeset.tmpl" KeyPath="yes" />
+ <File Id="raw.error.tmpl" Name="error.tmpl" />
+ <File Id="raw.fileannotate.tmpl" Name="fileannotate.tmpl" />
+ <File Id="raw.filediff.tmpl" Name="filediff.tmpl" />
+ <File Id="raw.index.tmpl" Name="index.tmpl" />
+ <File Id="raw.manifest.tmpl" Name="manifest.tmpl" />
+ <File Id="raw.map" Name="map" />
+ <File Id="raw.notfound.tmpl" Name="notfound.tmpl" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.rssdir" Name="rss">
+ <Component Id="templates.rss" Guid="$(var.templates.rss.guid)" Win64='$(var.IsX64)'>
+ <File Id="rss.changelog.tmpl" Name="changelog.tmpl" KeyPath="yes" />
+ <File Id="rss.changelogentry.tmpl" Name="changelogentry.tmpl" />
+ <File Id="rss.error.tmpl" Name="error.tmpl" />
+ <File Id="rss.filelog.tmpl" Name="filelog.tmpl" />
+ <File Id="rss.filelogentry.tmpl" Name="filelogentry.tmpl" />
+ <File Id="rss.header.tmpl" Name="header.tmpl" />
+ <File Id="rss.map" Name="map" />
+ <File Id="rss.tagentry.tmpl" Name="tagentry.tmpl" />
+ <File Id="rss.tags.tmpl" Name="tags.tmpl" />
+ <File Id="rss.bookmarks.tmpl" Name="bookmarks.tmpl" />
+ <File Id="rss.bookmarkentry.tmpl" Name="bookmarkentry.tmpl" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.spartandir" Name="spartan">
+ <Component Id="templates.spartan" Guid="$(var.templates.spartan.guid)" Win64='$(var.IsX64)'>
+ <File Id="spartan.branches.tmpl" Name="branches.tmpl" KeyPath="yes" />
+ <File Id="spartan.changelog.tmpl" Name="changelog.tmpl" />
+ <File Id="spartan.changelogentry.tmpl" Name="changelogentry.tmpl" />
+ <File Id="spartan.changeset.tmpl" Name="changeset.tmpl" />
+ <File Id="spartan.error.tmpl" Name="error.tmpl" />
+ <File Id="spartan.fileannotate.tmpl" Name="fileannotate.tmpl" />
+ <File Id="spartan.filediff.tmpl" Name="filediff.tmpl" />
+ <File Id="spartan.filelog.tmpl" Name="filelog.tmpl" />
+ <File Id="spartan.filelogentry.tmpl" Name="filelogentry.tmpl" />
+ <File Id="spartan.filerevision.tmpl" Name="filerevision.tmpl" />
+ <File Id="spartan.footer.tmpl" Name="footer.tmpl" />
+ <File Id="spartan.graph.tmpl" Name="graph.tmpl" />
+ <File Id="spartan.header.tmpl" Name="header.tmpl" />
+ <File Id="spartan.index.tmpl" Name="index.tmpl" />
+ <File Id="spartan.manifest.tmpl" Name="manifest.tmpl" />
+ <File Id="spartan.map" Name="map" />
+ <File Id="spartan.notfound.tmpl" Name="notfound.tmpl" />
+ <File Id="spartan.search.tmpl" Name="search.tmpl" />
+ <File Id="spartan.shortlog.tmpl" Name="shortlog.tmpl" />
+ <File Id="spartan.shortlogentry.tmpl" Name="shortlogentry.tmpl" />
+ <File Id="spartan.tags.tmpl" Name="tags.tmpl" />
+ </Component>
+ </Directory>
+
+ <Directory Id="templates.staticdir" Name="static">
+ <Component Id="templates.static" Guid="$(var.templates.static.guid)" Win64='$(var.IsX64)'>
+ <File Id="static.background.png" Name="background.png" KeyPath="yes" />
+ <File Id="static.coal.file.png" Name="coal-file.png" />
+ <File Id="static.coal.folder.png" Name="coal-folder.png" />
+ <File Id="static.excanvas.js" Name="excanvas.js" />
+ <File Id="static.mercurial.js" Name="mercurial.js" />
+ <File Id="static.hgicon.png" Name="hgicon.png" />
+ <File Id="static.hglogo.png" Name="hglogo.png" />
+ <File Id="static.style.coal.css" Name="style-coal.css" />
+ <File Id="static.style.gitweb.css" Name="style-gitweb.css" />
+ <File Id="static.style.monoblue.css" Name="style-monoblue.css" />
+ <File Id="static.style.paper.css" Name="style-paper.css" />
+ <File Id="static.style.css" Name="style.css" />
+ </Component>
+ </Directory>
+
+ </Directory>
+
+ </DirectoryRef>
+ </Fragment>
+
+ </Wix>
diff --git a/contrib/xml.rnc b/contrib/xml.rnc
new file mode 100644
index 0000000..3426887
--- /dev/null
+++ b/contrib/xml.rnc
@@ -0,0 +1,41 @@
+# RelaxNG schema for "xml" log style
+# Inspired by Subversion's XML log format.
+
+start = log
+node.type = xsd:string {minLength = "40" maxLength = "40"}
+
+log = element log { logentry+ }
+logentry = element logentry {
+ logentry.attlist,
+ branch*, tag*, hgparent*,
+ author, date,
+ msg, paths?, copies?, extra*
+}
+logentry.attlist =
+ attribute revision {xsd:nonNegativeInteger}
+ & attribute node {node.type}
+branch = element branch { text }
+tag = element tag { text }
+hgparent = element parent {hgparent.attlist, text}
+hgparent.attlist =
+ attribute revision {xsd:integer {minInclusive = "-1"} }
+ & attribute node {node.type}
+author = element author { author.attlist, text }
+author.attlist =
+ attribute email {text}
+date = element date {xsd:dateTime}
+msg = element msg {msg.attlist, text}
+msg.attlist =
+ attribute xml:space {"preserve"}
+paths = element paths { path* }
+path = element path { path.attlist, text }
+path.attlist =
+ # Action: (A)dd, (M)odify, (R)emove
+ attribute action {"A"|"M"|"R"}
+copies = element copies { copy+ }
+copy = element copy { copy.attlist, text }
+copy.attlist =
+ attribute source {text}
+extra = element extra {extra.attlist, text}
+extra.attlist =
+ attribute key {text}
diff --git a/contrib/zsh_completion b/contrib/zsh_completion
new file mode 100644
index 0000000..6cbafb2
--- /dev/null
+++ b/contrib/zsh_completion
@@ -0,0 +1,1077 @@
+#compdef hg
+
+# Zsh completion script for mercurial. Rename this file to _hg and copy
+# it into your zsh function path (/usr/share/zsh/site-functions for
+# instance)
+#
+# If you do not want to install it globally, you can copy it somewhere
+# else and add that directory to $fpath. This must be done before
+# compinit is called. If the file is copied to ~/.zsh.d, your ~/.zshrc
+# file could look like this:
+#
+# fpath=("$HOME/.zsh.d" $fpath)
+# autoload -U compinit
+# compinit
+#
+# Copyright (C) 2005, 2006 Steve Borho <steve@borho.org>
+# Copyright (C) 2006-10 Brendan Cully <brendan@kublai.com>
+#
+# Permission is hereby granted, without written agreement and without
+# licence or royalty fees, to use, copy, modify, and distribute this
+# software and to distribute modified versions of this software for any
+# purpose, provided that the above copyright notice and the following
+# two paragraphs appear in all copies of this software.
+#
+# In no event shall the authors be liable to any party for direct,
+# indirect, special, incidental, or consequential damages arising out of
+# the use of this software and its documentation, even if the authors
+# have been advised of the possibility of such damage.
+#
+# The authors specifically disclaim any warranties, including, but not
+# limited to, the implied warranties of merchantability and fitness for
+# a particular purpose. The software provided hereunder is on an "as
+# is" basis, and the authors have no obligation to provide maintenance,
+# support, updates, enhancements, or modifications.
+
+emulate -LR zsh
+setopt extendedglob
+
+local curcontext="$curcontext" state line
+typeset -A _hg_cmd_globals
+
+_hg() {
+ local cmd _hg_root
+ integer i=2
+ _hg_cmd_globals=()
+
+ while (( i < $#words ))
+ do
+ case "$words[$i]" in
+ -R|--repository)
+ eval _hg_root="$words[$i+1]"
+ _hg_cmd_globals+=("$words[$i]" "$_hg_root")
+ (( i += 2 ))
+ continue
+ ;;
+ -R*)
+ _hg_cmd_globals+="$words[$i]"
+ eval _hg_root="${words[$i]#-R}"
+ (( i++ ))
+ continue
+ ;;
+ --cwd|--config)
+ # pass along arguments to hg completer
+ _hg_cmd_globals+=("$words[$i]" "$words[$i+1]")
+ (( i += 2 ))
+ continue
+ ;;
+ -*)
+ # skip option
+ (( i++ ))
+ continue
+ ;;
+ esac
+ if [[ -z "$cmd" ]]
+ then
+ cmd="$words[$i]"
+ words[$i]=()
+ (( CURRENT-- ))
+ fi
+ (( i++ ))
+ done
+
+ if [[ -z "$cmd" ]]
+ then
+ _arguments -s -w : $_hg_global_opts \
+ ':mercurial command:_hg_commands'
+ return
+ fi
+
+ # resolve abbreviations and aliases
+ if ! (( $+functions[_hg_cmd_${cmd}] ))
+ then
+ local cmdexp
+ (( $#_hg_cmd_list )) || _hg_get_commands
+
+ cmdexp=$_hg_cmd_list[(r)${cmd}*]
+ if [[ $cmdexp == $_hg_cmd_list[(R)${cmd}*] ]]
+ then
+ # might be nice to rewrite the command line with the expansion
+ cmd="$cmdexp"
+ fi
+ if [[ -n $_hg_alias_list[$cmd] ]]
+ then
+ cmd=$_hg_alias_list[$cmd]
+ fi
+ fi
+
+ curcontext="${curcontext%:*:*}:hg-${cmd}:"
+
+ zstyle -s ":completion:$curcontext:" cache-policy update_policy
+
+ if [[ -z "$update_policy" ]]
+ then
+ zstyle ":completion:$curcontext:" cache-policy _hg_cache_policy
+ fi
+
+ if (( $+functions[_hg_cmd_${cmd}] ))
+ then
+ _hg_cmd_${cmd}
+ else
+ # complete unknown commands normally
+ _arguments -s -w : $_hg_global_opts \
+ '*:files:_hg_files'
+ fi
+}
+
+_hg_cache_policy() {
+ typeset -a old
+
+ # cache for a minute
+ old=( "$1"(mm+10) )
+ (( $#old )) && return 0
+
+ return 1
+}
+
+_hg_get_commands() {
+ typeset -ga _hg_cmd_list
+ typeset -gA _hg_alias_list
+ local hline cmd cmdalias
+
+ _call_program hg hg debugcomplete -v | while read -A hline
+ do
+ 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
+}
+
+_hg_commands() {
+ (( $#_hg_cmd_list )) || _hg_get_commands
+ _describe -t commands 'mercurial command' _hg_cmd_list
+}
+
+_hg_revrange() {
+ compset -P 1 '*:'
+ _hg_labels "$@"
+}
+
+_hg_labels() {
+ _hg_tags "$@"
+ _hg_bookmarks "$@"
+ _hg_branches "$@"
+}
+
+_hg_tags() {
+ typeset -a tags
+ local tag rev
+
+ _hg_cmd tags | while read tag
+ do
+ tags+=(${tag/ # [0-9]#:*})
+ done
+ (( $#tags )) && _describe -t tags 'tags' tags
+}
+
+_hg_bookmarks() {
+ typeset -a bookmark bookmarks
+
+ _hg_cmd bookmarks | while read -A bookmark
+ do
+ if test -z ${bookmark[-1]:#[0-9]*}
+ then
+ bookmarks+=($bookmark[-2])
+ fi
+ done
+ (( $#bookmarks )) && _describe -t bookmarks 'bookmarks' bookmarks
+}
+
+_hg_branches() {
+ typeset -a branches
+ local branch
+
+ _hg_cmd branches | while read branch
+ do
+ branches+=(${branch/ # [0-9]#:*})
+ done
+ (( $#branches )) && _describe -t branches 'branches' branches
+}
+
+# likely merge candidates
+_hg_mergerevs() {
+ typeset -a heads
+ local myrev
+
+ heads=(${(f)"$(_hg_cmd heads --template '{rev}\\n')"})
+ # exclude own revision
+ myrev=$(_hg_cmd log -r . --template '{rev}\\n')
+ heads=(${heads:#$myrev})
+
+ (( $#heads )) && _describe -t heads 'heads' heads
+}
+
+_hg_files() {
+ if [[ -n "$_hg_root" ]]
+ then
+ [[ -d "$_hg_root/.hg" ]] || return
+ case "$_hg_root" in
+ /*)
+ _files -W $_hg_root
+ ;;
+ *)
+ _files -W $PWD/$_hg_root
+ ;;
+ esac
+ else
+ _files
+ fi
+}
+
+_hg_status() {
+ [[ -d $PREFIX ]] || PREFIX=$PREFIX:h
+ status_files=(${(ps:\0:)"$(_hg_cmd status -0n$1 ./$PREFIX)"})
+}
+
+_hg_unknown() {
+ typeset -a status_files
+ _hg_status u
+ _wanted files expl 'unknown files' _multi_parts / status_files
+}
+
+_hg_missing() {
+ typeset -a status_files
+ _hg_status d
+ _wanted files expl 'missing files' _multi_parts / status_files
+}
+
+_hg_modified() {
+ typeset -a status_files
+ _hg_status m
+ _wanted files expl 'modified files' _multi_parts / status_files
+}
+
+_hg_resolve() {
+ local rstate rpath
+
+ [[ -d $PREFIX ]] || PREFIX=$PREFIX:h
+
+ _hg_cmd resolve -l ./$PREFIX | 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)"}%%\=*})
+ (( $#items )) && _describe -t config 'config item' items
+}
+
+_hg_addremove() {
+ _alternative 'files:unknown files:_hg_unknown' \
+ 'files:missing files:_hg_missing'
+}
+
+_hg_ssh_urls() {
+ if [[ -prefix */ ]]
+ then
+ if zstyle -T ":completion:${curcontext}:files" remote-access
+ then
+ local host=${PREFIX%%/*}
+ typeset -a remdirs
+ compset -p $(( $#host + 1 ))
+ local rempath=${(M)PREFIX##*/}
+ local cacheid="hg:${host}-${rempath//\//_}"
+ cacheid=${cacheid%[-_]}
+ compset -P '*/'
+ if _cache_invalid "$cacheid" || ! _retrieve_cache "$cacheid"
+ then
+ remdirs=(${${(M)${(f)"$(_call_program files ssh -a -x $host ls -1FL "${(q)rempath}")"}##*/}%/})
+ _store_cache "$cacheid" remdirs
+ fi
+ _describe -t directories 'remote directory' remdirs -S/
+ else
+ _message 'remote directory'
+ fi
+ else
+ if compset -P '*@'
+ then
+ _hosts -S/
+ else
+ _alternative 'hosts:remote host name:_hosts -S/' \
+ 'users:user:_users -S@'
+ fi
+ fi
+}
+
+_hg_urls() {
+ if compset -P bundle://
+ then
+ _files
+ elif compset -P ssh://
+ then
+ _hg_ssh_urls
+ elif [[ -prefix *: ]]
+ then
+ _urls
+ else
+ local expl
+ compset -S '[^:]*'
+ _wanted url-schemas expl 'URL schema' compadd -S '' - \
+ http:// https:// ssh:// bundle://
+ fi
+}
+
+_hg_paths() {
+ typeset -a paths pnames
+ _hg_cmd paths | while read -A pnames
+ do
+ paths+=($pnames[1])
+ done
+ (( $#paths )) && _describe -t path-aliases 'repository alias' paths
+}
+
+_hg_remote() {
+ _alternative 'path-aliases:repository alias:_hg_paths' \
+ 'directories:directory:_files -/' \
+ 'urls:URL:_hg_urls'
+}
+
+_hg_clone_dest() {
+ _alternative 'directories:directory:_files -/' \
+ 'urls:URL:_hg_urls'
+}
+
+# Common options
+_hg_global_opts=(
+ '(--repository -R)'{-R+,--repository}'[repository root directory]:repository:_files -/'
+ '--cwd[change working directory]:new working directory:_files -/'
+ '(--noninteractive -y)'{-y,--noninteractive}'[do not prompt, assume yes for any required answers]'
+ '(--verbose -v)'{-v,--verbose}'[enable additional output]'
+ '*--config[set/override config option]:defined config items:_hg_config'
+ '(--quiet -q)'{-q,--quiet}'[suppress output]'
+ '(--help -h)'{-h,--help}'[display help and exit]'
+ '--debug[debug mode]'
+ '--debugger[start debugger]'
+ '--encoding[set the charset encoding]'
+ '--encodingmode[set the charset encoding mode]'
+ '--lsprof[print improved command execution profile]'
+ '--traceback[print traceback on exception]'
+ '--time[time how long the command takes]'
+ '--profile[profile]'
+ '--version[output version information and exit]'
+)
+
+_hg_pat_opts=(
+ '*'{-I+,--include}'[include names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/'
+ '*'{-X+,--exclude}'[exclude names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/')
+
+_hg_diff_opts=(
+ '(--text -a)'{-a,--text}'[treat all files as text]'
+ '(--git -g)'{-g,--git}'[use git extended diff format]'
+ "--nodates[omit dates from diff headers]")
+
+_hg_dryrun_opts=(
+ '(--dry-run -n)'{-n,--dry-run}'[do not perform actions, just print output]')
+
+_hg_style_opts=(
+ '--style[display using template map file]:'
+ '--template[display with template]:')
+
+_hg_commit_opts=(
+ '(-m --message -l --logfile --edit -e)'{-e,--edit}'[edit commit message]'
+ '(-e --edit -l --logfile --message -m)'{-m+,--message}'[use <text> as commit message]:message:'
+ '(-e --edit -m --message --logfile -l)'{-l+,--logfile}'[read the commit message from <file>]:log file:_files')
+
+_hg_remote_opts=(
+ '(--ssh -e)'{-e+,--ssh}'[specify ssh command to use]:'
+ '--remotecmd[specify hg command to run on the remote side]:')
+
+_hg_cmd() {
+ _call_program hg HGPLAIN=1 hg "$_hg_cmd_globals[@]" "$@" 2> /dev/null
+}
+
+_hg_cmd_add() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+ '*:unknown files:_hg_unknown'
+}
+
+_hg_cmd_addremove() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+ '(--similarity -s)'{-s+,--similarity}'[guess renamed files by similarity (0<=s<=100)]:' \
+ '*:unknown or missing files:_hg_addremove'
+}
+
+_hg_cmd_annotate() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--rev -r)'{-r+,--rev}'[annotate the specified revision]:revision:_hg_labels' \
+ '(--follow -f)'{-f,--follow}'[follow file copies and renames]' \
+ '(--text -a)'{-a,--text}'[treat all files as text]' \
+ '(--user -u)'{-u,--user}'[list the author]' \
+ '(--date -d)'{-d,--date}'[list the date]' \
+ '(--number -n)'{-n,--number}'[list the revision number (default)]' \
+ '(--changeset -c)'{-c,--changeset}'[list the changeset]' \
+ '*:files:_hg_files'
+}
+
+_hg_cmd_archive() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '--no-decode[do not pass files through decoders]' \
+ '(--prefix -p)'{-p+,--prefix}'[directory prefix for files in archive]:' \
+ '(--rev -r)'{-r+,--rev}'[revision to distribute]:revision:_hg_labels' \
+ '(--type -t)'{-t+,--type}'[type of distribution to create]:archive type:(files tar tbz2 tgz uzip zip)' \
+ '*:destination:_files'
+}
+
+_hg_cmd_backout() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '--merge[merge with old dirstate parent after backout]' \
+ '(--date -d)'{-d+,--date}'[record datecode as commit date]:date code:' \
+ '--parent[parent to choose when backing out merge]' \
+ '(--user -u)'{-u+,--user}'[record user as commiter]:user:' \
+ '(--rev -r)'{-r+,--rev}'[revision]:revision:_hg_labels' \
+ '(--message -m)'{-m+,--message}'[use <text> as commit message]:text:' \
+ '(--logfile -l)'{-l+,--logfile}'[read commit message from <file>]:log file:_files -g \*.txt'
+}
+
+_hg_cmd_bisect() {
+ _arguments -s -w : $_hg_global_opts \
+ '(-)'{-r,--reset}'[reset bisect state]' \
+ '(--good -g --bad -b --skip -s --reset -r)'{-g,--good}'[mark changeset good]'::revision:_hg_labels \
+ '(--good -g --bad -b --skip -s --reset -r)'{-b,--bad}'[mark changeset bad]'::revision:_hg_labels \
+ '(--good -g --bad -b --skip -s --reset -r)'{-s,--skip}'[skip testing changeset]' \
+ '(--command -c --noupdate -U)'{-c+,--command}'[use command to check changeset state]':commands:_command_names \
+ '(--command -c --noupdate -U)'{-U,--noupdate}'[do not update to target]'
+}
+
+_hg_cmd_bookmarks() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--force -f)'{-f,--force}'[force]' \
+ '(--rev -r --delete -d --rename -m)'{-r+,--rev}'[revision]:revision:_hg_labels' \
+ '(--rev -r --delete -d --rename -m)'{-d,--delete}'[delete a given bookmark]' \
+ '(--rev -r --delete -d --rename -m)'{-m+,--rename}'[rename a given bookmark]:bookmark:_hg_bookmarks' \
+ ':bookmark:_hg_bookmarks'
+}
+
+_hg_cmd_branch() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--force -f)'{-f,--force}'[set branch name even if it shadows an existing branch]' \
+ '(--clean -C)'{-C,--clean}'[reset branch name to parent branch name]'
+}
+
+_hg_cmd_branches() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--active -a)'{-a,--active}'[show only branches that have unmerge heads]'
+}
+
+_hg_cmd_bundle() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
+ '(--force -f)'{-f,--force}'[run even when remote repository is unrelated]' \
+ '(2)*--base[a base changeset to specify instead of a destination]:revision:_hg_labels' \
+ ':output file:_files' \
+ ':destination repository:_files -/'
+}
+
+_hg_cmd_cat() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--output -o)'{-o+,--output}'[print output to file with formatted name]:filespec:' \
+ '(--rev -r)'{-r+,--rev}'[revision]:revision:_hg_labels' \
+ '*:file:_hg_files'
+}
+
+_hg_cmd_clone() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
+ '(--noupdate -U)'{-U,--noupdate}'[do not update the new working directory]' \
+ '(--rev -r)'{-r+,--rev}'[a changeset you would like to have after cloning]:' \
+ '--uncompressed[use uncompressed transfer (fast over LAN)]' \
+ ':source repository:_hg_remote' \
+ ':destination:_hg_clone_dest'
+}
+
+_hg_cmd_commit() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--addremove -A)'{-A,--addremove}'[mark new/missing files as added/removed before committing]' \
+ '(--message -m)'{-m+,--message}'[use <text> as commit message]:text:' \
+ '(--logfile -l)'{-l+,--logfile}'[read commit message from <file>]:log file:_files -g \*.txt' \
+ '(--date -d)'{-d+,--date}'[record datecode as commit date]:date code:' \
+ '(--user -u)'{-u+,--user}'[record user as commiter]:user:' \
+ '--amend[amend the parent of the working dir]' \
+ '*:file:_hg_files'
+}
+
+_hg_cmd_copy() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+ '(--after -A)'{-A,--after}'[record a copy that has already occurred]' \
+ '(--force -f)'{-f,--force}'[forcibly copy over an existing managed file]' \
+ '*:file:_hg_files'
+}
+
+_hg_cmd_diff() {
+ typeset -A opt_args
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_diff_opts \
+ '*'{-r,--rev}'+[revision]:revision:_hg_revrange' \
+ '(--show-function -p)'{-p,--show-function}'[show which function each change is in]' \
+ '(--ignore-all-space -w)'{-w,--ignore-all-space}'[ignore white space when comparing lines]' \
+ '(--ignore-space-change -b)'{-b,--ignore-space-change}'[ignore changes in the amount of white space]' \
+ '(--ignore-blank-lines -B)'{-B,--ignore-blank-lines}'[ignore changes whose lines are all blank]' \
+ '*:file:->diff_files'
+
+ if [[ $state == 'diff_files' ]]
+ then
+ if [[ -n $opt_args[-r] ]]
+ then
+ _hg_files
+ else
+ _hg_modified
+ fi
+ fi
+}
+
+_hg_cmd_export() {
+ _arguments -s -w : $_hg_global_opts $_hg_diff_opts \
+ '(--outout -o)'{-o+,--output}'[print output to file with formatted name]:filespec:' \
+ '--switch-parent[diff against the second parent]' \
+ '*:revision:_hg_labels'
+}
+
+_hg_cmd_graft() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--continue -c)'{-c,--continue}'[resume interrupted graft]' \
+ '(--edit -e)'{-e,--edit}'[invoke editor on commit messages]' \
+ '--log[append graft info to log message]' \
+ '(--currentdate -D)'{-D,--currentdate}'[record the current date as commit date]' \
+ '(--currentuser -U)'{-U,--currentuser}'[record the current user as committer]' \
+ '(--date -d)'{-d,--date}'[record the specified date as commit date]' \
+ '(--user -u)'{-u,--user}'[record the specified user as committer]' \
+ '(--tool -t)'{-t,--tool}'[specify merge tool]' \
+ '(--dry-run -n)'{-n,--dry-run}'[do not perform actions, just print output]' \
+ '*:revision:_hg_labels'
+}
+
+_hg_cmd_grep() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--print0 -0)'{-0,--print0}'[end filenames with NUL]' \
+ '--all[print all revisions with matches]' \
+ '(--follow -f)'{-f,--follow}'[follow changeset or file history]' \
+ '(--ignore-case -i)'{-i,--ignore-case}'[ignore case when matching]' \
+ '(--files-with-matches -l)'{-l,--files-with-matches}'[print only filenames and revs that match]' \
+ '(--line-number -n)'{-n,--line-number}'[print matching line numbers]' \
+ '*'{-r+,--rev}'[search in given revision range]:revision:_hg_revrange' \
+ '(--user -u)'{-u,--user}'[print user who committed change]' \
+ '1:search pattern:' \
+ '*:files:_hg_files'
+}
+
+_hg_cmd_heads() {
+ _arguments -s -w : $_hg_global_opts $_hg_style_opts \
+ '(--rev -r)'{-r+,--rev}'[show only heads which are descendants of rev]:revision:_hg_labels'
+}
+
+_hg_cmd_help() {
+ _arguments -s -w : $_hg_global_opts \
+ '*:mercurial command:_hg_commands'
+}
+
+_hg_cmd_identify() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--rev -r)'{-r+,--rev}'[identify the specified rev]:revision:_hg_labels' \
+ '(--num -n)'{-n+,--num}'[show local revision number]' \
+ '(--id -i)'{-i+,--id}'[show global revision id]' \
+ '(--branch -b)'{-b+,--branch}'[show branch]' \
+ '(--tags -t)'{-t+,--tags}'[show tags]'
+}
+
+_hg_cmd_import() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--strip -p)'{-p+,--strip}'[directory strip option for patch (default: 1)]:count:' \
+ '(--message -m)'{-m+,--message}'[use <text> as commit message]:text:' \
+ '(--force -f)'{-f,--force}'[skip check for outstanding uncommitted changes]' \
+ '--bypass[apply patch without touching the working directory]' \
+ '*:patch:_files'
+}
+
+_hg_cmd_incoming() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts $_hg_style_opts \
+ '(--no-merges -M)'{-M,--no-merges}'[do not show merge revisions]' \
+ '(--force -f)'{-f,--force}'[run even when the remote repository is unrelated]' \
+ '(--patch -p)'{-p,--patch}'[show patch]' \
+ '(--rev -r)'{-r+,--rev}'[a specific revision up to which you would like to pull]:revision:_hg_tags' \
+ '(--newest-first -n)'{-n,--newest-first}'[show newest record first]' \
+ '--bundle[file to store the bundles into]:bundle file:_files' \
+ ':source:_hg_remote'
+}
+
+_hg_cmd_init() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
+ ':dir:_files -/'
+}
+
+_hg_cmd_locate() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--rev -r)'{-r+,--rev}'[search repository as it stood at revision]:revision:_hg_labels' \
+ '(--print0 -0)'{-0,--print0}'[end filenames with NUL, for use with xargs]' \
+ '(--fullpath -f)'{-f,--fullpath}'[print complete paths]' \
+ '*:search pattern:_hg_files'
+}
+
+_hg_cmd_log() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_style_opts \
+ '(--follow --follow-first -f)'{-f,--follow}'[follow changeset or history]' \
+ '(-f --follow)--follow-first[only follow the first parent of merge changesets]' \
+ '(--copies -C)'{-C,--copies}'[show copied files]' \
+ '(--keyword -k)'{-k+,--keyword}'[search for a keyword]:' \
+ '(--limit -l)'{-l+,--limit}'[limit number of changes displayed]:' \
+ '*'{-r,--rev}'[show the specified revision or range]:revision:_hg_revrange' \
+ '(--no-merges -M)'{-M,--no-merges}'[do not show merges]' \
+ '(--only-merges -m)'{-m,--only-merges}'[show only merges]' \
+ '(--patch -p)'{-p,--patch}'[show patch]' \
+ '(--prune -P)'{-P+,--prune}'[do not display revision or any of its ancestors]:revision:_hg_labels' \
+ '(--branch -b)'{-b+,--branch}'[show changesets within the given named branch]:branch:_hg_branches' \
+ '*:files:_hg_files'
+}
+
+_hg_cmd_manifest() {
+ _arguments -s -w : $_hg_global_opts \
+ '--all[list files from all revisions]' \
+ ':revision:_hg_labels'
+}
+
+_hg_cmd_merge() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--force -f)'{-f,--force}'[force a merge with outstanding changes]' \
+ '(--rev -r 1)'{-r,--rev}'[revision to merge]:revision:_hg_mergerevs' \
+ '(--preview -P)'{-P,--preview}'[review revisions to merge (no merge is performed)]' \
+ '(--tool -t)'{-t,--tool}'[specify merge tool]' \
+ ':revision:_hg_mergerevs'
+}
+
+_hg_cmd_outgoing() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts $_hg_style_opts \
+ '(--no-merges -M)'{-M,--no-merges}'[do not show merge revisions]' \
+ '(--force -f)'{-f,--force}'[run even when the remote repository is unrelated]' \
+ '(--patch -p)'{-p,--patch}'[show patch]' \
+ '(--rev -r)'{-r+,--rev}'[a specific revision you would like to push]' \
+ '(--newest-first -n)'{-n,--newest-first}'[show newest record first]' \
+ ':destination:_hg_remote'
+}
+
+_hg_cmd_parents() {
+ _arguments -s -w : $_hg_global_opts $_hg_style_opts \
+ '(--rev -r)'{-r+,--rev}'[show parents of the specified rev]:revision:_hg_labels' \
+ ':last modified file:_hg_files'
+}
+
+_hg_cmd_paths() {
+ _arguments -s -w : $_hg_global_opts \
+ ':path:_hg_paths'
+}
+
+_hg_cmd_phase() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--public -p)'{-p,--public}'[set changeset phase to public]' \
+ '(--draft -d)'{-d,--draft}'[set changeset phase to draft]' \
+ '(--secret -s)'{-s,--secret}'[set changeset phase to secret]' \
+ '(--force -f)'{-f,--force}'[allow to move boundary backward]' \
+ '(--rev -r)'{-r+,--rev}'[target revision]:revision:_hg_labels' \
+ ':revision:_hg_labels'
+}
+
+_hg_cmd_pull() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
+ '(--force -f)'{-f,--force}'[run even when the remote repository is unrelated]' \
+ '(--update -u)'{-u,--update}'[update to new tip if changesets were pulled]' \
+ '(--rev -r)'{-r+,--rev}'[a specific revision up to which you would like to pull]:revision:' \
+ ':source:_hg_remote'
+}
+
+_hg_cmd_push() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
+ '(--force -f)'{-f,--force}'[force push]' \
+ '(--rev -r)'{-r+,--rev}'[a specific revision you would like to push]:revision:_hg_labels' \
+ ':destination:_hg_remote'
+}
+
+_hg_cmd_remove() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--after -A)'{-A,--after}'[record remove that has already occurred]' \
+ '(--force -f)'{-f,--force}'[remove file even if modified]' \
+ '*:file:_hg_files'
+}
+
+_hg_cmd_rename() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+ '(--after -A)'{-A,--after}'[record a rename that has already occurred]' \
+ '(--force -f)'{-f,--force}'[forcibly copy over an existing managed file]' \
+ '*: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_labels' \
+ '(--no-backup -C)'{-C,--no-backup}'[do not save backup copies of files]' \
+ '*:file:->diff_files'
+
+ if [[ $state == 'diff_files' ]]
+ then
+ if [[ -n $opt_args[-r] ]]
+ then
+ _hg_files
+ else
+ typeset -a status_files
+ _hg_status mard
+ _wanted files expl 'modified, added, removed or deleted file' _multi_parts / status_files
+ fi
+ fi
+}
+
+_hg_cmd_serve() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--accesslog -A)'{-A+,--accesslog}'[name of access log file]:log file:_files' \
+ '(--errorlog -E)'{-E+,--errorlog}'[name of error log file]:log file:_files' \
+ '(--daemon -d)'{-d,--daemon}'[run server in background]' \
+ '(--port -p)'{-p+,--port}'[listen port]:listen port:' \
+ '(--address -a)'{-a+,--address}'[interface address]:interface address:' \
+ '(--name -n)'{-n+,--name}'[name to show in web pages]:repository name:' \
+ '(--templates -t)'{-t,--templates}'[web template directory]:template dir:_files -/' \
+ '--style[web template style]:style' \
+ '--stdio[for remote clients]' \
+ '(--ipv6 -6)'{-6,--ipv6}'[use IPv6 in addition to IPv4]'
+}
+
+_hg_cmd_showconfig() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--untrusted -u)'{-u+,--untrusted}'[show untrusted configuration options]' \
+ ':config item:_hg_config'
+}
+
+_hg_cmd_status() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--all -A)'{-A,--all}'[show status of all files]' \
+ '(--modified -m)'{-m,--modified}'[show only modified files]' \
+ '(--added -a)'{-a,--added}'[show only added files]' \
+ '(--removed -r)'{-r,--removed}'[show only removed files]' \
+ '(--deleted -d)'{-d,--deleted}'[show only deleted (but tracked) files]' \
+ '(--clean -c)'{-c,--clean}'[show only files without changes]' \
+ '(--unknown -u)'{-u,--unknown}'[show only unknown files]' \
+ '(--ignored -i)'{-i,--ignored}'[show ignored files]' \
+ '(--no-status -n)'{-n,--no-status}'[hide status prefix]' \
+ '(--copies -C)'{-C,--copies}'[show source of copied files]' \
+ '(--print0 -0)'{-0,--print0}'[end filenames with NUL, for use with xargs]' \
+ '--rev[show difference from revision]:revision:_hg_labels' \
+ '*:files:_files'
+}
+
+_hg_cmd_summary() {
+ _arguments -s -w : $_hg_global_opts \
+ '--remote[check for push and pull]'
+}
+
+_hg_cmd_tag() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--local -l)'{-l,--local}'[make the tag local]' \
+ '(--message -m)'{-m+,--message}'[message for tag commit log entry]:message:' \
+ '(--date -d)'{-d+,--date}'[record datecode as commit date]:date code:' \
+ '(--user -u)'{-u+,--user}'[record user as commiter]:user:' \
+ '(--rev -r)'{-r+,--rev}'[revision to tag]:revision:_hg_labels' \
+ ':tag name:'
+}
+
+_hg_cmd_tip() {
+ _arguments -s -w : $_hg_global_opts $_hg_style_opts \
+ '(--patch -p)'{-p,--patch}'[show patch]'
+}
+
+_hg_cmd_unbundle() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--update -u)'{-u,--update}'[update to new tip if changesets were unbundled]' \
+ ':files:_files'
+}
+
+_hg_cmd_update() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--clean -C)'{-C,--clean}'[overwrite locally modified files]' \
+ '(--rev -r)'{-r+,--rev}'[revision]:revision:_hg_labels' \
+ ':revision:_hg_labels'
+}
+
+## extensions ##
+
+# HGK
+_hg_cmd_view() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--limit -l)'{-l+,--limit}'[limit number of changes displayed]:' \
+ ':revision range:_hg_tags'
+}
+
+# MQ
+_hg_qseries() {
+ typeset -a patches
+ patches=(${(f)"$(_hg_cmd qseries)"})
+ (( $#patches )) && _describe -t hg-patches 'patches' patches
+}
+
+_hg_qapplied() {
+ typeset -a patches
+ patches=(${(f)"$(_hg_cmd qapplied)"})
+ if (( $#patches ))
+ then
+ patches+=(qbase qtip)
+ _describe -t hg-applied-patches 'applied patches' patches
+ fi
+}
+
+_hg_qunapplied() {
+ typeset -a patches
+ patches=(${(f)"$(_hg_cmd qunapplied)"})
+ (( $#patches )) && _describe -t hg-unapplied-patches 'unapplied patches' patches
+}
+
+# unapplied, including guarded patches
+_hg_qdeletable() {
+ typeset -a unapplied
+ unapplied=(${(f)"$(_hg_cmd qseries)"})
+ for p in $(_hg_cmd qapplied)
+ do
+ unapplied=(${unapplied:#$p})
+ done
+
+ (( $#unapplied )) && _describe -t hg-allunapplied-patches 'all unapplied patches' unapplied
+}
+
+_hg_qguards() {
+ typeset -a guards
+ local guard
+ compset -P "+|-"
+ _hg_cmd qselect -s | while read guard
+ do
+ guards+=(${guard#(+|-)})
+ done
+ (( $#guards )) && _describe -t hg-guards 'guards' guards
+}
+
+_hg_qseries_opts=(
+ '(--summary -s)'{-s,--summary}'[print first line of patch header]')
+
+_hg_cmd_qapplied() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
+
+_hg_cmd_qdelete() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--keep -k)'{-k,--keep}'[keep patch file]' \
+ '*'{-r+,--rev}'[stop managing a revision]:applied patch:_hg_revrange' \
+ '*:unapplied patch:_hg_qdeletable'
+}
+
+_hg_cmd_qdiff() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '*:pattern:_hg_files'
+}
+
+_hg_cmd_qfinish() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--applied -a)'{-a,--applied}'[finish all applied patches]' \
+ '*:patch:_hg_qapplied'
+}
+
+_hg_cmd_qfold() {
+ _arguments -s -w : $_hg_global_opts $_h_commit_opts \
+ '(--keep,-k)'{-k,--keep}'[keep folded patch files]' \
+ '*:unapplied patch:_hg_qunapplied'
+}
+
+_hg_cmd_qgoto() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--force -f)'{-f,--force}'[overwrite any local changes]' \
+ ':patch:_hg_qseries'
+}
+
+_hg_cmd_qguard() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--list -l)'{-l,--list}'[list all patches and guards]' \
+ '(--none -n)'{-n,--none}'[drop all guards]' \
+ ':patch:_hg_qseries' \
+ '*:guards:_hg_qguards'
+}
+
+_hg_cmd_qheader() {
+ _arguments -s -w : $_hg_global_opts \
+ ':patch:_hg_qseries'
+}
+
+_hg_cmd_qimport() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--existing -e)'{-e,--existing}'[import file in patch dir]' \
+ '(--name -n 2)'{-n+,--name}'[patch file name]:name:' \
+ '(--force -f)'{-f,--force}'[overwrite existing files]' \
+ '*'{-r+,--rev}'[place existing revisions under mq control]:revision:_hg_revrange' \
+ '*:patch:_files'
+}
+
+_hg_cmd_qnew() {
+ _arguments -s -w : $_hg_global_opts $_hg_commit_opts \
+ '(--force -f)'{-f,--force}'[import uncommitted changes into patch]' \
+ ':patch:'
+}
+
+_hg_cmd_qnext() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
+
+_hg_cmd_qpop() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--all -a :)'{-a,--all}'[pop all patches]' \
+ '(--name -n)'{-n+,--name}'[queue name to pop]:' \
+ '(--force -f)'{-f,--force}'[forget any local changes]' \
+ ':patch:_hg_qapplied'
+}
+
+_hg_cmd_qprev() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
+
+_hg_cmd_qpush() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--all -a :)'{-a,--all}'[apply all patches]' \
+ '(--list -l)'{-l,--list}'[list patch name in commit text]' \
+ '(--merge -m)'{-m+,--merge}'[merge from another queue]:' \
+ '(--name -n)'{-n+,--name}'[merge queue name]:' \
+ '(--force -f)'{-f,--force}'[apply if the patch has rejects]' \
+ '(--exact -e)'{-e,--exact}'[apply the target patch to its recorded parent]' \
+ '--move[reorder patch series and apply only the patch]' \
+ ':patch:_hg_qunapplied'
+}
+
+_hg_cmd_qrefresh() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_commit_opts \
+ '(--git -g)'{-g,--git}'[use git extended diff format]' \
+ '(--short -s)'{-s,--short}'[short refresh]' \
+ '*:files:_hg_files'
+}
+
+_hg_cmd_qrename() {
+ _arguments -s -w : $_hg_global_opts \
+ ':patch:_hg_qseries' \
+ ':destination:'
+}
+
+_hg_cmd_qselect() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--none -n :)'{-n,--none}'[disable all guards]' \
+ '(--series -s :)'{-s,--series}'[list all guards in series file]' \
+ '--pop[pop to before first guarded applied patch]' \
+ '--reapply[pop and reapply patches]' \
+ '*:guards:_hg_qguards'
+}
+
+_hg_cmd_qseries() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts \
+ '(--missing -m)'{-m,--missing}'[print patches not in series]'
+}
+
+_hg_cmd_qunapplied() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
+
+_hg_cmd_qtop() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
+
+_hg_cmd_strip() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--force -f)'{-f,--force}'[force multi-head removal]' \
+ '(--backup -b)'{-b,--backup}'[bundle unrelated changesets]' \
+ '(--nobackup -n)'{-n,--nobackup}'[no backups]' \
+ ':revision:_hg_labels'
+}
+
+# Patchbomb
+_hg_cmd_email() {
+ _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
+ '(--git -g)'{-g,--git}'[use git extended diff format]' \
+ '--plain[omit hg patch header]' \
+ '--body[send patches as inline message text (default)]' \
+ '(--outgoing -o)'{-o,--outgoing}'[send changes not found in the target repository]' \
+ '(--bundle -b)'{-b,--bundle}'[send changes not in target as a binary bundle]' \
+ '--bundlename[name of the bundle attachment file (default: bundle)]:' \
+ '*'{-r+,--rev}'[search in given revision range]:revision:_hg_revrange' \
+ '--force[run even when remote repository is unrelated (with -b/--bundle)]' \
+ '*--base[a base changeset to specify instead of a destination (with -b/--bundle)]:revision:_hg_labels' \
+ '--intro[send an introduction email for a single patch]' \
+ '(--inline -i --attach -a)'{-a,--attach}'[send patches as attachments]' \
+ '(--attach -a --inline -i)'{-i,--inline}'[send patches as inline attachments]' \
+ '*--bcc[email addresses of blind carbon copy recipients]:email:' \
+ '*'{-c+,--cc}'[email addresses of copy recipients]:email:' \
+ '(--diffstat -d)'{-d,--diffstat}'[add diffstat output to messages]' \
+ '--date[use the given date as the sending date]:date:' \
+ '--desc[use the given file as the series description]:files:_files' \
+ '(--from -f)'{-f,--from}'[email address of sender]:email:' \
+ '(--test -n)'{-n,--test}'[print messages that would be sent]' \
+ '(--mbox -m)'{-m,--mbox}'[write messages to mbox file instead of sending them]:file:' \
+ '*--reply-to[email addresses replies should be sent to]:email:' \
+ '(--subject -s)'{-s,--subject}'[subject of first message (intro or single patch)]:subject:' \
+ '--in-reply-to[message identifier to reply to]:msgid:' \
+ '*--flag[flags to add in subject prefixes]:flag:' \
+ '*'{-t,--to}'[email addresses of recipients]:email:' \
+ ':revision:_hg_revrange'
+}
+
+# Rebase
+_hg_cmd_rebase() {
+ _arguments -s -w : $_hg_global_opts \
+ '*'{-r,--rev}'[rebase these revisions]:revision:_hg_revrange' \
+ '(--source -s)'{-s,--source}'[rebase from the specified changeset]:revision:_hg_labels' \
+ '(--base -b)'{-b,--base}'[rebase from the base of the specified changeset]:revision:_hg_labels' \
+ '(--dest -d)'{-d,--dest}'[rebase onto the specified changeset]' \
+ '--collapse[collapse the rebased changeset]' \
+ '(--message -m)'{-m+,--message}'[use <text> as collapse commit message]:text:' \
+ '(--edit -e)'{-e,--edit}'[invoke editor on commit messages]' \
+ '(--logfile -l)'{-l+,--logfile}'[read collapse commit message from <file>]:log file:_files -g \*.txt' \
+ '--keep[keep original changeset]' \
+ '--keepbranches[keep original branch name]' \
+ '(--tool -t)'{-t,--tool}'[specify merge tool]' \
+ '(--continue -c)'{-c,--continue}'[continue an interrupted rebase]' \
+ '(--abort -a)'{-a,--abort}'[abort an interrupted rebase]' \
+}
+
+_hg "$@"