From a498da43c7fdb9f24b73680c02a4a3588cc62d9a Mon Sep 17 00:00:00 2001 From: Lorry Date: Wed, 22 Aug 2012 14:49:51 +0100 Subject: Tarball conversion --- contrib/bash_completion | 593 +++++ contrib/buildrpm | 110 + contrib/casesmash.py | 34 + contrib/check-code.py | 450 ++++ contrib/convert-repo | 27 + contrib/debugcmdserver.py | 47 + contrib/debugshell.py | 21 + contrib/dumprevlog | 25 + contrib/hg-ssh | 86 + contrib/hgfixes/__init__.py | 0 contrib/hgfixes/fix_bytes.py | 97 + contrib/hgfixes/fix_bytesmod.py | 63 + contrib/hgfixes/fix_leftover_imports.py | 108 + contrib/hgk | 4061 +++++++++++++++++++++++++++++++ contrib/hgsh/Makefile | 13 + contrib/hgsh/hgsh.c | 440 ++++ contrib/hgweb.fcgi | 19 + contrib/hgweb.wsgi | 18 + contrib/logo-droplets.svg | 5 + contrib/macosx/Readme.html | 37 + contrib/macosx/Welcome.html | 20 + contrib/macosx/macosx-build.txt | 11 + contrib/memory.py | 36 + contrib/mercurial.el | 1293 ++++++++++ contrib/mercurial.spec | 87 + contrib/mergetools.hgrc | 123 + contrib/mq.el | 417 ++++ contrib/perf.py | 252 ++ contrib/plan9/9diff | 42 + contrib/plan9/README | 39 + contrib/plan9/hgrc.d/9diff.rc | 7 + contrib/plan9/hgrc.d/factotum.rc | 4 + contrib/plan9/mkfile | 37 + contrib/plan9/proto | 24 + contrib/pylintrc | 313 +++ contrib/python-hook-examples.py | 22 + contrib/sample.hgrc | 133 + contrib/setup3k.py | 373 +++ contrib/shrink-revlog.py | 294 +++ contrib/simplemerge | 67 + contrib/tcsh_completion | 50 + contrib/tcsh_completion_build.sh | 74 + contrib/tmplrewrite.py | 23 + contrib/undumprevlog | 37 + contrib/vim/HGAnnotate.vim | 27 + contrib/vim/hg-menu.vim | 93 + contrib/vim/hgcommand.vim | 1703 +++++++++++++ contrib/vim/hgtest.vim | 41 + contrib/vim/patchreview.txt | 97 + contrib/vim/patchreview.vim | 868 +++++++ contrib/win32/ReadMe.html | 162 ++ contrib/win32/buildlocal.bat | 9 + contrib/win32/hg.bat | 12 + contrib/win32/hgwebdir_wsgi.py | 95 + contrib/win32/mercurial.ico | Bin 0 -> 2238 bytes contrib/win32/mercurial.ini | 97 + contrib/win32/mercurial.iss | 148 ++ contrib/win32/postinstall.txt | 9 + contrib/win32/win32-build.txt | 133 + contrib/wix/COPYING.rtf | Bin 0 -> 1686 bytes contrib/wix/README.txt | 31 + contrib/wix/contrib.wxs | 44 + contrib/wix/defines.wxi | 9 + contrib/wix/dist.wxs | 31 + contrib/wix/doc.wxs | 50 + contrib/wix/guids.wxi | 51 + contrib/wix/help.wxs | 34 + contrib/wix/hg.cmd | 3 + contrib/wix/i18n.wxs | 26 + contrib/wix/locale.wxs | 34 + contrib/wix/mercurial.wxs | 172 ++ contrib/wix/templates.wxs | 222 ++ contrib/xml.rnc | 41 + contrib/zsh_completion | 1077 ++++++++ 74 files changed, 15351 insertions(+) create mode 100644 contrib/bash_completion create mode 100755 contrib/buildrpm create mode 100644 contrib/casesmash.py create mode 100755 contrib/check-code.py create mode 100755 contrib/convert-repo create mode 100755 contrib/debugcmdserver.py create mode 100644 contrib/debugshell.py create mode 100755 contrib/dumprevlog create mode 100755 contrib/hg-ssh create mode 100644 contrib/hgfixes/__init__.py create mode 100644 contrib/hgfixes/fix_bytes.py create mode 100644 contrib/hgfixes/fix_bytesmod.py create mode 100644 contrib/hgfixes/fix_leftover_imports.py create mode 100755 contrib/hgk create mode 100644 contrib/hgsh/Makefile create mode 100644 contrib/hgsh/hgsh.c create mode 100755 contrib/hgweb.fcgi create mode 100644 contrib/hgweb.wsgi create mode 100644 contrib/logo-droplets.svg create mode 100644 contrib/macosx/Readme.html create mode 100644 contrib/macosx/Welcome.html create mode 100644 contrib/macosx/macosx-build.txt create mode 100644 contrib/memory.py create mode 100644 contrib/mercurial.el create mode 100755 contrib/mercurial.spec create mode 100644 contrib/mergetools.hgrc create mode 100644 contrib/mq.el create mode 100644 contrib/perf.py create mode 100755 contrib/plan9/9diff create mode 100644 contrib/plan9/README create mode 100644 contrib/plan9/hgrc.d/9diff.rc create mode 100644 contrib/plan9/hgrc.d/factotum.rc create mode 100644 contrib/plan9/mkfile create mode 100644 contrib/plan9/proto create mode 100644 contrib/pylintrc create mode 100644 contrib/python-hook-examples.py create mode 100644 contrib/sample.hgrc create mode 100644 contrib/setup3k.py create mode 100644 contrib/shrink-revlog.py create mode 100755 contrib/simplemerge create mode 100644 contrib/tcsh_completion create mode 100755 contrib/tcsh_completion_build.sh create mode 100755 contrib/tmplrewrite.py create mode 100755 contrib/undumprevlog create mode 100644 contrib/vim/HGAnnotate.vim create mode 100644 contrib/vim/hg-menu.vim create mode 100644 contrib/vim/hgcommand.vim create mode 100644 contrib/vim/hgtest.vim create mode 100644 contrib/vim/patchreview.txt create mode 100644 contrib/vim/patchreview.vim create mode 100644 contrib/win32/ReadMe.html create mode 100644 contrib/win32/buildlocal.bat create mode 100644 contrib/win32/hg.bat create mode 100644 contrib/win32/hgwebdir_wsgi.py create mode 100644 contrib/win32/mercurial.ico create mode 100644 contrib/win32/mercurial.ini create mode 100644 contrib/win32/mercurial.iss create mode 100644 contrib/win32/postinstall.txt create mode 100644 contrib/win32/win32-build.txt create mode 100644 contrib/wix/COPYING.rtf create mode 100644 contrib/wix/README.txt create mode 100644 contrib/wix/contrib.wxs create mode 100644 contrib/wix/defines.wxi create mode 100644 contrib/wix/dist.wxs create mode 100644 contrib/wix/doc.wxs create mode 100644 contrib/wix/guids.wxi create mode 100644 contrib/wix/help.wxs create mode 100644 contrib/wix/hg.cmd create mode 100644 contrib/wix/i18n.wxs create mode 100644 contrib/wix/locale.wxs create mode 100644 contrib/wix/mercurial.wxs create mode 100644 contrib/wix/templates.wxs create mode 100644 contrib/xml.rnc create mode 100644 contrib/zsh_completion (limited to 'contrib') 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 +# +# 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) + "" + +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'.*(? 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'(?]\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'(?< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\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'(?:(?\#.*?$)| + ((?P('''|\"\"\"|(?(([^\\]|\\.)*?)) + (?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(?([^"]|\\")+)"(?!")''', 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'(?', 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 +# +# Author(s): +# Thomas Arendsen Hein +# +# 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 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 . 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 > + """ % 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 {} } + +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 [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 <> + event add <> +} else { + event add <> +} + +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 "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 {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 {resizecdetpanes %W %w} + + pack .ctop -side top -fill both -expand 1 + + bindall <1> {selcanvline %W %x %y} + #bindall {selcanvline %W %x %y} + bindall "allcansmousewheel %D" + bindall "allcanvs yview scroll -5 units" + bindall "allcanvs yview scroll 5 units" + bindall <2> "allcanvs scan mark 0 %y" + bindall "allcanvs scan dragto 0 %y" + bind . "selnextline -1" + bind . "selnextline 1" + bind . "allcanvs yview scroll -1 pages" + bind . "allcanvs yview scroll 1 pages" + bindkey "$ctext yview scroll -1 pages" + bindkey "$ctext yview scroll -1 pages" + bindkey "$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 {findnext 0} + bindkey ? findprev + bindkey f nextfile + bind . doquit + bind . doquit + bind . dofind + bind . {findnext 0} + bind . findprev + bind . {incrfont 1} + bind . {incrfont 1} + bind . {incrfont -1} + bind . {incrfont -1} + bind $cflist <> listboxsel + bind . {savestuff %W} + bind . "click %W" + bind $fstring dofind + bind $sha1entry gotocommit + bind $sha1entry <> 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 ] + } + 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 "lineenter %x %y $id" + $canv bind $t "linemotion %x %y $id" + $canv bind $t "lineleave $id" + $canv bind $t "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) <> "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 { %W configure -cursor hand2 } + $ctext tag bind link { %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 { %W configure -cursor hand2 } + $ctext tag bind link { %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 { %W configure -cursor hand2 } + $ctext tag bind link { %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 + * + * 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 +#include +#include +#include +#include +#include +#include + +/* + * 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 @@ + + +image/svg+xmlMercurial "droplets" logoCali Mastny and Matt MackallFeb 12 2008 + + 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 @@ + + + + + + + + + + +

