--- a/contrib/vim/hgcommand.vim
+++ b/contrib/vim/hgcommand.vim
@@ -29,10 +29,10 @@
" completes. This allows various actions to only be taken by functions after
" system initialization.
-if exists("loaded_hgcommand")
+if exists("g:loaded_hgcommand")
finish
endif
-let loaded_hgcommand = 1
+let g:loaded_hgcommand = 1
" store 'compatible' settings
let s:save_cpo = &cpo
@@ -45,7 +45,7 @@ function! s:HGCleanupOnFailure(err)
echohl WarningMsg
echomsg s:script_name . ":" a:err "Plugin not loaded"
echohl None
- let loaded_hgcommand = "no"
+ let g:loaded_hgcommand = "no"
unlet s:save_cpo s:script_name
endfunction
@@ -372,7 +372,7 @@ function! s:HGGetStatusVars(revisionVar,
let revision="ADDED"
else
" The file is tracked, we can try to get is revision number
- let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " parents -b "
+ let hgCommand = <SID>HGGetOption("HGCommandHGExec", "hg") . " parents "
let statustext=system(hgCommand)
if(v:shell_error)
return ""
@@ -566,11 +566,11 @@ function! s:HGInstallDocumentation(full_
1
" Delete from first line to a line starts with
" === START_DOC
- silent 1,/^=\{3,}\s\+START_DOC\C/ d
+ 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/,$ d
+ 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:
@@ -1048,7 +1048,7 @@ com! HGDisableBufferSetup call HGDisable
com! HGEnableBufferSetup call HGEnableBufferSetup()
" Allow reloading hgcommand.vim
-com! HGReload unlet! loaded_hgcommand | runtime plugin/hgcommand.vim
+com! HGReload unlet! g:loaded_hgcommand | runtime plugin/hgcommand.vim
" Section: Plugin command mappings {{{1
nnoremap <silent> <Plug>HGAdd :HGAdd<CR>
@@ -1200,7 +1200,7 @@ delfunction <SID>HGFlexiMkdir
delfunction <SID>HGCleanupOnFailure
unlet s:script_version s:script_name
-let loaded_hgcommand=2
+let g:loaded_hgcommand=2
silent do HGCommand User HGPluginFinish
let &cpo = s:save_cpo
--- a/contrib/zsh_completion
+++ b/contrib/zsh_completion
@@ -5,6 +5,7 @@
# instance)
#
# Copyright (C) 2005 Steve Borho
+# Copyright (C) 2006 Brendan Cully <brendan@kublai.com>
#
# 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
@@ -13,499 +14,663 @@
#
local curcontext="$curcontext" state line
-typeset -A opt_args
-local repos newFiles addedFiles includeExclude commitMessage
-local ridx _hgroot
+typeset -A _hg_cmd_globals
+
+_hg() {
+ local cmd
+ integer i=2
+ _hg_cmd_globals=()
+
+ while (( i < $#words ))
+ do
+ case "$words[$i]" in
+ -R|--repository|--cwd|--config)
+ # pass along arguments to hg completer
+ _hg_cmd_globals+="$words[$i]"
+ _hg_cmd_globals+="$words[$i+1]"
+ (( i += 2 ))
+ continue
+ ;;
+ -R*)
+ _hg_cmd_globals+="$words[$i]"
+ (( i++ ))
+ 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
+
+ if (( $+functions[_hg_cmd_${cmd}] ))
+ then
+ curcontext="${curcontext%:*:*}:hg-${cmd}:"
+ _hg_cmd_${cmd}
+ return
+ fi
+}
-# FIXME: why isn't opt_args available?
-[[ -d .hg ]] && _hgroot="$PWD"
-ridx=$words[(i)-R]
-(( $ridx < $#words )) && _hgroot="${words[$ridx+1]}"
+_hg_get_commands() {
+ typeset -ga _hg_cmd_list
+ typeset -gA _hg_alias_list
+ local hline cmd cmdalias
+ _call_program help hg --verbose help | while read -A hline
+ do
+ cmd="$hline[1]"
+ case $cmd in
+ *:)
+ cmd=${cmd%:}
+ _hg_cmd_list+=($cmd)
+ ;;
+ *,)
+ cmd=${cmd%,}
+ _hg_cmd_list+=($cmd)
+ integer i=2
+ while (( i <= $#hline ))
+ do
+ cmdalias=${hline[$i]%(:|,)}
+ _hg_cmd_list+=($cmdalias)
+ _hg_alias_list+=($cmdalias $cmd)
+ (( i++ ))
+ done
+ ;;
+ esac
+ done
+}
+
+_hg_commands() {
+ (( $#_hg_cmd_list )) || _hg_get_commands
+ _describe -t commands 'mercurial command' _hg_cmd_list
+}
+
+_hg_revrange() {
+ compset -P 1 '*:'
+ _hg_tags "$@"
+}
+
+_hg_tags() {
+ typeset -a tags
+ local tag rev
-# hg dispatch (borrowed from _cvs)
-(( $+functions[_hg_cmd] )) ||
-_hg_cmd () {
- _call_program hg hg -R "$_hgroot" "$@"
+ _hg_cmd tags 2> /dev/null | while read tag rev
+ do
+ tags+=($tag)
+ done
+ (( $#tags )) && _describe -t tags 'tags' tags
+}
+
+_hg_status() {
+ status_files=(${(ps:\0:)"$(_hg_cmd status -0n$1 . 2>/dev/null)"})
+}
+
+_hg_unknown() {
+ typeset -a status_files
+ _hg_status u
+ (( $#status_files )) && _describe -t files 'unknown files' status_files
+}
+
+_hg_missing() {
+ typeset -a status_files
+ _hg_status d
+ (( $#status_files )) && _describe -t files 'missing files' status_files
+}
+
+_hg_addremove() {
+ _alternative 'files:unknown files:_hg_unknown' \
+ 'files:missing files:_hg_missing'
+}
+
+_hg_urls() {
+ if compset -P bundle://
+ then
+ _files
+ elif [[ -prefix *: ]]
+ then
+ _urls
+ else
+ local expl
+ compset -S '[^:]*'
+ _wanted url-schemas expl 'URL schema' compadd -S '' - \
+ http:// https:// ssh:// bundle://
+ fi
}
-(( $+functions[_hg_state] )) ||
-_hg_state () {
- case "$state" in
- (tags)
- compadd $(_hg_cmd tags 2> /dev/null |
- sed -e 's/[0-9]*:[a-f0-9]*$//; s/ *$//')
- ;;
- (qapplied)
- compadd $(_hg_cmd qapplied)
- ;;
- (qunapplied)
- compadd $(_hg_cmd qunapplied)
- ;;
- esac
+_hg_paths() {
+ typeset -a paths pnames
+ _hg_cmd paths 2> /dev/null | 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 schema:_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]'
+ '(--quiet -q)'{-q,--quiet}'[suppress output]'
+ '(--help -h)'{-h,--help}'[display help and exit]'
+ '--debug[debug mode]'
+ '--debugger[start debugger]'
+ '--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[don't include dates in diff headers]")
+
+_hg_dryrun_opts=(
+ '(--dry-run -n)'{-n,--dry-run}'[do not perform actions, just print output]')
+
+_hg_style_opts=(
+ '--style[display using template map file]:'
+ '--template[display with template]:')
+
+_hg_commit_opts=(
+ '(-m --message -l --logfile --edit -e)'{-e,--edit}'[edit commit message]'
+ '(-e --edit -l --logfile --message -m)'{-m+,--message}'[use <text> as commit message]:message:'
+ '(-e --edit -m --message --logfile -l)'{-l+,--logfile}'[read the commit message from <file>]:log file:_files')
+
+_hg_remote_opts=(
+ '(--ssh -e)'{-e+,--ssh}'[specify ssh command to use]:'
+ '--remotecmd[specify hg command to run on the remote side]:')
+
+_hg_cmd() {
+ _call_program hg hg "$_hg_cmd_globals[@]" "$@"
+}
+
+_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_tags' \
+ '(--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:_files -W $(_hg_cmd root)'
+}
+
+_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_tags' \
+ '(--type -t)'{-t+,--type}'[type of distribution to create]:archive type:(files tar tbz2 tgz uzip zip)' \
+ '*:destination:_files'
+}
+
+_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_tags' \
+ ':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_tags' \
+ '*:file:_files -W $(_hg_cmd root)'
+}
+
+_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:_files -/'
+}
+
+_hg_cmd_commit() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '(--addremove -A)'{-A,--addremove}'[mark new/missing files as added/removed before committing]'
+ '(--message -m)'{-m+,--message}'[use <text> as commit message]:text:' \
+ '(--logfile -l)'{-l+,--logfile}'[read commit message from <file>]:log file:_file -g \*.txt' \
+ '(--date -d)'{-d+,--date}'[record datecode as commit date]:date code:' \
+ '(--user -u)'{-u+,--user}'[record user as commiter]:user:' \
+ '*:file:_files -W $(_hg_cmd root)'
+}
+
+_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:_files -W $(_hg_cmd root)'
+}
+
+_hg_cmd_diff() {
+ _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:_files -W $(_hg_cmd root)'
+}
+
+_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_tags'
+}
+
+_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]' \
+ '*:search pattern:_files -W $(_hg_cmd root)'
+}
+
+_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_tags'
+}
+
+_hg_cmd_help() {
+ _arguments -s -w : $_hg_global_opts \
+ '*:mercurial command:_hg_commands'
}
-(( $+_hg_commands )) ||
-_hg_commands=($(hg -v help | sed -e '1,/^list of commands:/d' \
- -e '/^global options:/,$d' -e '/^ [^ ]/!d; s/[,:].*//g;'))
-
-# A lot of commands have these arguments
-includeExclude=(
- '*-I-[include names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/'
- '*--include-[include names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/'
- '*-X-[exclude names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/'
- '*--exclude-[exclude names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/')
-
-styleOpts=(
- '--style[display using template map file]:'
- '--template[display with template]:')
-
-commitMessage=(
- '(-m --message -l --logfile --edit)-e[edit commit message]'
- '(-m --message -l --logfile -e)--edit[edit commit message]'
- '(-e --edit -l --logfile --message)-m[use <text> as commit message]:message:'
- '(-e --edit -l --logfile -m)--message[use <text> as commit message]:message:'
- '(-e --edit -m --message --logfile)-l[read the commit message from <file>]:log file:_files'
- '(-e --edit -m --message -l)--logfile[read the commit message from <file>]:log file:_files')
+_hg_cmd_import() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--strip -p)'{-p+,--strip}'[directory strip option for patch (default: 1)]:count:' \
+ '(--message -m)'{-m+,--message}'[use <text> as commit message]:text:' \
+ '(--force -f)'{-f,--force}'[skip check for outstanding uncommitted changes]' \
+ '*:patch:_files'
+}
-if [[ $service == "hg" ]]; then
- _arguments -C -A "-*" \
- '(--repository)-R[repository root directory]:root:_files -/' \
- '(-R)--repository[repository root directory]:root:_files -/' \
- '--cwd[change working directory]:new working directory:_files -/' \
- '(--noninteractive)-y[do not prompt, assume yes for any required answers]' \
- '(-y)--noninteractive[do not prompt, assume yes for any required answers]' \
- '(--verbose)-v[enable additional output]' \
- '(-v)--verbose[enable additional output]' \
- '(--quiet)-q[suppress output]' \
- '(-q)--quiet[suppress output]' \
- '(--help)-h[display help and exit]' \
- '(-h)--help[display help and exit]' \
- '--debug[debug mode]' \
- '--debugger[start debugger]' \
- '--traceback[print traceback on exception]' \
- '--time[time how long the command takes]' \
- '--profile[profile]' \
- '--version[output version information and exit]' \
- '*::command:->subcmd' && return 0
+_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]' \
+ '(--newest-first -n)'{-n,--newest-first}'[show newest record first]' \
+ '--bundle[file to store the bundles into]:bundle file:_files' \
+ ':source:_hg_remote'
+}
- if (( CURRENT == 1 )); then
- _wanted commands expl 'hg command' compadd -a _hg_commands
- return
- fi
- service="$words[1]"
- curcontext="${curcontext%:*}=$service:"
-fi
+_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_tags' \
+ '(--print0 -0)'{-0,--print0}'[end filenames with NUL, for use with xargs]' \
+ '(--fullpath -f)'{-f,--fullpath}'[print complete paths]' \
+ '*:search pattern:_files -W $(_hg_cmd root)'
+}
-case $service in
- (add)
- newFiles=(${(ps:\0:)"$(_hg_cmd status -0un .)"})
- _arguments $includeExclude \
- '*:file:->unknown'
- _wanted files expl 'unknown files' compadd -a newFiles
- ;;
+_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_tags' \
+ '*:files:_files -W $(_hg_cmd root)'
+}
- (addremove)
- _arguments $includeExclude \
- '*:directories:_files -/' # assume they want to add/remove a dir
- ;;
-
- (forget)
- addedFiles=(${(ps:\0:)"$(_hg_cmd status -0an .)"})
- _arguments $includeExclude \
- '*:file:->added'
- _wanted files expl 'newly added files' compadd -a addedFiles
- ;;
-
- (remove|rm)
- _arguments $includeExclude \
- '*:file:_files'
- ;;
+_hg_cmd_manifest() {
+ _arguments -s -w : $_hg_global_opts \
+ ':revision:_hg_tags'
+}
- (copy|cp)
- _arguments $includeExclude \
- '(--after)-A[record a copy that has already occurred]' \
- '(-A)--after[record a copy that has already occurred]' \
- '(--force)-f[forcibly copy over an existing managed file]' \
- '(-f)--force[forcibly copy over an existing managed file]' \
- '(--parents)-p[append source path to dest]' \
- '(-p)--parents[append source path to dest]' \
- '*:files:_files'
- ;;
+_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'
+}
- (rename|mv)
- if (( CURRENT == 2 )); then
- _arguments $includeExclude \
- '(--after)-A[record a rename that has already occurred]' \
- '(-A)--after[record a rename that has already occurred]' \
- '(--force)-f[replace destination if it exists]' \
- '(-F)--force[replace destination if it exists]' \
- '(--parents)-p[append source path to dest]' \
- '(-p)--parents[append source path to dest]' \
- '*:files:_files'
- else
- _arguments '*:destination:_files'
- fi
- ;;
+_hg_cmd_parents() {
+ _arguments -s -w : $_hg_global_opts $_hg_style_opts \
+ '(--rev -r)'{-r+,--rev}'[show parents of the specified rev]:revision:_hg_tags' \
+ ':revision:_hg_tags'
+}
- (diff)
- _arguments $includeExclude \
- '*-r[revision]:revision:->tags' \
- '*--rev[revision]:revision:->tags' \
- '(--text)-a[treat all files as text]' \
- '(-a)--text[treat all files as text]' \
- '*:file:_files'
- ;;
+_hg_cmd_paths() {
+ _arguments -s -w : $_hg_global_opts \
+ ':path:_hg_paths'
+}
+
+_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]' \
+ ':source:_hg_remote'
+}
- (status|st)
- _arguments $includeExclude \
- '(--no-status)-n[hide status prefix]' \
- '(-n)--no-status[hide status prefix]' \
- '(--print0)-0[end filenames with NUL, for use with xargs]' \
- '(-0)--print0[end filenames with NUL, for use with xargs]' \
- '(--modified)-m[show only modified files]' \
- '(-m)--modified[show only modified files]' \
- '(--added)-a[show only added files]' \
- '(-a)--added[show only added files]' \
- '(--removed)-r[show only removed files]' \
- '(-r)--removed[show only removed files]' \
- '(--unknown)-u[show only unknown files]' \
- '(-u)--unknown[show only unknown files]' \
- '*:search pattern, then files:_files'
- ;;
+_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]' \
+ ':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:_files -W $(_hg_cmd root)'
+}
- (revert)
- addedFiles=(${(ps:\0:)"$(_hg_cmd status -0amrn .)"})
- _arguments \
- '(--rev)-r[revision to revert to]:revision:->tags' \
- '(-r)--rev[revision to revert to]:revision:->tags' \
- '(--nonrecursive)-n[do not recurse into subdirectories]' \
- '(-n)--nonrecursive[do not recurse into subdirectories]' \
- '*:file:->modified'
- _wanted files expl 'mofified files' compadd -a addedFiles
- ;;
+_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:_files -W $(_hg_cmd root)'
+}
- (commit|ci)
- addedFiles=(${(ps:\0:)"$(_hg_cmd status -0amrn .)"})
- _arguments $includeExclude \
- '(--addremove)-A[run addremove during commit]' \
- '(-A)--addremove[run addremove during commit]' \
- '(--message)-m[use <txt> as commit message]:string:' \
- '(-m)--message[use <txt> as commit message]:string:' \
- '(--logfile)-l[read commit message from <file>]:.log file:_file -g \*.txt' \
- '(-l)--logfile[read commit message from <file>]:.log file:_file -g \*.txt' \
- '(--date)-d[record datecode as commit date]:date code:' \
- '(-d)--date[record datecode as commit date]:date code:' \
- '(--user)-u[record user as commiter]:user:' \
- '(-u)--user[record user as commiter]:user:' \
- '*:file:->modified'
- _wanted files expl 'mofified files' compadd -a addedFiles
- ;;
-
- (cat)
- _arguments $includeExclude \
- '(--output)-o[print output to file with formatted name]:filespec:' \
- '(-o)--output[print output to file with formatted name]:filespec:' \
- '(--rev)-r[revision]:revision:->tags' \
- '(-r)--rev[revision]:revision:->tags' \
- '*:file:_files'
- ;;
+_hg_cmd_revert() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+ '(--all -a :)'{-a,--all}'[revert all changes when no arguments given]' \
+ '(--rev -r)'{-r+,--rev}'[revision to revert to]:revision:_hg_tags' \
+ '--no-backup[do not save backup copies of files]' \
+ '*:file:_files -W $(_hg_cmd root)'
+}
- (annotate)
- _arguments $includeExclude \
- '(--rev)-r[annotate the specified revision]:revision:->tags' \
- '(-r)--rev[annotate the specified revision]:revision:->tags' \
- '(--text)-a[treat all files as text]' \
- '(-a)--text[treat all files as text]' \
- '(--user)-u[list the author]' \
- '(-u)--user[list the author]' \
- '(--changeset)-c[list the changeset]' \
- '(-c)--changeset[list the changeset]' \
- '(--number)-n[list the revision number (default)]' \
- '(-n)--number[list the revision number (default)]' \
- '*:files:_files'
- ;;
+_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]'
+}
- (grep)
- _arguments $includeExclude \
- '*-r[search in given revision range]:revision:->tags' \
- '*--rev[search in given revision range]:revision:->tags' \
- '--all[print all revisions with matches]' \
- '(-print0)-0[end filenames with NUL, for use with xargs]' \
- '(-0)--print0[end filenames with NUL, for use with xargs]' \
- '(--ignore-case)-i[ignore case when matching]' \
- '(-i)--ignore-case[ignore case when matching]' \
- '(--files-with-matches)-l[print names of files and revs that match]' \
- '(-l)--files-with-matches[print names of files and revs that match]' \
- '(--line-number)-n[print matching line numbers]' \
- '(-n)--line-number[print matching line numbers]' \
- '(--user)-u[print user who committed change]' \
- '(-u)--user[print user who committed change]' \
- '*:search pattern:'
- ;;
+_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_tags' \
+ '*:files:_files'
+}
- (locate)
- _arguments $includeExclude \
- '(--rev)-r[search repository as it stood at revision]:revision:->tags' \
- '(-r)--rev[search repository as it stood at revision]:revision:->tags' \
- '(--print0)-0[end filenames with NUL, for use with xargs]' \
- '(-0)--print0[end filenames with NUL, for use with xargs]' \
- '(--fullpath)-f[print complete paths]' \
- '(-f)--fullpath[print complete paths]' \
- '*:search pattern:'
- ;;
+_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_tags' \
+ ':tag name:'
+}
- (log|history)
- _arguments $includeExclude $styleOpts \
- '*-r[show the specified revision or range]:revision:->tags' \
- '*--rev[show the specified revision or range]:revision:->tags' \
- '(--no-merges -M --only-merges)-m[show only merge revisions]' \
- '(--no-merges -M -m)--only-merges[show only merge revisions]' \
- '(--only-merges -m --no-merges)-M[do not show merge revisions]' \
- '(--only-merges -m -M)--no-merges[do not show merge revisions]' \
- '(--keyword)-k[search for a keyword]:keyword:' \
- '(-k)--keyword[search for a keyword]:keyword:' \
- '(--branch)-b[show branches]' \
- '(-b)--branch[show branches]' \
- '(--patch)-p[show patch]' \
- '(-p)--patch[show patch]' \
- '*:file:_files'
- ;;
+_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'
+}
- (update|checkout|co)
- _arguments \
- '(--branch)-b[checkout the head of a specific branch]' \
- '(-b)--branch[checkout the head of a specific branch]' \
- '(-C --clean --merge)-m[allow merging of branches]' \
- '(-C --clean -m)--merge[allow merging of branches]' \
- '(-m --merge --clean)-C[overwrite locally modified files]' \
- '(-m --merge -C)--clean[overwrite locally modified files]' \
- '*:revision or tag:->tags'
- ;;
+_hg_cmd_update() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--clean -C)'{-C,--clean}'[overwrite locally modified files]' \
+ '(--force -f)'{-f,--force}'[force a merge with outstanding changes]' \
+ ':revision:_hg_tags'
+}
- (tag)
- _arguments \
- '(--local)-l[make the tag local]' \
- '(-l)--local[make the tag local]' \
- '(--message)-m[message for tag commit log entry]:string:' \
- '(-m)--message[message for tag commit log entry]:string:' \
- '(--date)-d[record datecode as commit date]:date code:' \
- '(-d)--date[record datecode as commit date]:date code:' \
- '(--user)-u[record user as commiter]:user:' \
- '(-u)--user[record user as commiter]:user:' \
- '*:name, then revision:->tags'
- ;;
+# HGK
+_hg_cmd_view() {
+ _arguments -s -w : $_hg_global_opts \
+ '(--limit -l)'{-l+,--limit}'[limit number of changes displayed]:' \
+ ':revision range:_hg_tags'
+}
- (clone)
- if (( CURRENT == 2 )); then
- repos=( $(_hg_cmd paths | sed -e 's/^.*= //') )
- _arguments \
- '(--no-update)-U[do not update the new working directory]' \
- '(-U)--no-update[do not update the new working directory]' \
- '(--ssh)-e[specify ssh command to use]:string:' \
- '(-e)--ssh[specify ssh command to use]:string:' \
- '--pull[use pull protocol to copy metadata]' \
- '--remotecmd[specify hg command to run on the remote side]:remote hg:' \
- '*:local repo:_files -/'
- _wanted source expl 'source repository' compadd -a repos
- elif (( CURRENT == 3 )); then
- _arguments '*:dest repo:_files -/'
- fi
- ;;
+# MQ
+_hg_qseries() {
+ typeset -a patches
+ patches=($(_hg_cmd qseries 2>/dev/null))
+ (( $#patches )) && _describe -t hg-patches 'patches' patches
+}
- (rawcommit)
- _arguments \
- '(--parent)-p[parent revision]:revision:->tags' \
- '(-p)--parent[parent revision]:revision:->tags' \
- '(--date)-d[record datecode as commit date]:date code:' \
- '(-d)--date[record datecode as commit date]:date code:' \
- '(--user)-u[record user as commiter]:user:' \
- '(-u)--user[record user as commiter]:user:' \
- '(--message)-m[use <txt> as commit message]:string:' \
- '(-m)--message[use <txt> as commit message]:string:' \
- '(--logfile)-l[read commit message from <file>]:.log file:_file -g \*.txt' \
- '(-l)--logfile[read commit message from <file>]:.log file:_file -g \*.txt' \
- '(--files)-F[file list]:file list:_files' \
- '(-F)--files[file list]:file list:_files' \
- '*:files to commit:_files'
- ;;
+_hg_qapplied() {
+ typeset -a patches
+ patches=($(_hg_cmd qapplied 2>/dev/null))
+ if (( $#patches ))
+ then
+ patches+=(qbase qtip)
+ _describe -t hg-applied-patches 'applied patches' patches
+ fi
+}
+
+_hg_qunapplied() {
+ typeset -a patches
+ patches=($(_hg_cmd qunapplied 2>/dev/null))
+ (( $#patches )) && _describe -t hg-unapplied-patches 'unapplied patches' patches
+}
- (bundle)
- if (( CURRENT == 2 )); then
- _arguments '*:changegroup file:_files -g \*.hg'
- elif (( CURRENT == 3 )); then
- _arguments '*:other repo:_files -/'
- fi
- ;;
+_hg_qguards() {
+ typeset -a guards
+ local guard
+ compset -P "+|-"
+ _hg_cmd qselect -s 2>/dev/null | while read guard
+ do
+ guards+=(${guard#(+|-)})
+ done
+ (( $#guards )) && _describe -t hg-guards 'guards' guards
+}
- (unbundle)
- _arguments '*:changegroup .hg file:_files -g \*.hg'
- ;;
+_hg_qseries_opts=(
+ '(--summary -s)'{-s,--summary}'[print first line of patch header]')
- (incoming)
- _arguments $styleOpts \
- '(--patch)-p[show patch]' \
- '(-p)--patch[show patch]' \
- '(--no-merges)-M[do not show merge revisions]' \
- '(-M)--no-merges[do not show merge revisions]' \
- '(--newest-first)-n[show newest record first]' \
- '(-n)--newest-first[show newest record first]' \
- '*:mercurial repository:_files -/'
- ;;
+_hg_cmd_qapplied() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
- (import|patch)
- _arguments \
- '(--strip)-p[directory strip option for patch (default: 1)]:count:' \
- '(-p)--strip[directory strip option for patch (default: 1)]:count:' \
- '(--force)-f[skip check for outstanding uncommitted changes]' \
- '(-f)--force[skip check for outstanding uncommitted changes]' \
- '(--base)-b[base directory to read patches from]:file:_files -W $(_hg_cmd root) -/' \
- '(-b)--base[base directory to read patches from]:file:_files -W $(_hg_cmd root) -/' \
- '*:patch file:_files'
- ;;
+_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_qunapplied'
+}
+
+_hg_cmd_qdiff() {
+ _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+ '*:pattern:_files -W $(_hg_cmd root)'
+}
- (pull)
- repos=( $(_hg_cmd paths | sed -e 's/^.*= //') )
- _arguments \
- '(--update)-u[update working directory to tip after pull]' \
- '(-u)--update[update working directory to tip after pull]' \
- '(--ssh)-e[specify ssh command to use]:ssh command:' \
- '(-e)--ssh[specify ssh command to use]:ssh command:' \
- '--remotecmd[specify hg command to run on the remote side]:remote hg:' \
- '*:local repo:_files -/'
- _wanted source expl 'source repository' compadd -a repos
- ;;
+_hg_cmd_qfold() {
+ _arguments -s -w : $_hg_global_opts $_h_commit_opts \
+ '(--keep,-k)'{-k,--keep}'[keep folded patch files]' \
+ '*:unapplied patch:_hg_qunapplied'
+}
- (outgoing)
- _arguments $styleOpts \
- '(--patch)-p[show patch]' \
- '(-p)--patch[show patch]' \
- '(--no-merges)-M[do not show merge revisions]' \
- '(-M)--no-merges[do not show merge revisions]' \
- '(--newest-first)-n[show newest record first]' \
- '(-n)--newest-first[show newest record first]' \
- '*:local repo:_files -/'
- _wanted source expl 'source repository' compadd -a repos
- ;;
+_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'
+}
- (export)
- _arguments \
- '(--outout)-o[print output to file with formatted name]:filespec:' \
- '(-o)--output[print output to file with formatted name]:filespec:' \
- '(--text)-a[treat all files as text]' \
- '(-a)--text[treat all files as text]' \
- '*:revision:->revs'
- _wanted revs expl 'revision or tag' compadd -a tags
- ;;
+_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'
+}
- (push)
- repos=( $(_hg_cmd paths | sed -e 's/^.*= //') )
- _arguments \
- '(--force)-f[force push]' \
- '(-f)--force[force push]' \
- '(--ssh)-e[specify ssh command to use]:ssh command:' \
- '(-e)--ssh[specify ssh command to use]:ssh command:' \
- '--remotecmd[specify hg command to run on the remote side]:remote hg:' \
- '*:local repo:_files -/'
- _wanted source expl 'source repository' compadd -a repos
- ;;
+_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
+}
- (serve)
- _arguments \
- '(--accesslog)-A[name of access log file]:log file:_files' \
- '(-A)--accesslog[name of access log file]:log file:_files' \
- '(--errorlog)-E[name of error log file]:log file:_files' \
- '(-E)--errorlog[name of error log file]:log file:_files' \
- '(--port)-p[listen port]:listen port:' \
- '(-p)--port[listen port]:listen port:' \
- '(--address)-a[interface address]:interface address:' \
- '(-a)--address[interface address]:interface address:' \
- '(--name)-n[name to show in web pages]:repository name:' \
- '(-n)--name[name to show in web pages]:repository name:' \
- '(--templates)-t[web template directory]:template dir:_files -/' \
- '(-t)--templates[web template directory]:template dir:_files -/' \
- '--style[web template style]:style' \
- '--stdio[for remote clients]' \
- '(--ipv6)-6[use IPv6 in addition to IPv4]' \
- '(-6)--ipv6[use IPv6 in addition to IPv4]'
- ;;
+_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'
+}
- (help)
- _wanted commands expl 'hg command' compadd -a _hg_commands
- ;;
+_hg_cmd_qprev() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
- (heads)
- _arguments $styleOpts \
- '(--branches)-b[find branch info]' \
- '(-b)--branches[find branch info]'
- ;;
-
- (paths)
- _arguments '*:symbolic name:(default default-push)'
- ;;
-
- (init)
- _arguments '*:new repo directory:_files -/'
- ;;
+_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]' \
+ ':patch:_hg_qunapplied'
+}
- (manifest)
- _arguments '*:revision:->tags'
- ;;
-
- (par*)
- _arguments $styleOpts \
- '(--rev 1)-r[show parents of the specified rev]:revision:->tags' \
- '(-r 1)--rev[show parents of the specified rev]:revision:->tags' \
- '::revision:->tags'
- ;;
-
- (identify|recover|root|undo|verify|version|ct|tags)
- # no arguments for these commands
- ;;
+_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:_files -W $(_hg_cmd root)'
+}
- # HGK
- (vi*)
- _arguments \
- '(--limit)-l[limit number of changes displayed]:' \
- '(-l)--limit[limit number of changes displayed]:' \
- '::revision range:->tags'
- ;;
+_hg_cmd_qrename() {
+ _arguments -s -w : $_hg_global_opts \
+ ':patch:_hg_qseries' \
+ ':destination:'
+}
- # MQ commands
- (qdel*|qrm|qrem*)
- _arguments \
- {-k,--keep}'[keep patch file]' \
- {-r,--rev}'[revision]:applied patch:->qapplied' \
- '*:unapplied patches:->qunapplied'
- ;;
+_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'
+}
- (qnew)
- _arguments $commitMessage \
- {-f,--force}'[import uncommitted changes into patch]' \
- ':patch name:'
- ;;
+_hg_cmd_qseries() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts \
+ '(--missing -m)'{-m,--missing}'[print patches not in series]'
+}
- (qpo*)
- _arguments \
- (1){-a,--all}'[pop all patches]' \
- {-f,--force}'[forget any local changes]' \
- ':applied patch:->qapplied'
- ;;
+_hg_cmd_qunapplied() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
- (qpu*)
- _arguments \
- (1){-a,--all}'[apply all patches]' \
- {-f,--force}'[apply if the patch has rejects]' \
- ':unapplied patch:->qunapplied'
- ;;
- (qref*)
- _arguments $commitMessage $includeExclude \
- {-g,--git}'[use git extended diff format]' \
- {-s,--short}'[short refresh]'
- ;;
+_hg_cmd_qtop() {
+ _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+}
- (*)
- _message "unknown hg command completion: $service"
- ;;
-esac
+_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_tags'
+}
-_hg_state
+_hg "$@"
--- a/doc/hg.1.txt
+++ b/doc/hg.1.txt
@@ -8,7 +8,7 @@ hg - Mercurial source code management sy
SYNOPSIS
--------
-'hg' [-v -d -q -y] <command> [command options] [files]
+'hg' [global option]... <command> [command/global option]... [argument]...
DESCRIPTION
-----------
--- a/doc/hgrc.5.txt
+++ b/doc/hgrc.5.txt
@@ -50,6 +50,9 @@ installed.
particular repository. This file is not version-controlled, and
will not get transferred during a "clone" operation. Options in
this file override options in all other configuration files.
+ On Unix, most of this file will be ignored if it doesn't belong
+ to a trusted user or to a trusted group. See the documentation
+ for the trusted section below for more details.
SYNTAX
------
@@ -364,6 +367,22 @@ server::
6Mbps), uncompressed streaming is slower, because of the extra
data transfer overhead. Default is False.
+trusted::
+ For security reasons, Mercurial will not use the settings in
+ the .hg/hgrc file from a repository if it doesn't belong to a
+ trusted user or to a trusted group. The main exception is the
+ web interface, which automatically uses some safe settings, since
+ it's common to serve repositories from different users.
+
+ This section specifies what users and groups are trusted. The
+ current user is always trusted. To trust everybody, list a user
+ or a group with name "*".
+
+ users;;
+ Comma-separated list of trusted users.
+ groups;;
+ Comma-separated list of trusted groups.
+
ui::
User interface controls.
debug;;
--- a/hgext/mq.py
+++ b/hgext/mq.py
@@ -1337,13 +1337,20 @@ class queue:
for filename in files:
if existing:
+ if filename == '-':
+ raise util.Abort(_('-e is incompatible with import from -'))
if not patchname:
patchname = filename
if not os.path.isfile(self.join(patchname)):
raise util.Abort(_("patch %s does not exist") % patchname)
else:
try:
- text = file(filename).read()
+ if filename == '-':
+ if not patchname:
+ raise util.Abort(_('need --name to import a patch from -'))
+ text = sys.stdin.read()
+ else:
+ text = file(filename).read()
except IOError:
raise util.Abort(_("unable to read %s") % patchname)
if not patchname:
--- a/mercurial/bdiff.c
+++ b/mercurial/bdiff.c
@@ -13,11 +13,7 @@
#include <stdlib.h>
#include <string.h>
-#ifdef __hpux
-#define inline
-#endif
-
-#ifdef __SUNPRO_C
+#if defined __hpux || defined __SUNPRO_C || defined _AIX
# define inline
#endif
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1196,7 +1196,7 @@ def debugcheckstate(ui, repo):
error = _(".hg/dirstate inconsistent with current parent's manifest")
raise util.Abort(error)
-def showconfig(ui, repo, *values):
+def showconfig(ui, repo, *values, **opts):
"""show combined config settings from all hgrc files
With no args, print names and values of all config items.
@@ -1207,10 +1207,11 @@ def showconfig(ui, repo, *values):
With multiple args, print names and values of all config items
with matching section names."""
+ untrusted = bool(opts.get('untrusted'))
if values:
if len([v for v in values if '.' in v]) > 1:
raise util.Abort(_('only one config item permitted'))
- for section, name, value in ui.walkconfig():
+ for section, name, value in ui.walkconfig(untrusted=untrusted):
sectname = section + '.' + name
if values:
for v in values:
@@ -2091,10 +2092,17 @@ def pull(ui, repo, source="default", **o
Valid URLs are of the form:
- local/filesystem/path
+ local/filesystem/path (or file://local/filesystem/path)
http://[user@]host[:port]/[path]
https://[user@]host[:port]/[path]
ssh://[user@]host[:port]/[path]
+ static-http://host[:port]/[path]
+
+ Paths in the local filesystem can either point to Mercurial
+ repositories or to bundle files (as created by 'hg bundle' or
+ 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
+ allows access to a Mercurial repository where you simply use a web
+ server to publish the .hg directory as static content.
Some notes about using SSH with Mercurial:
- SSH requires an accessible shell account on the destination machine
@@ -2142,14 +2150,16 @@ def push(ui, repo, dest=None, **opts):
Valid URLs are of the form:
- local/filesystem/path
+ local/filesystem/path (or file://local/filesystem/path)
ssh://[user@]host[:port]/[path]
+ http://[user@]host[:port]/[path]
+ https://[user@]host[:port]/[path]
Look at the help text for the pull command for important details
about ssh:// URLs.
- Pushing to http:// and https:// URLs is possible, too, if this
- feature is enabled on the remote Mercurial server.
+ Pushing to http:// and https:// URLs is only possible, if this
+ feature is explicitly enabled on the remote Mercurial server.
"""
dest = ui.expandpath(dest or 'default-push', dest or 'default')
setremoteconfig(ui, opts)
@@ -3074,7 +3084,10 @@ table = {
_('hg revert [-r REV] [NAME]...')),
"rollback": (rollback, [], _('hg rollback')),
"root": (root, [], _('hg root')),
- "showconfig|debugconfig": (showconfig, [], _('showconfig [NAME]...')),
+ "showconfig|debugconfig":
+ (showconfig,
+ [('u', 'untrusted', None, _('show untrusted configuration options'))],
+ _('showconfig [-u] [NAME]...')),
"^serve":
(serve,
[('A', 'accesslog', '', _('name of access log file to write to')),
@@ -3486,6 +3499,15 @@ def dispatch(args):
u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
else:
u.warn(_("abort: %s\n") % inst.strerror)
+ except util.UnexpectedOutput, inst:
+ u.warn(_("abort: %s") % inst[0])
+ if not isinstance(inst[1], basestring):
+ u.warn(" %r\n" % (inst[1],))
+ elif not inst[1]:
+ u.warn(_(" empty string\n"))
+ else:
+ u.warn("\n%r%s\n" %
+ (inst[1][:400], len(inst[1]) > 400 and '...' or ''))
except util.Abort, inst:
u.warn(_("abort: %s\n") % inst)
except TypeError, inst:
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -69,7 +69,7 @@ def revnavgen(pos, pagelen, limit, nodef
class hgweb(object):
def __init__(self, repo, name=None):
if type(repo) == type(""):
- self.repo = hg.repository(ui.ui(), repo)
+ self.repo = hg.repository(ui.ui(report_untrusted=False), repo)
else:
self.repo = repo
@@ -77,24 +77,41 @@ class hgweb(object):
self.reponame = name
self.archives = 'zip', 'gz', 'bz2'
self.stripecount = 1
- self.templatepath = self.repo.ui.config("web", "templates",
- templater.templatepath())
+ # a repo owner may set web.templates in .hg/hgrc to get any file
+ # readable by the user running the CGI script
+ self.templatepath = self.config("web", "templates",
+ templater.templatepath(),
+ untrusted=False)
+
+ # The CGI scripts are often run by a user different from the repo owner.
+ # Trust the settings from the .hg/hgrc files by default.
+ def config(self, section, name, default=None, untrusted=True):
+ return self.repo.ui.config(section, name, default,
+ untrusted=untrusted)
+
+ def configbool(self, section, name, default=False, untrusted=True):
+ return self.repo.ui.configbool(section, name, default,
+ untrusted=untrusted)
+
+ def configlist(self, section, name, default=None, untrusted=True):
+ return self.repo.ui.configlist(section, name, default,
+ untrusted=untrusted)
def refresh(self):
mtime = get_mtime(self.repo.root)
if mtime != self.mtime:
self.mtime = mtime
self.repo = hg.repository(self.repo.ui, self.repo.root)
- self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
- self.stripecount = int(self.repo.ui.config("web", "stripes", 1))
- self.maxshortchanges = int(self.repo.ui.config("web", "maxshortchanges", 60))
- self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
- self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
+ self.maxchanges = int(self.config("web", "maxchanges", 10))
+ self.stripecount = int(self.config("web", "stripes", 1))
+ self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
+ self.maxfiles = int(self.config("web", "maxfiles", 10))
+ self.allowpull = self.configbool("web", "allowpull", True)
def archivelist(self, nodeid):
- allowed = self.repo.ui.configlist("web", "allow_archive")
+ allowed = self.configlist("web", "allow_archive")
for i, spec in self.archive_specs.iteritems():
- if i in allowed or self.repo.ui.configbool("web", "allow" + i):
+ if i in allowed or self.configbool("web", "allow" + i):
yield {"type" : i, "extension" : spec[2], "node" : nodeid}
def listfilediffs(self, files, changeset):
@@ -169,7 +186,7 @@ class hgweb(object):
modified, added, removed = map(lambda x: filterfiles(files, x),
(modified, added, removed))
- diffopts = patch.diffopts(self.repo.ui)
+ diffopts = patch.diffopts(self.repo.ui, untrusted=True)
for f in modified:
to = r.file(f).read(mmap1[f])
tn = r.file(f).read(mmap2[f])
@@ -571,10 +588,10 @@ class hgweb(object):
end = min(count, start + self.maxchanges)
yield self.t("summary",
- desc = self.repo.ui.config("web", "description", "unknown"),
- owner = (self.repo.ui.config("ui", "username") or # preferred
- self.repo.ui.config("web", "contact") or # deprecated
- self.repo.ui.config("web", "author", "unknown")), # also
+ desc = self.config("web", "description", "unknown"),
+ owner = (self.config("ui", "username") or # preferred
+ self.config("web", "contact") or # deprecated
+ self.config("web", "author", "unknown")), # also
lastchange = cl.read(cl.tip())[2],
tags = tagentries,
heads = heads,
@@ -650,7 +667,7 @@ class hgweb(object):
yield self.t("footer", **map)
def motd(**map):
- yield self.repo.ui.config("web", "motd", "")
+ yield self.config("web", "motd", "")
def expand_form(form):
shortcuts = {
@@ -748,7 +765,7 @@ class hgweb(object):
fields = []
if req.form.has_key('style'):
style = req.form['style'][0]
- if style != self.repo.ui.config('web', 'style', ''):
+ if style != self.config('web', 'style', ''):
fields.append(('style', style))
separator = req.url[-1] == '?' and ';' or '?'
@@ -761,7 +778,7 @@ class hgweb(object):
expand_form(req.form)
rewrite_request(req)
- style = self.repo.ui.config("web", "style", "")
+ style = self.config("web", "style", "")
if req.form.has_key('style'):
style = req.form['style'][0]
mapfile = style_map(self.templatepath, style)
@@ -771,7 +788,7 @@ class hgweb(object):
urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port)
if not self.reponame:
- self.reponame = (self.repo.ui.config("web", "name")
+ self.reponame = (self.config("web", "name")
or req.env.get('REPO_NAME')
or req.url.strip('/') or self.repo.root)
@@ -985,9 +1002,9 @@ class hgweb(object):
def do_archive(self, req):
changeset = self.repo.lookup(req.form['node'][0])
type_ = req.form['type'][0]
- allowed = self.repo.ui.configlist("web", "allow_archive")
+ allowed = self.configlist("web", "allow_archive")
if (type_ in self.archives and (type_ in allowed or
- self.repo.ui.configbool("web", "allow" + type_, False))):
+ self.configbool("web", "allow" + type_, False))):
self.archive(req, changeset, type_)
return
@@ -995,15 +1012,17 @@ class hgweb(object):
def do_static(self, req):
fname = req.form['file'][0]
- static = self.repo.ui.config("web", "static",
- os.path.join(self.templatepath,
- "static"))
+ # a repo owner may set web.static in .hg/hgrc to get any file
+ # readable by the user running the CGI script
+ static = self.config("web", "static",
+ os.path.join(self.templatepath, "static"),
+ untrusted=False)
req.write(staticfile(static, fname, req)
or self.t("error", error="%r not found" % fname))
def do_capabilities(self, req):
caps = ['unbundle', 'lookup', 'changegroupsubset']
- if self.repo.ui.configbool('server', 'uncompressed'):
+ if self.configbool('server', 'uncompressed'):
caps.append('stream=%d' % self.repo.revlogversion)
resp = ' '.join(caps)
req.httphdr("application/mercurial-0.1", length=len(resp))
@@ -1016,11 +1035,11 @@ class hgweb(object):
user = req.env.get('REMOTE_USER')
- deny = self.repo.ui.configlist('web', 'deny_' + op)
+ deny = self.configlist('web', 'deny_' + op)
if deny and (not user or deny == ['*'] or user in deny):
return False
- allow = self.repo.ui.configlist('web', 'allow_' + op)
+ allow = self.configlist('web', 'allow_' + op)
return (allow and (allow == ['*'] or user in allow)) or default
def do_unbundle(self, req):
@@ -1036,7 +1055,7 @@ class hgweb(object):
# require ssl by default, auth info cannot be sniffed and
# replayed
- ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
+ ssl_req = self.configbool('web', 'push_ssl', True)
if ssl_req:
if not req.env.get('HTTPS'):
bail(_('ssl required\n'))
--- a/mercurial/hgweb/hgwebdir_mod.py
+++ b/mercurial/hgweb/hgwebdir_mod.py
@@ -87,9 +87,10 @@ class hgwebdir(object):
"url": url})
def archivelist(ui, nodeid, url):
- allowed = ui.configlist("web", "allow_archive")
+ allowed = ui.configlist("web", "allow_archive", untrusted=True)
for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
- if i[0] in allowed or ui.configbool("web", "allow" + i[0]):
+ if i[0] in allowed or ui.configbool("web", "allow" + i[0],
+ untrusted=True):
yield {"type" : i[0], "extension": i[1],
"node": nodeid, "url": url}
@@ -109,12 +110,13 @@ class hgwebdir(object):
rows = []
parity = 0
for name, path in self.repos:
- u = ui.ui()
+ u = ui.ui(report_untrusted=False)
try:
u.readconfig(os.path.join(path, '.hg', 'hgrc'))
except IOError:
pass
- get = u.config
+ def get(section, name, default=None):
+ return u.config(section, name, default, untrusted=True)
url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
.replace("//", "/")) + '/'
--- a/mercurial/httprepo.py
+++ b/mercurial/httprepo.py
@@ -218,9 +218,15 @@ class httprepository(remoterepository):
self.ui.debug(_("sending %s command\n") % cmd)
q = {"cmd": cmd}
q.update(args)
- qs = urllib.urlencode(q)
- cu = "%s?%s" % (self._url, qs)
+ qs = '?%s' % urllib.urlencode(q)
+ cu = "%s%s" % (self._url, qs)
try:
+ if data:
+ if isinstance(data, file):
+ # urllib2 needs string or buffer when using a proxy
+ data.seek(0)
+ data = data.read()
+ self.ui.debug(_("sending %d bytes\n") % len(data))
resp = urllib2.urlopen(urllib2.Request(cu, data, headers))
except urllib2.HTTPError, inst:
if inst.code == 401:
@@ -233,6 +239,8 @@ class httprepository(remoterepository):
except IndexError:
# this only happens with Python 2.3, later versions raise URLError
raise util.Abort(_('http error, possibly caused by proxy setting'))
+ # record the url we got redirected to
+ self._url = resp.geturl().rstrip(qs)
try:
proto = resp.getheader('content-type')
except AttributeError:
@@ -273,8 +281,7 @@ class httprepository(remoterepository):
try:
return map(bin, d[:-1].split(" "))
except:
- self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
- raise
+ raise util.UnexpectedOutput(_("unexpected response:"), d)
def branches(self, nodes):
n = " ".join(map(hex, nodes))
@@ -283,8 +290,7 @@ class httprepository(remoterepository):
br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
return br
except:
- self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
- raise
+ raise util.UnexpectedOutput(_("unexpected response:"), d)
def between(self, pairs):
n = "\n".join(["-".join(map(hex, p)) for p in pairs])
@@ -293,8 +299,7 @@ class httprepository(remoterepository):
p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
return p
except:
- self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n")
- raise
+ raise util.UnexpectedOutput(_("unexpected response:"), d)
def changegroup(self, nodes, kind):
n = " ".join(map(hex, nodes))
@@ -340,7 +345,7 @@ class httprepository(remoterepository):
try:
rfp = self.do_cmd(
'unbundle', data=fp,
- headers={'content-length': length,
+ headers={'content-length': str(length),
'content-type': 'application/octet-stream'},
heads=' '.join(map(hex, heads)))
try:
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1795,17 +1795,32 @@ class localrepository(repo.repository):
def stream_in(self, remote):
fp = remote.stream_out()
- resp = int(fp.readline())
+ l = fp.readline()
+ try:
+ resp = int(l)
+ except ValueError:
+ raise util.UnexpectedOutput(
+ _('Unexpected response from remote server:'), l)
if resp != 0:
raise util.Abort(_('operation forbidden by server'))
self.ui.status(_('streaming all changes\n'))
- total_files, total_bytes = map(int, fp.readline().split(' ', 1))
+ l = fp.readline()
+ try:
+ total_files, total_bytes = map(int, l.split(' ', 1))
+ except ValueError, TypeError:
+ raise util.UnexpectedOutput(
+ _('Unexpected response from remote server:'), l)
self.ui.status(_('%d files to transfer, %s of data\n') %
(total_files, util.bytecount(total_bytes)))
start = time.time()
for i in xrange(total_files):
- name, size = fp.readline().split('\0', 1)
- size = int(size)
+ l = fp.readline()
+ try:
+ name, size = l.split('\0', 1)
+ size = int(size)
+ except ValueError, TypeError:
+ raise util.UnexpectedOutput(
+ _('Unexpected response from remote server:'), l)
self.ui.debug('adding %s (%s)\n' % (name, util.bytecount(size)))
ofp = self.sopener(name, 'w')
for chunk in util.filechunkiter(fp, limit=size):
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -326,21 +326,18 @@ def patch(patchname, ui, strip=1, cwd=No
return fuzz
-def diffopts(ui, opts={}):
+def diffopts(ui, opts={}, untrusted=False):
+ def get(key, name=None):
+ return (opts.get(key) or
+ ui.configbool('diff', name or key, None, untrusted=untrusted))
return mdiff.diffopts(
text=opts.get('text'),
- git=(opts.get('git') or
- ui.configbool('diff', 'git', None)),
- nodates=(opts.get('nodates') or
- ui.configbool('diff', 'nodates', None)),
- showfunc=(opts.get('show_function') or
- ui.configbool('diff', 'showfunc', None)),
- ignorews=(opts.get('ignore_all_space') or
- ui.configbool('diff', 'ignorews', None)),
- ignorewsamount=(opts.get('ignore_space_change') or
- ui.configbool('diff', 'ignorewsamount', None)),
- ignoreblanklines=(opts.get('ignore_blank_lines') or
- ui.configbool('diff', 'ignoreblanklines', None)))
+ git=get('git'),
+ nodates=get('nodates'),
+ showfunc=get('show_function', 'showfunc'),
+ ignorews=get('ignore_all_space', 'ignorews'),
+ ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
+ ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
def updatedir(ui, repo, patches, wlock=None):
'''Update dirstate after patch application according to metadata'''
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -26,7 +26,8 @@ def updateconfig(source, dest, sections=
class ui(object):
def __init__(self, verbose=False, debug=False, quiet=False,
- interactive=True, traceback=False, parentui=None):
+ interactive=True, traceback=False, report_untrusted=True,
+ parentui=None):
self.overlay = None
self.header = []
self.prev_header = []
@@ -39,14 +40,23 @@ class ui(object):
self.debugflag = debug
self.interactive = interactive
self.traceback = traceback
+ self.report_untrusted = report_untrusted
+ self.trusted_users = {}
+ self.trusted_groups = {}
+ # if ucdata is not None, its keys must be a superset of cdata's
self.cdata = util.configparser()
+ self.ucdata = None
self.readconfig(util.rcpath())
self.updateopts(verbose, debug, quiet, interactive)
else:
# parentui may point to an ui object which is already a child
self.parentui = parentui.parentui or parentui
self.readhooks = self.parentui.readhooks[:]
+ self.trusted_users = parentui.trusted_users.copy()
+ self.trusted_groups = parentui.trusted_groups.copy()
self.cdata = dupconfig(self.parentui.cdata)
+ if self.parentui.ucdata:
+ self.ucdata = dupconfig(self.parentui.ucdata)
if self.parentui.overlay:
self.overlay = dupconfig(self.parentui.overlay)
@@ -82,14 +92,52 @@ class ui(object):
elif self.verbose and self.quiet:
self.quiet = self.verbose = False
+ def _is_trusted(self, fp, f, warn=True):
+ tusers = self.trusted_users
+ tgroups = self.trusted_groups
+ if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
+ st = util.fstat(fp)
+ user = util.username(st.st_uid)
+ group = util.groupname(st.st_gid)
+ if user not in tusers and group not in tgroups:
+ if warn and self.report_untrusted:
+ self.warn(_('Not trusting file %s from untrusted '
+ 'user %s, group %s\n') % (f, user, group))
+ return False
+ return True
+
def readconfig(self, fn, root=None):
if isinstance(fn, basestring):
fn = [fn]
for f in fn:
try:
- self.cdata.read(f)
+ fp = open(f)
+ except IOError:
+ continue
+ cdata = self.cdata
+ trusted = self._is_trusted(fp, f)
+ if not trusted:
+ if self.ucdata is None:
+ self.ucdata = dupconfig(self.cdata)
+ cdata = self.ucdata
+ elif self.ucdata is not None:
+ # use a separate configparser, so that we don't accidentally
+ # override ucdata settings later on.
+ cdata = util.configparser()
+
+ try:
+ cdata.readfp(fp, f)
except ConfigParser.ParsingError, inst:
- raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
+ msg = _("Failed to parse %s\n%s") % (f, inst)
+ if trusted:
+ raise util.Abort(msg)
+ self.warn(_("Ignored: %s\n") % msg)
+
+ if trusted:
+ if cdata != self.cdata:
+ updateconfig(cdata, self.cdata)
+ if self.ucdata is not None:
+ updateconfig(cdata, self.ucdata)
# override data from config files with data set with ui.setconfig
if self.overlay:
updateconfig(self.overlay, self.cdata)
@@ -103,7 +151,10 @@ class ui(object):
self.readhooks.append(hook)
def readsections(self, filename, *sections):
- "read filename and add only the specified sections to the config data"
+ """Read filename and add only the specified sections to the config data
+
+ The settings are added to the trusted config data.
+ """
if not sections:
return
@@ -119,6 +170,8 @@ class ui(object):
cdata.add_section(section)
updateconfig(cdata, self.cdata, sections)
+ if self.ucdata:
+ updateconfig(cdata, self.ucdata, sections)
def fixconfig(self, section=None, name=None, value=None, root=None):
# translate paths relative to root (or home) into absolute paths
@@ -126,7 +179,7 @@ class ui(object):
if root is None:
root = os.getcwd()
items = section and [(name, value)] or []
- for cdata in self.cdata, self.overlay:
+ for cdata in self.cdata, self.ucdata, self.overlay:
if not cdata: continue
if not items and cdata.has_section('paths'):
pathsitems = cdata.items('paths')
@@ -144,62 +197,111 @@ class ui(object):
if name is None or name == 'interactive':
self.interactive = self.configbool("ui", "interactive", True)
+ # update trust information
+ if section is None or section == 'trusted':
+ user = util.username()
+ if user is not None:
+ self.trusted_users[user] = 1
+ for user in self.configlist('trusted', 'users'):
+ self.trusted_users[user] = 1
+ for group in self.configlist('trusted', 'groups'):
+ self.trusted_groups[group] = 1
+
def setconfig(self, section, name, value):
if not self.overlay:
self.overlay = util.configparser()
- for cdata in (self.overlay, self.cdata):
+ for cdata in (self.overlay, self.cdata, self.ucdata):
+ if not cdata: continue
if not cdata.has_section(section):
cdata.add_section(section)
cdata.set(section, name, value)
self.fixconfig(section, name, value)
- def _config(self, section, name, default, funcname):
- if self.cdata.has_option(section, name):
+ def _get_cdata(self, untrusted):
+ if untrusted and self.ucdata:
+ return self.ucdata
+ return self.cdata
+
+ def _config(self, section, name, default, funcname, untrusted, abort):
+ cdata = self._get_cdata(untrusted)
+ if cdata.has_option(section, name):
try:
- func = getattr(self.cdata, funcname)
+ func = getattr(cdata, funcname)
return func(section, name)
except ConfigParser.InterpolationError, inst:
- raise util.Abort(_("Error in configuration section [%s] "
- "parameter '%s':\n%s")
- % (section, name, inst))
+ msg = _("Error in configuration section [%s] "
+ "parameter '%s':\n%s") % (section, name, inst)
+ if abort:
+ raise util.Abort(msg)
+ self.warn(_("Ignored: %s\n") % msg)
return default
- def config(self, section, name, default=None):
- return self._config(section, name, default, 'get')
+ def _configcommon(self, section, name, default, funcname, untrusted):
+ value = self._config(section, name, default, funcname,
+ untrusted, abort=True)
+ if self.debugflag and not untrusted and self.ucdata:
+ uvalue = self._config(section, name, None, funcname,
+ untrusted=True, abort=False)
+ if uvalue is not None and uvalue != value:
+ self.warn(_("Ignoring untrusted configuration option "
+ "%s.%s = %s\n") % (section, name, uvalue))
+ return value
- def configbool(self, section, name, default=False):
- return self._config(section, name, default, 'getboolean')
+ def config(self, section, name, default=None, untrusted=False):
+ return self._configcommon(section, name, default, 'get', untrusted)
- def configlist(self, section, name, default=None):
+ def configbool(self, section, name, default=False, untrusted=False):
+ return self._configcommon(section, name, default, 'getboolean',
+ untrusted)
+
+ def configlist(self, section, name, default=None, untrusted=False):
"""Return a list of comma/space separated strings"""
- result = self.config(section, name)
+ result = self.config(section, name, untrusted=untrusted)
if result is None:
result = default or []
if isinstance(result, basestring):
result = result.replace(",", " ").split()
return result
- def has_config(self, section):
+ def has_config(self, section, untrusted=False):
'''tell whether section exists in config.'''
- return self.cdata.has_section(section)
+ cdata = self._get_cdata(untrusted)
+ return cdata.has_section(section)
- def configitems(self, section):
+ def _configitems(self, section, untrusted, abort):
items = {}
- if self.cdata.has_section(section):
+ cdata = self._get_cdata(untrusted)
+ if cdata.has_section(section):
try:
- items.update(dict(self.cdata.items(section)))
+ items.update(dict(cdata.items(section)))
except ConfigParser.InterpolationError, inst:
- raise util.Abort(_("Error in configuration section [%s]:\n%s")
- % (section, inst))
+ msg = _("Error in configuration section [%s]:\n"
+ "%s") % (section, inst)
+ if abort:
+ raise util.Abort(msg)
+ self.warn(_("Ignored: %s\n") % msg)
+ return items
+
+ def configitems(self, section, untrusted=False):
+ items = self._configitems(section, untrusted=untrusted, abort=True)
+ if self.debugflag and not untrusted and self.ucdata:
+ uitems = self._configitems(section, untrusted=True, abort=False)
+ keys = uitems.keys()
+ keys.sort()
+ for k in keys:
+ if uitems[k] != items.get(k):
+ self.warn(_("Ignoring untrusted configuration option "
+ "%s.%s = %s\n") % (section, k, uitems[k]))
x = items.items()
x.sort()
return x
- def walkconfig(self):
- sections = self.cdata.sections()
+ def walkconfig(self, untrusted=False):
+ cdata = self._get_cdata(untrusted)
+ sections = cdata.sections()
sections.sort()
for section in sections:
- for name, value in self.configitems(section):
+ for name, value in self.configitems(section, untrusted):
yield section, name, value.replace('\n', '\\n')
def extensions(self):
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -138,6 +138,9 @@ def unique(g):
class Abort(Exception):
"""Raised if a command needs to print an error and exit."""
+class UnexpectedOutput(Abort):
+ """Raised to print an error with part of output and exit."""
+
def always(fn): return True
def never(fn): return False
@@ -521,6 +524,36 @@ def is_win_9x():
except AttributeError:
return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
+def username(uid=None):
+ """Return the name of the user with the given uid.
+
+ If uid is None, return the name of the current user."""
+ try:
+ import pwd
+ if uid is None:
+ uid = os.getuid()
+ try:
+ return pwd.getpwuid(uid)[0]
+ except KeyError:
+ return str(uid)
+ except ImportError:
+ return None
+
+def groupname(gid=None):
+ """Return the name of the group with the given gid.
+
+ If gid is None, return the name of the current group."""
+ try:
+ import grp
+ if gid is None:
+ gid = os.getgid()
+ try:
+ return grp.getgrgid(gid)[0]
+ except KeyError:
+ return str(gid)
+ except ImportError:
+ return None
+
# Platform specific variants
if os.name == 'nt':
demandload(globals(), "msvcrt")
new file mode 100644
--- /dev/null
+++ b/tests/test-trusted.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+# Since it's not easy to write a test that portably deals
+# with files from different users/groups, we cheat a bit by
+# monkey-patching some functions in the util module
+
+import os
+from mercurial import ui, util
+
+hgrc = os.environ['HGRCPATH']
+
+def testui(user='foo', group='bar', tusers=(), tgroups=(),
+ cuser='foo', cgroup='bar', debug=False, silent=False):
+ # user, group => owners of the file
+ # tusers, tgroups => trusted users/groups
+ # cuser, cgroup => user/group of the current process
+
+ # write a global hgrc with the list of trusted users/groups and
+ # some setting so that we can be sure it was read
+ f = open(hgrc, 'w')
+ f.write('[paths]\n')
+ f.write('global = /some/path\n\n')
+
+ if tusers or tgroups:
+ f.write('[trusted]\n')
+ if tusers:
+ f.write('users = %s\n' % ', '.join(tusers))
+ if tgroups:
+ f.write('groups = %s\n' % ', '.join(tgroups))
+ f.close()
+
+ # override the functions that give names to uids and gids
+ def username(uid=None):
+ if uid is None:
+ return cuser
+ return user
+ util.username = username
+
+ def groupname(gid=None):
+ if gid is None:
+ return 'bar'
+ return group
+ util.groupname = groupname
+
+ # try to read everything
+ #print '# File belongs to user %s, group %s' % (user, group)
+ #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
+ kind = ('different', 'same')
+ who = ('', 'user', 'group', 'user and the group')
+ trusted = who[(user in tusers) + 2*(group in tgroups)]
+ if trusted:
+ trusted = ', but we trust the ' + trusted
+ print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
+ trusted)
+
+ parentui = ui.ui()
+ parentui.updateopts(debug=debug)
+ u = ui.ui(parentui=parentui)
+ u.readconfig('.hg/hgrc')
+ if silent:
+ return u
+ print 'trusted'
+ for name, path in u.configitems('paths'):
+ print ' ', name, '=', path
+ print 'untrusted'
+ for name, path in u.configitems('paths', untrusted=True):
+ print '.',
+ u.config('paths', name) # warning with debug=True
+ print '.',
+ u.config('paths', name, untrusted=True) # no warnings
+ print name, '=', path
+ print
+
+ return u
+
+os.mkdir('repo')
+os.chdir('repo')
+os.mkdir('.hg')
+f = open('.hg/hgrc', 'w')
+f.write('[paths]\n')
+f.write('local = /another/path\n\n')
+f.write('interpolated = %(global)s%(local)s\n\n')
+f.close()
+
+#print '# Everything is run by user foo, group bar\n'
+
+# same user, same group
+testui()
+# same user, different group
+testui(group='def')
+# different user, same group
+testui(user='abc')
+# ... but we trust the group
+testui(user='abc', tgroups=['bar'])
+# different user, different group
+testui(user='abc', group='def')
+# ... but we trust the user
+testui(user='abc', group='def', tusers=['abc'])
+# ... but we trust the group
+testui(user='abc', group='def', tgroups=['def'])
+# ... but we trust the user and the group
+testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
+# ... but we trust all users
+print '# we trust all users'
+testui(user='abc', group='def', tusers=['*'])
+# ... but we trust all groups
+print '# we trust all groups'
+testui(user='abc', group='def', tgroups=['*'])
+# ... but we trust the whole universe
+print '# we trust all users and groups'
+testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
+# ... check that users and groups are in different namespaces
+print "# we don't get confused by users and groups with the same name"
+testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
+# ... lists of user names work
+print "# list of user names"
+testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
+ tgroups=['bar', 'baz', 'qux'])
+# ... lists of group names work
+print "# list of group names"
+testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
+ tgroups=['bar', 'def', 'baz', 'qux'])
+
+print "# Can't figure out the name of the user running this process"
+testui(user='abc', group='def', cuser=None)
+
+print "# prints debug warnings"
+u = testui(user='abc', group='def', cuser='foo', debug=True)
+
+print "# ui.readsections"
+filename = 'foobar'
+f = open(filename, 'w')
+f.write('[foobar]\n')
+f.write('baz = quux\n')
+f.close()
+u.readsections(filename, 'foobar')
+print u.config('foobar', 'baz')
+
+print
+print "# read trusted, untrusted, new ui, trusted"
+u = ui.ui()
+u.updateopts(debug=True)
+u.readconfig(filename)
+u2 = ui.ui(parentui=u)
+def username(uid=None):
+ return 'foo'
+util.username = username
+u2.readconfig('.hg/hgrc')
+print 'trusted:'
+print u2.config('foobar', 'baz')
+print u2.config('paths', 'interpolated')
+print 'untrusted:'
+print u2.config('foobar', 'baz', untrusted=True)
+print u2.config('paths', 'interpolated', untrusted=True)
+
+print
+print "# error handling"
+
+def assertraises(f, exc=util.Abort):
+ try:
+ f()
+ except exc, inst:
+ print 'raised', inst.__class__.__name__
+ else:
+ print 'no exception?!'
+
+print "# file doesn't exist"
+os.unlink('.hg/hgrc')
+assert not os.path.exists('.hg/hgrc')
+testui(debug=True, silent=True)
+testui(user='abc', group='def', debug=True, silent=True)
+
+print
+print "# parse error"
+f = open('.hg/hgrc', 'w')
+f.write('foo = bar')
+f.close()
+testui(user='abc', group='def', silent=True)
+assertraises(lambda: testui(debug=True, silent=True))
+
+print
+print "# interpolation error"
+f = open('.hg/hgrc', 'w')
+f.write('[foo]\n')
+f.write('bar = %(')
+f.close()
+u = testui(debug=True, silent=True)
+print '# regular config:'
+print ' trusted',
+assertraises(lambda: u.config('foo', 'bar'))
+print 'untrusted',
+assertraises(lambda: u.config('foo', 'bar', untrusted=True))
+
+u = testui(user='abc', group='def', debug=True, silent=True)
+print ' trusted ',
+print u.config('foo', 'bar')
+print 'untrusted',
+assertraises(lambda: u.config('foo', 'bar', untrusted=True))
+
+print '# configitems:'
+print ' trusted ',
+print u.configitems('foo')
+print 'untrusted',
+assertraises(lambda: u.configitems('foo', untrusted=True))
+
new file mode 100644
--- /dev/null
+++ b/tests/test-trusted.py.out
@@ -0,0 +1,212 @@
+# same user, same group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# same user, different group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# different user, same group
+Not trusting file .hg/hgrc from untrusted user abc, group bar
+trusted
+ global = /some/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# different user, same group, but we trust the group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# different user, different group
+Not trusting file .hg/hgrc from untrusted user abc, group def
+trusted
+ global = /some/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# different user, different group, but we trust the user
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# different user, different group, but we trust the group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# different user, different group, but we trust the user and the group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# we trust all users
+# different user, different group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# we trust all groups
+# different user, different group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# we trust all users and groups
+# different user, different group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# we don't get confused by users and groups with the same name
+# different user, different group
+Not trusting file .hg/hgrc from untrusted user abc, group def
+trusted
+ global = /some/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# list of user names
+# different user, different group, but we trust the user
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# list of group names
+# different user, different group, but we trust the group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# Can't figure out the name of the user running this process
+# different user, different group
+trusted
+ global = /some/path
+ interpolated = /some/path/another/path
+ local = /another/path
+untrusted
+. . global = /some/path
+. . interpolated = /some/path/another/path
+. . local = /another/path
+
+# prints debug warnings
+# different user, different group
+Not trusting file .hg/hgrc from untrusted user abc, group def
+trusted
+Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
+Ignoring untrusted configuration option paths.local = /another/path
+ global = /some/path
+untrusted
+. . global = /some/path
+.Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
+ . interpolated = /some/path/another/path
+.Ignoring untrusted configuration option paths.local = /another/path
+ . local = /another/path
+
+# ui.readsections
+quux
+
+# read trusted, untrusted, new ui, trusted
+Not trusting file foobar from untrusted user abc, group def
+trusted:
+Ignoring untrusted configuration option foobar.baz = quux
+None
+/some/path/another/path
+untrusted:
+quux
+/some/path/another/path
+
+# error handling
+# file doesn't exist
+# same user, same group
+# different user, different group
+
+# parse error
+# different user, different group
+Not trusting file .hg/hgrc from untrusted user abc, group def
+Ignored: Failed to parse .hg/hgrc
+File contains no section headers.
+file: .hg/hgrc, line: 1
+'foo = bar'
+# same user, same group
+raised Abort
+
+# interpolation error
+# same user, same group
+# regular config:
+ trusted raised Abort
+untrusted raised Abort
+# different user, different group
+Not trusting file .hg/hgrc from untrusted user abc, group def
+ trusted Ignored: Error in configuration section [foo] parameter 'bar':
+bad interpolation variable reference '%('
+ None
+untrusted raised Abort
+# configitems:
+ trusted Ignored: Error in configuration section [foo]:
+bad interpolation variable reference '%('
+ []
+untrusted raised Abort