Before you install

+


+

This is an OS X 10.6 version of Mercurial that depends on the default Python 2.6 installation.

+


+

After you install

+


+

This package installs the hg executable in /usr/local/bin and the Mercurial files in /Library/Python/2.6/site-packages/mercurial.

+


+

Documentation

+


+

Visit the Mercurial web site and wiki

+


+

There's also a free book, Distributed revision control with Mercurial

+


+

Reporting problems

+


+

If you run into any problems, please file a bug online:

+

http://mercurial.selenic.com/bts/

+ + 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 @@ + + + + + + + + + + +

This is a prepackaged release of Mercurial for Mac OS X.

+


+
+

+Please be sure to read the latest release notes.

+ + 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 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 + +;; 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 . + +;;; 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 + +;; 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 . + +(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 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 + +# username = Joe User + +### --- 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 +# as a patch to rewrite-log. Cleaned up, refactored, documented, and +# renamed by Greg Ward . + +# 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 < +" 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,ci :!hg commit %:e! +" amenu H&G.Commit\ All,call :!hg commit:e! +" amenu H&G.-SEP1- + amenu H&G.Add\\add :!hg add % + amenu H&G.Forget\ Add\\fgt :!hg forget % + amenu H&G.Show\ Differences\\diff :call ShowResults("FileDiff", "hg\ diff") + amenu H&G.Revert\ to\ Last\ Version\\revert :!hg revert %:e! + amenu H&G.Show\ History\\log :call ShowResults("FileLog", "hg\ log") + amenu H&G.Annotate\\an :call ShowResults("annotate", "hg\ annotate") + amenu H&G.-SEP1- + amenu H&G.Repo\ Status\\stat :call ShowResults("RepoStatus", "hg\ status") + amenu H&G.Pull\\pull :!hg pull:e! + amenu H&G.Update\\upd :!hg update:e! +endif + +" Section: Mappings {{{1 +if(v:version >= 600) + " The default Leader is \ 'backslash' + map add :!hg add % + map fgt :!hg forget % + map diff :call ShowResults("FileDiff", "hg\ diff") + map revert :!hg revert %:e! + map log :call ShowResults("FileLog", "hg\ log") + map an :call ShowResults("annotate", "hg\ annotate") + map stat :call ShowResults("RepoStatus", "hg\ status") + map upd :!hg update:e! + map pull :!hg pull:e! +else + " pre 6.0, the default Leader was a comma + map ,add :!hg add % + map ,fgt :!hg forget % + map ,diff :call ShowResults("FileDiff", "hg\ diff") + map ,revert :!hg revert:e! + map ,log :call ShowResults("FileLog", "hg\ log") + map ,an :call ShowResults("annotate", "hg\ annotate") + map ,stat :call ShowResults("RepoStatus", "hg\ status") + map ,upd :!hg update:e! + map ,pull :!hg pull:e! +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 +" License: This file is placed in the public domain. +" Credits: +" Bob Hiestand 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 +" +" in conjunction with Vladmir Marek's Hg backend +" +" 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(":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 HGCleanupOnFailure("VIM 6.2 or later required.") + finish +endif + +if !exists("*system") + call 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 = 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=HGResolveLink(a:fileName) + let newCwd=fnamemodify(fileName, ':h') + if strlen(newCwd) > 0 + execute 'cd' escape(newCwd, ' ') + endif + return oldCwd +endfunction + +" Function: 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 = HGGetOption('HGCommandEdit', 'edit') + if editCommand != 'edit' + if 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 HGGetOption("HGCommandNameResultBuffers", 0) + let nameMarker = 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 = 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 HGEditFile(resultBufferName, a:origBuffNR) == -1 + return -1 + endif + + set buftype=nofile + set noswapfile + set filetype= + + if 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 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=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(HGResolveLink(fileName), ':t') + let oldCwd=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=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=HGCurrentBufferCheck() + "echomsg "DBG : in HGGetStatusVars" + if hgBufferCheck == -1 + return "" + endif + let fileName=bufname(hgBufferCheck) + let fileNameWithoutLink=HGResolveLink(fileName) + let realFileName = fnamemodify(fileNameWithoutLink, ':t') + let oldCwd=HGChangeToCurrentFileDir(realFileName) + try + let hgCommand = 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 = 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 = 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 !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 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 = 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 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 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 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 . "/\" + " 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 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 HGSetupBuffer() + au BufWritePost * call HGSetupBuffer() + " Force resetting up buffer on external file change (HG update) + au FileChangedShell * call 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 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 HGMarkOrigBufferForSetup(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 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=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 HGMarkOrigBufferForSetup(HGDoCommand('commit -m "' . a:2 . '"', 'hgcommit', '')) + endif + + let hgBufferCheck=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=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 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 HGCommit '. + \ ':au! HGCommit * ' . autoPattern . ''. + \ ':g/^HG:/d'. + \ ':update'. + \ ':call HGFinishCommit("' . messageFileName . '",' . + \ '"' . newCwd . '",' . + \ '"' . realFileName . '",' . + \ hgBufferCheck . ')' + + silent 0put ='HG: ----------------------------------------------------------------------' + silent put =\"HG: Enter Log. Lines beginning with `HG:' are removed automatically\" + silent put ='HG: Type cc (or your own HGCommit mapping)' + + if HGGetOption('HGCommandCommitOnWrite', 1) == 1 + execute 'au HGCommit BufWritePre' autoPattern 'g/^HG:/d' + execute 'au HGCommit BufWritePost' autoPattern 'call 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=HGGetOption('HGCommandDiffOpt', 'w') + + if hgdiffopt == "" + let diffoptionstring="" + else + let diffoptionstring=" -" . hgdiffopt . " " + endif + + let resultBuffer = 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 = 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=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 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=HGDoCommand('log' . versionOption, 'hglog', caption) + if resultBuffer != "" + set filetype=rcslog + endif + return resultBuffer +endfunction + +" Function: s:HGRevert() {{{2 +function! s:HGRevert() + return HGMarkOrigBufferForSetup(HGDoCommand('revert', 'hgrevert', '')) +endfunction + +" Function: s:HGReview(...) {{{2 +function! s:HGReview(...) + if a:0 == 0 + let versiontag="" + if 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 = 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 HGDoCommand('status', 'hgstatus', '') +endfunction + + +" Function: s:HGUpdate() {{{2 +function! s:HGUpdate() + return HGMarkOrigBufferForSetup(HGDoCommand('update', 'update', '')) +endfunction + +" Function: s:HGVimDiff(...) {{{2 +function! s:HGVimDiff(...) + let originalBuffer = 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 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 HGWipeoutCommandBuffers(s:vimDiffSourceBuffer, 'vimdiff') + endif + let resultBuffer = 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 HGOverrideOption('HGCommandSplit', HGGetOption('HGCommandDiffSplit', HGGetOption('HGCommandSplit', 'vertical'))) + let resultBuffer=HGReview(a:2) + finally + call 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 HGOverrideOption("HGCommandEdit", "split") + call HGOverrideOption("HGCommandSplit", HGGetOption('HGCommandDiffSplit', HGGetOption('HGCommandSplit', 'vertical'))) + if(a:0 == 0) + let resultBuffer=HGReview() + else + let resultBuffer=HGReview(a:1) + endif + finally + call HGOverrideOption("HGCommandEdit") + call 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 HGAdd() +com! -nargs=? HGAnnotate call HGAnnotate() +com! -bang -nargs=? HGCommit call HGCommit(, ) +com! -nargs=* HGDiff call HGDiff() +com! -bang HGGotoOriginal call HGGotoOriginal() +com! -nargs=? HGLog call HGLog() +com! HGRevert call HGRevert() +com! -nargs=? HGReview call HGReview() +com! HGStatus call HGStatus() +com! HGUpdate call HGUpdate() +com! -nargs=* HGVimDiff call HGVimDiff() + +" 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 HGAdd :HGAdd +nnoremap HGAnnotate :HGAnnotate +nnoremap HGCommit :HGCommit +nnoremap HGDiff :HGDiff +nnoremap HGGotoOriginal :HGGotoOriginal +nnoremap HGClearAndGotoOriginal :HGGotoOriginal! +nnoremap HGLog :HGLog +nnoremap HGRevert :HGRevert +nnoremap HGReview :HGReview +nnoremap HGStatus :HGStatus +nnoremap HGUpdate :HGUpdate +nnoremap HGVimDiff :HGVimDiff + +" Section: Default mappings {{{1 +if !hasmapto('HGAdd') + nmap hga HGAdd +endif +if !hasmapto('HGAnnotate') + nmap hgn HGAnnotate +endif +if !hasmapto('HGClearAndGotoOriginal') + nmap hgG HGClearAndGotoOriginal +endif +if !hasmapto('HGCommit') + nmap hgc HGCommit +endif +if !hasmapto('HGDiff') + nmap hgd HGDiff +endif +if !hasmapto('HGGotoOriginal') + nmap hgg HGGotoOriginal +endif +if !hasmapto('HGLog') + nmap hgl HGLog +endif +if !hasmapto('HGRevert') + nmap hgq HGRevert +endif +if !hasmapto('HGReview') + nmap hgr HGReview +endif +if !hasmapto('HGStatus') + nmap hgs HGStatus +endif +if !hasmapto('HGUpdate') + nmap hgu HGUpdate +endif +if !hasmapto('HGVimDiff') + nmap hgv HGVimDiff +endif + +" Section: Menu items {{{1 +silent! aunmenu Plugin.HG +amenu &Plugin.HG.&Add HGAdd +amenu &Plugin.HG.A&nnotate HGAnnotate +amenu &Plugin.HG.&Commit HGCommit +amenu &Plugin.HG.&Diff HGDiff +amenu &Plugin.HG.&Log HGLog +amenu &Plugin.HG.Revert HGRevert +amenu &Plugin.HG.&Review HGReview +amenu &Plugin.HG.&Status HGStatus +amenu &Plugin.HG.&Update HGUpdate +amenu &Plugin.HG.&VimDiff 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 HGVimDiffRestore(expand("")) +augroup END + +" Section: Optional activation of buffer management {{{1 + +if s:HGGetOption('HGCommandEnableBufferSetup', 1) + call HGEnableBufferSetup() +endif + +" Section: Doc installation {{{1 + +if HGInstallDocumentation(expand(":p")) + echomsg s:script_name s:script_version . ": updated documentation" +endif + +" Section: Plugin completion {{{1 + +" delete one-time vars and functions +delfunction HGInstallDocumentation +delfunction HGFlexiMkdir +delfunction 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 +Credits: Bob Hiestand +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 + 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 + 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. + + hga HGAdd + hgn HGAnnotate + hgc HGCommit + hgd HGDiff + hgg HGGotoOriginal + hgG HGGotoOriginal! + hgl HGLog + hgr HGReview + hgs HGStatus + hgu HGUpdate + hgv HGVimDiff + + *hgcommand-mappings-override* + + The default mappings can be overriden by user-provided instead by mapping + to 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 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 (' __'). 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 q: + bwipeout + 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 hgN :vshhgn:vertical res 40 + \ggdddd:set scb:set nowraplgg:set scb + \:set nowrap +< + + 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! Debug(str) "{{{ + if exists('g:patchreview_debug') + Pecho 'DEBUG: ' . a:str + endif +endfunction +command! -nargs=+ -complete=expression Debug call s:Debug() +"}}} + +function! 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! 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() +"}}} + +function! 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! 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! 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 " + 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() +"}}} + +function! 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! _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! 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 () + +" :DiffReview +command! -nargs=0 DiffReview call s:DiffReview() +"}}} + +" Development "{{{ +if exists('g:patchreview_debug') + " Tests + function! 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! 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() + command! -nargs=+ -complete=file PRTestNative call s:PRExtractTestNative() +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 @@ + + + + Mercurial for Windows + + + + + +

Mercurial for Windows

+ +

Welcome to Mercurial for Windows!

+ +

+ Mercurial is a command-line application. You must run it from + the Windows command prompt (or if you're hard core, a MinGW shell). +

+ +

+ Note: the standard MinGW + msys startup script uses rxvt which has problems setting up + standard input and output. Running bash directly works + correctly. +

+ +

+ For documentation, please visit the Mercurial web site. + You can also download a free book, Mercurial: The Definitive + Guide. +

+ +

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

+ +

Testing Mercurial after you've installed it

+ +

+ The easiest way to check that Mercurial is installed properly is + to just type the following at the command prompt: +

+ +
+hg
+
+ +

+ This command should print a useful help message. If it does, + other Mercurial commands should work fine for you. +

+ +

Configuration notes

+

Default editor

+

+ The default editor for commit messages is 'notepad'. You can set + the EDITOR (or HGEDITOR) environment variable + to specify your preference or set it in mercurial.ini: +

+
+[ui]
+editor = whatever
+
+ +

Configuring a Merge program

+

+ 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. +

+ +

+ By default, Mercurial will use the merge program defined by the + HGMERGE environment variable, or uses the one defined + in the mercurial.ini file. (see MergeProgram + on the Mercurial Wiki for more information) +

+ +

Reporting problems

+ +

+ Before you report any problems, please consult the Mercurial web site + and see if your question is already in our list of Frequently + Answered Questions (the "FAQ"). +

+ +

+ If you cannot find an answer to your question, please feel free + to send mail to the Mercurial mailing list, at mercurial@selenic.com. + Remember, the more useful information you include in your + report, the easier it will be for us to help you! +

+ +

+ If you are IRC-savvy, that's usually the fastest way to get + help. Go to #mercurial on irc.freenode.net. +

+ +

Author and copyright information

+ +

+ Mercurial was written by Matt + Mackall, and is maintained by Matt and a team of volunteers. +

+ +

+ The Windows installer was written by Bryan O'Sullivan. +

+ +

+ Mercurial is Copyright 2005-2012 Matt Mackall and others. See + the Contributors.txt file for a list of contributors. +

+ +

+ Mercurial 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 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. +

+ + 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 +# +# 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 Binary files /dev/null and b/contrib/win32/mercurial.ico 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 +; username = Joe User + +; 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 Binary files /dev/null and b/contrib/wix/COPYING.rtf 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 + +[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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + VersionNT >= 501 + + + + + + + + mercurial@selenic.com + http://mercurial.selenic.com/wiki/ + http://mercurial.selenic.com/about/ + http://mercurial.selenic.com/downloads/ + http://mercurial.selenic.com/wiki/Support + hgIcon.ico + + + amus + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 +# Copyright (C) 2006-10 Brendan Cully +# +# 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 as commit message]:message:' + '(-e --edit -m --message --logfile -l)'{-l+,--logfile}'[read the commit message from ]: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 as commit message]:text:' \ + '(--logfile -l)'{-l+,--logfile}'[read commit message from ]: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 as commit message]:text:' \ + '(--logfile -l)'{-l+,--logfile}'[read commit message from ]: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 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 as collapse commit message]:text:' \ + '(--edit -e)'{-e,--edit}'[invoke editor on commit messages]' \ + '(--logfile -l)'{-l+,--logfile}'[read collapse commit message from ]: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 "$@" -- cgit v1.2.1