# HG changeset patch # User Benoit Boissinot # Date 1157448827 -7200 # Node ID 713de12d9ce5438ad7b55eca306fae547dd1a7c2 # Parent 3dab573a43300a380d000a27047cb8960438968c# Parent 50e0392d51df7dbafe2b7572f0b25f3d70000935 merge crew with hg-churn diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -21,6 +21,7 @@ doc/*.[0-9].{x,ht}ml MANIFEST patches mercurial/__version__.py +.DS_Store syntax: regexp ^\.pc/ diff --git a/contrib/hgk b/contrib/hgk --- a/contrib/hgk +++ b/contrib/hgk @@ -100,7 +100,7 @@ to allow selection of commits to be disp set ids [string range $cmit 0 [expr {$j - 1}]] set ok 1 foreach id $ids { - if {![regexp {^[0-9a-f]{40}$} $id]} { + if {![regexp {^[0-9a-f]{12}$} $id]} { set ok 0 break } @@ -261,7 +261,7 @@ proc readotherrefs {base dname excl} { catch { set fd [open $f r] set line [read $fd 40] - if {[regexp {^[0-9a-f]{40}} $line id]} { + if {[regexp {^[0-9a-f]{12}} $line id]} { set name "$dname[file tail $f]" set otherrefids($name) $id lappend idotherrefs($id) $name @@ -1743,7 +1743,7 @@ proc readfindproc {} { } return } - if {![regexp {^[0-9a-f]{40}} $line id]} { + if {![regexp {^[0-9a-f]{12}} $line id]} { error_popup "Can't parse git-diff-tree output: $line" stopfindproc return @@ -1856,7 +1856,7 @@ proc readfilediffs {df} { } return } - if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} { + 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] @@ -2014,7 +2014,7 @@ proc appendwithlinks {text} { set start [$ctext index "end - 1c"] $ctext insert end $text $ctext insert end "\n" - set links [regexp -indices -all -inline {[0-9a-f]{40}} $text] + set links [regexp -indices -all -inline {[0-9a-f]{12}} $text] foreach l $links { set s [lindex $l 0] set e [lindex $l 1] diff --git a/contrib/mercurial.el b/contrib/mercurial.el --- a/contrib/mercurial.el +++ b/contrib/mercurial.el @@ -1,6 +1,6 @@ ;;; mercurial.el --- Emacs support for the Mercurial distributed SCM -;; Copyright (C) 2005 Bryan O'Sullivan +;; Copyright (C) 2005, 2006 Bryan O'Sullivan ;; Author: Bryan O'Sullivan @@ -289,7 +289,7 @@ XEmacs and GNU Emacs." (defsubst hg-chomp (str) "Strip trailing newlines from a string." - (hg-replace-in-string str "[\r\n]+\'" "")) + (hg-replace-in-string str "[\r\n]+\\'" "")) (defun hg-run-command (command &rest args) "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT). @@ -502,6 +502,43 @@ directory names from the file system. W (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))))))) + (defmacro hg-do-across-repo (path &rest body) (let ((root-name (gensym "root-")) (buf-name (gensym "buf-"))) @@ -548,13 +585,31 @@ current frame." '(("M " . modified) ("A " . added) ("R " . removed) + ("! " . deleted) ("? " . nil))))) (if state (cdr state) 'normal))))) -(defun hg-tip () - (split-string (hg-chomp (hg-run0 "-q" "tip")) ":")) +(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) + (if (equal (substring entry 1 2) " ") + (setq state (cdr (assoc (substring entry 0 2) + '(("M " . modified) + ("A " . added) + ("R " . removed) + ("! " . deleted) + ("C " . normal) + ("I " . ignored) + ("? " . nil)))) + name (substring entry 2)) + (setq name (substring entry 0 (search ": " entry :from-end t)))) + (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. @@ -589,7 +644,7 @@ being viewed." (put 'hg-view-output 'lisp-indent-function 1) -;;; Context save and restore across revert. +;;; Context save and restore across revert and other operations. (defun hg-position-context (pos) "Return information to help find the given position again." @@ -631,22 +686,28 @@ Always returns a valid, hopefully sane, ;;; 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." - (when (and (hg-root) (or force hg-update-modeline (not hg-mode))) - (let ((status (hg-file-status buffer-file-name))) - (setq hg-status status - hg-mode (and status (concat " Hg:" - (car (hg-tip)) - (cdr (assq status - '((normal . "") - (removed . "r") - (added . "a") - (modified . "m"))))))) - status))) + (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. @@ -724,6 +785,13 @@ code by typing `M-x find-library mercuri 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." @@ -732,9 +800,8 @@ With a prefix argument, prompt for the p (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 add" shows pathes relative NOT TO ROOT BUT TO REPOSITORY - (replace-regexp " \\.\\.." " " nil 0 (buffer-size)) - (goto-char 0) + (hg-fix-paths) + (goto-char (point-min)) (cd (hg-root path))) (when update (unless vc-make-backup-files @@ -820,8 +887,7 @@ hg-commit-allow-empty-file-list is nil, (let ((buf hg-prev-buffer)) (kill-buffer nil) (switch-to-buffer buf)) - (hg-do-across-repo root - (hg-mode-line))))) + (hg-update-mode-lines root)))) (defun hg-commit-mode () "Mode for describing a commit of changes to a Mercurial repository. @@ -973,8 +1039,8 @@ With a prefix argument, prompt for the p (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 - (replace-regexp " \\.\\.." " " nil 0 (buffer-size)) - (goto-char 0) + (hg-fix-paths) + (goto-char (point-min)) (cd (hg-root path))) (when update (with-current-buffer buf @@ -1148,6 +1214,21 @@ prompts for a path to check." 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. diff --git a/contrib/mq.el b/contrib/mq.el new file mode 100644 --- /dev/null +++ b/contrib/mq.el @@ -0,0 +1,281 @@ +;;; 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 version 2 of the GNU General Public License as +;; published by the Free Software Foundation. + +;; 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, write to the Free Software Foundation, Inc., 59 +;; Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +(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) + + +;;; Internal variables. + +(defvar mq-patch-history nil) + +(defvar mq-prev-buffer nil) +(make-variable-buffer-local 'mq-prev-buffer) +(put 'mq-prev-buffer 'permanent-local t) + + +;;; Global keymap. + +(defvar mq-global-map (make-sparse-keymap)) +(fset 'mq-global-map mq-global-map) +(global-set-key mq-global-prefix 'mq-global-map) +(define-key mq-global-map "." 'mq-push) +(define-key mq-global-map ">" 'mq-push-all) +(define-key mq-global-map "," 'mq-pop) +(define-key mq-global-map "<" 'mq-pop-all) +(define-key mq-global-map "r" 'mq-refresh) +(define-key mq-global-map "e" 'mq-refresh-edit) +(define-key mq-global-map "n" 'mq-next) +(define-key mq-global-map "p" 'mq-previous) +(define-key mq-global-map "t" 'mq-top) + + +;;; Refresh edit mode keymap. + +(defvar mq-edit-mode-map (make-sparse-keymap)) +(define-key mq-edit-mode-map "\C-c\C-c" 'mq-edit-finish) +(define-key mq-edit-mode-map "\C-c\C-k" 'mq-edit-kill) + + +;;; Helper functions. + +(defun mq-read-patch-name (&optional source prompt) + "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 current-prefix-arg + (completing-read (format "Patch%s: " (or prompt "")) + (map 'list 'cons patches 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)) + +(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"))) + (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 (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"))) + (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 () + "Refresh the topmost applied patch." + (interactive) + (let ((root (hg-root))) + (unless root + (error "Cannot refresh outside of a repository!")) + (mq-refresh-internal root))) + +(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) + (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-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.")) + + +(provide 'mq) + + +;;; Local Variables: +;;; prompt-to-byte-compile: nil +;;; end: diff --git a/doc/hg.1.txt b/doc/hg.1.txt --- a/doc/hg.1.txt +++ b/doc/hg.1.txt @@ -193,6 +193,10 @@ FILES global /etc/mercurial/hgrc configuration. See hgrc(5) for details of the contents and format of these files. +Some commands (e.g. revert) produce backup files ending in .orig, if +the .orig file already exists and is not tracked by Mercurial, it +will be overwritten. + BUGS ---- Probably lots, please post them to the mailing list (See Resources below) diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -50,6 +50,8 @@ 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, this file is only read if it belongs to a trusted user + or to a trusted group. SYNTAX ------ @@ -133,6 +135,21 @@ decode/encode:: # them to the working dir **.txt = tempfile: unix2dos -n INFILE OUTFILE +defaults:: + Use the [defaults] section to define command defaults, i.e. the + default options/arguments to pass to the specified commands. + + The following example makes 'hg log' run in verbose mode, and + 'hg status' show only the modified files, by default. + + [defaults] + log = -v + status = -m + + The actual commands, instead of their aliases, must be used when + defining command defaults. The command defaults will also be + applied to the aliases of the commands defined. + email:: Settings for extensions that send email messages. from;; @@ -349,6 +366,16 @@ server:: 6Mbps), uncompressed streaming is slower, because of the extra data transfer overhead. Default is False. +trusted:: + Mercurial will only read the .hg/hgrc file from a repository if + it belongs to a trusted user or to a trusted group. This section + specifies what users and groups are 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;; @@ -377,6 +404,9 @@ ui:: remote command to use for clone/push/pull operations. Default is 'hg'. ssh;; command to use for SSH connections. Default is 'ssh'. + strict;; + Require exact command names, instead of allowing unambiguous + abbreviations. True or False. Default is False. timeout;; The timeout used when a lock is held (in seconds), a negative value means no timeout. Default is 600. diff --git a/hgeditor b/hgeditor --- a/hgeditor +++ b/hgeditor @@ -41,13 +41,15 @@ HGTMP="${TMPDIR-/tmp}/hgeditor.$RANDOM.$ cat "$1" > "$HGTMP/msg" -CHECKSUM=`md5sum "$HGTMP/msg"` +MD5=$(which md5sum 2>/dev/null) || \ + MD5=$(which md5 2>/dev/null) +[ -x "${MD5}" ] && CHECKSUM=`${MD5} "$HGTMP/msg"` if [ -s "$HGTMP/diff" ]; then $EDITOR "$HGTMP/msg" "$HGTMP/diff" || exit $? else $EDITOR "$HGTMP/msg" || exit $? fi -echo "$CHECKSUM" | md5sum -c >/dev/null 2>&1 && exit 13 +[ -x "${MD5}" ] && (echo "$CHECKSUM" | ${MD5} -c >/dev/null 2>&1 && exit 13) mv "$HGTMP/msg" "$1" diff --git a/hgext/hgk.py b/hgext/hgk.py --- a/hgext/hgk.py +++ b/hgext/hgk.py @@ -46,18 +46,20 @@ def dodiff(fp, ui, repo, node1, node2, f if f in mmap: to = repo.file(f).read(mmap[f]) tn = read(f) + opts = mdiff.diffopts() + opts.text = text fp.write("diff --git a/%s b/%s\n" % (f, f)) - fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, opts=opts)) for f in added: to = None tn = read(f) fp.write("diff --git /dev/null b/%s\n" % (f)) - fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, opts=opts)) for f in removed: to = repo.file(f).read(mmap[f]) tn = None fp.write("diff --git a/%s /dev/null\n" % (f)) - fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, opts=opts)) def difftree(ui, repo, node1=None, node2=None, **opts): """diff trees from two commits""" @@ -85,12 +87,17 @@ def difftree(ui, repo, node1=None, node2 for f in modified: # TODO get file permissions - print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]), - hg.hex(mmap2[f]), f, f) + print ":100664 100664 %s %s M\t%s\t%s" % (hg.short(mmap[f]), + hg.short(mmap2[f]), + f, f) for f in added: - print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f) + print ":000000 100664 %s %s N\t%s\t%s" % (empty, + hg.short(mmap2[f]), + f, f) for f in removed: - print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f) + print ":100664 000000 %s %s D\t%s\t%s" % (hg.short(mmap[f]), + empty, + f, f) ## while True: @@ -122,11 +129,11 @@ def difftree(ui, repo, node1=None, node2 def catcommit(repo, n, prefix, changes=None): nlprefix = '\n' + prefix; (p1, p2) = repo.changelog.parents(n) - (h, h1, h2) = map(hg.hex, (n, p1, p2)) + (h, h1, h2) = map(hg.short, (n, p1, p2)) (i1, i2) = map(repo.changelog.rev, (p1, p2)) if not changes: changes = repo.changelog.read(n) - print "tree %s" % (hg.hex(changes[0])) + print "tree %s" % (hg.short(changes[0])) if i1 != -1: print "parent %s" % (h1) if i2 != -1: print "parent %s" % (h2) date_ar = changes[2] @@ -152,7 +159,7 @@ def base(ui, repo, node1, node2): node1 = repo.lookup(node1) node2 = repo.lookup(node2) n = repo.changelog.ancestor(node1, node2) - print hg.hex(n) + print hg.short(n) def catfile(ui, repo, type=None, r=None, **opts): """cat a specific revision""" @@ -274,17 +281,17 @@ def revtree(args, repo, full="tree", max if parents: pp = repo.changelog.parents(n) if pp[0] != hg.nullid: - parentstr += " " + hg.hex(pp[0]) + parentstr += " " + hg.short(pp[0]) if pp[1] != hg.nullid: - parentstr += " " + hg.hex(pp[1]) + parentstr += " " + hg.short(pp[1]) if not full: - print hg.hex(n) + parentstr + print hg.short(n) + parentstr elif full is "commit": - print hg.hex(n) + parentstr + print hg.short(n) + parentstr catcommit(repo, n, ' ', changes) else: (p1, p2) = repo.changelog.parents(n) - (h, h1, h2) = map(hg.hex, (n, p1, p2)) + (h, h1, h2) = map(hg.short, (n, p1, p2)) (i1, i2) = map(repo.changelog.rev, (p1, p2)) date = changes[2][0] diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -31,8 +31,9 @@ refresh contents of top applied patch from mercurial.demandload import * from mercurial.i18n import gettext as _ +from mercurial import commands demandload(globals(), "os sys re struct traceback errno bz2") -demandload(globals(), "mercurial:cmdutil,commands,hg,patch,revlog,ui,util") +demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,ui,util") commands.norepo += " qclone qversion" @@ -915,16 +916,16 @@ class queue: def refresh(self, repo, pats=None, **opts): if len(self.applied) == 0: self.ui.write("No patches applied\n") - return + return 1 wlock = repo.wlock() self.check_toppatch(repo) - (top, patch) = (self.applied[-1].rev, self.applied[-1].name) + (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name) top = revlog.bin(top) cparents = repo.changelog.parents(top) patchparent = self.qparents(repo, top) - message, comments, user, date, patchfound = self.readheaders(patch) + message, comments, user, date, patchfound = self.readheaders(patchfn) - patchf = self.opener(patch, "w") + patchf = self.opener(patchfn, "w") msg = opts.get('msg', '').rstrip() if msg: if comments: @@ -994,8 +995,9 @@ class queue: r = list(util.unique(dd)) a = list(util.unique(aa)) filelist = filter(matchfn, util.unique(m + r + a)) - self.printdiff(repo, patchparent, files=filelist, - changes=(m, a, r, [], u), fp=patchf) + patch.diff(repo, patchparent, files=filelist, match=matchfn, + fp=patchf, changes=(m, a, r, [], u), + opts=self.diffopts()) patchf.close() changes = repo.changelog.read(tip) @@ -1018,7 +1020,7 @@ class queue: if not msg: if not message: - message = "patch queue: %s\n" % patch + message = "patch queue: %s\n" % patchfn else: message = "\n".join(message) else: @@ -1026,7 +1028,7 @@ class queue: self.strip(repo, top, update=False, backup='strip', wlock=wlock) n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock) - self.applied[-1] = statusentry(revlog.hex(n), patch) + self.applied[-1] = statusentry(revlog.hex(n), patchfn) self.applied_dirty = 1 else: self.printdiff(repo, patchparent, fp=patchf) @@ -1237,11 +1239,13 @@ class queue: self.ui.write(p + '\n') else: self.ui.write("No patches applied\n") + return 1 def next(self, repo): end = self.series_end() if end == len(self.series): self.ui.write("All patches applied\n") + return 1 else: p = self.series[end] if self.ui.verbose: @@ -1254,8 +1258,10 @@ class queue: self.ui.write(p + '\n') elif len(self.applied) == 1: self.ui.write("Only one patch applied\n") + return 1 else: self.ui.write("No patches applied\n") + return 1 def qimport(self, repo, files, patch=None, existing=None, force=None): if len(files) > 1 and patch: @@ -1402,18 +1408,15 @@ def series(ui, repo, **opts): def top(ui, repo, **opts): """print the name of the current patch""" - repo.mq.top(repo) - return 0 + return repo.mq.top(repo) def next(ui, repo, **opts): """print the name of the next patch""" - repo.mq.next(repo) - return 0 + return repo.mq.next(repo) def prev(ui, repo, **opts): """print the name of the previous patch""" - repo.mq.prev(repo) - return 0 + return repo.mq.prev(repo) def new(ui, repo, patch, **opts): """create a new patch @@ -1449,9 +1452,9 @@ def refresh(ui, repo, *pats, **opts): patch = q.applied[-1].name (message, comment, user, date, hasdiff) = q.readheaders(patch) message = ui.edit('\n'.join(message), user or ui.username()) - q.refresh(repo, pats, msg=message, **opts) + ret = q.refresh(repo, pats, msg=message, **opts) q.save_dirty() - return 0 + return ret def diff(ui, repo, *pats, **opts): """diff of the current patch""" @@ -1571,7 +1574,7 @@ def header(ui, repo, patch=None): else: if not q.applied: ui.write('No patches applied\n') - return + return 1 patch = q.lookup('qtip') message = repo.mq.readheaders(patch)[0] diff --git a/hgext/notify.py b/hgext/notify.py --- a/hgext/notify.py +++ b/hgext/notify.py @@ -238,7 +238,7 @@ class notifier(object): return fp = templater.stringio() prev = self.repo.changelog.parents(node)[0] - patch.diff(self.repo, fp, prev, ref) + patch.diff(self.repo, prev, ref, fp=fp) difflines = fp.getvalue().splitlines(1) if maxdiff > 0 and len(difflines) > maxdiff: self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') % diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py --- a/hgext/patchbomb.py +++ b/hgext/patchbomb.py @@ -140,7 +140,9 @@ def patchbomb(ui, repo, *revs, **opts): if line.startswith('#'): if line.startswith('# Node ID'): node = line.split()[-1] continue - if line.startswith('diff -r'): break + if (line.startswith('diff -r') + or line.startswith('diff --git')): + break desc.append(line) if not node: raise ValueError @@ -205,7 +207,8 @@ def patchbomb(ui, repo, *revs, **opts): commands.export(ui, repo, *revs, **{'output': exportee(patches), 'switch_parent': False, - 'text': None}) + 'text': None, + 'git': opts.get('git')}) jumbo = [] msgs = [] @@ -322,6 +325,7 @@ cmdtable = { ('', 'bcc', [], 'email addresses of blind copy recipients'), ('c', 'cc', [], 'email addresses of copy recipients'), ('d', 'diffstat', None, 'add diffstat output to messages'), + ('g', 'git', None, _('use git extended diff format')), ('f', 'from', '', 'email address of sender'), ('', 'plain', None, 'omit hg patch header'), ('n', 'test', None, 'print messages that would be sent'), diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -505,7 +505,7 @@ def help_(ui, name=None, with_version=Fa if with_version: show_version(ui) ui.write('\n') - aliases, i = findcmd(name) + aliases, i = findcmd(ui, name) # synopsis ui.write("%s\n\n" % i[2]) @@ -818,6 +818,7 @@ def backout(ui, repo, rev, **opts): parent = p1 hg.clean(repo, node, show_stats=False) revert_opts = opts.copy() + revert_opts['all'] = True revert_opts['rev'] = hex(parent) revert(ui, repo, **revert_opts) commit_opts = opts.copy() @@ -1151,7 +1152,7 @@ def debugcomplete(ui, cmd='', **opts): options = [] otables = [globalopts] if cmd: - aliases, entry = findcmd(cmd) + aliases, entry = findcmd(ui, cmd) otables.append(entry[1]) for t in otables: for o in t: @@ -1161,7 +1162,7 @@ def debugcomplete(ui, cmd='', **opts): ui.write("%s\n" % "\n".join(options)) return - clist = findpossible(cmd).keys() + clist = findpossible(ui, cmd).keys() clist.sort() ui.write("%s\n" % "\n".join(clist)) @@ -2271,8 +2272,8 @@ def revert(ui, repo, *pats, **opts): Modified files are saved with a .orig suffix before reverting. To disable these backups, use --no-backup. - Using the -r option, revert the given files or directories to - their contents as of a specific revision. This can be helpful to"roll + Using the -r option, revert the given files or directories to their + contents as of a specific revision. This can be helpful to "roll back" some or all of a change that should not have been committed. Revert modifies the working directory. It does not commit any @@ -2286,8 +2287,13 @@ def revert(ui, repo, *pats, **opts): If names are given, all files matching the names are reverted. - If no arguments are given, all files in the repository are reverted. + If no arguments are given, no files are reverted. """ + + if not pats and not opts['all']: + raise util.Abort(_('no files or directories specified; ' + 'use --all to revert the whole repo')) + parent, p2 = repo.dirstate.parents() if opts['rev']: node = repo.lookup(opts['rev']) @@ -2881,6 +2887,7 @@ table = { (export, [('o', 'output', '', _('print output to file with formatted name')), ('a', 'text', None, _('treat all files as text')), + ('g', 'git', None, _('use git extended diff format')), ('', 'switch-parent', None, _('diff against the second parent'))], _('hg export [-a] [-o OUTFILESPEC] REV...')), "debugforget|forget": @@ -3046,7 +3053,8 @@ table = { _('hg rename [OPTION]... SOURCE... DEST')), "^revert": (revert, - [('r', 'rev', '', _('revision to revert to')), + [('a', 'all', None, _('revert all changes when no arguments given')), + ('r', 'rev', '', _('revision to revert to')), ('', 'no-backup', None, _('do not save backup copies of files')), ('I', 'include', [], _('include names matching given patterns')), ('X', 'exclude', [], _('exclude names matching given patterns')), @@ -3145,7 +3153,7 @@ norepo = ("clone init version help debug " debugindex debugindexdot") optionalrepo = ("paths serve debugconfig") -def findpossible(cmd): +def findpossible(ui, cmd): """ Return cmd -> (aliases, command table entry) for each matching command. @@ -3158,7 +3166,7 @@ def findpossible(cmd): found = None if cmd in aliases: found = cmd - else: + elif not ui.config("ui", "strict"): for a in aliases: if a.startswith(cmd): found = a @@ -3174,9 +3182,9 @@ def findpossible(cmd): return choice -def findcmd(cmd): +def findcmd(ui, cmd): """Return (aliases, command table entry) for command string.""" - choice = findpossible(cmd) + choice = findpossible(ui, cmd) if choice.has_key(cmd): return choice[cmd] @@ -3211,7 +3219,7 @@ def parse(ui, args): if args: cmd, args = args[0], args[1:] - aliases, i = findcmd(cmd) + aliases, i = findcmd(ui, cmd) cmd = aliases[0] defaults = ui.config("defaults", cmd) if defaults: @@ -3299,12 +3307,14 @@ def dispatch(args): if num: signal.signal(num, catchterm) try: - u = ui.ui(traceback='--traceback' in sys.argv[1:], - readhooks=[load_extensions]) + u = ui.ui(traceback='--traceback' in sys.argv[1:]) except util.Abort, inst: sys.stderr.write(_("abort: %s\n") % inst) return -1 + load_extensions(u) + u.addreadhook(load_extensions) + try: cmd, func, args, options, cmdoptions = parse(u, args) if options["time"]: diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -127,12 +127,7 @@ def clone(ui, source, dest=None, pull=Fa if self.dir_: self.rmtree(self.dir_, True) - dest_repo = None - try: - dest_repo = repository(ui, dest) - raise util.Abort(_("destination '%s' already exists." % dest)) - except RepoError: - dest_repo = repository(ui, dest, create=True) + dest_repo = repository(ui, dest, create=True) dest_path = None dir_cleanup = None diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -31,8 +31,16 @@ class localrepository(repo.repository): path = p self.path = os.path.join(path, ".hg") - if not create and not os.path.isdir(self.path): - raise repo.RepoError(_("repository %s not found") % path) + if not os.path.isdir(self.path): + if create: + if not os.path.exists(path): + os.mkdir(path) + os.mkdir(self.path) + os.mkdir(self.join("data")) + else: + raise repo.RepoError(_("repository %s not found") % path) + elif create: + raise repo.RepoError(_("repository %s already exists") % path) self.root = os.path.abspath(path) self.origroot = path @@ -75,12 +83,6 @@ class localrepository(repo.repository): self.decodepats = None self.transhandle = None - if create: - if not os.path.exists(path): - os.mkdir(path) - os.mkdir(self.path) - os.mkdir(self.join("data")) - self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root) def url(self): @@ -641,7 +643,11 @@ class localrepository(repo.repository): if node: fdict = dict.fromkeys(files) for fn in self.manifest.read(self.changelog.read(node)[0]): - fdict.pop(fn, None) + for ffn in fdict: + # match if the file is the exact name or a directory + if ffn == fn or fn.startswith("%s/" % ffn): + del fdict[ffn] + break if match(fn): yield 'm', fn for fn in fdict: diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py --- a/mercurial/mdiff.py +++ b/mercurial/mdiff.py @@ -50,6 +50,9 @@ class diffopts(object): defaultopts = diffopts() def unidiff(a, ad, b, bd, fn, r=None, opts=defaultopts): + def datetag(date): + return opts.git and '\n' or '\t%s\n' % date + if not a and not b: return "" epoch = util.datestr((0, 0)) @@ -58,19 +61,19 @@ def unidiff(a, ad, b, bd, fn, r=None, op elif not a: b = splitnewlines(b) if a is None: - l1 = "--- %s\t%s\n" % ("/dev/null", epoch) + l1 = '--- /dev/null%s' % datetag(epoch) else: - l1 = "--- %s\t%s\n" % ("a/" + fn, ad) - l2 = "+++ %s\t%s\n" % ("b/" + fn, bd) + l1 = "--- %s%s" % ("a/" + fn, datetag(ad)) + l2 = "+++ %s%s" % ("b/" + fn, datetag(bd)) l3 = "@@ -0,0 +1,%d @@\n" % len(b) l = [l1, l2, l3] + ["+" + e for e in b] elif not b: a = splitnewlines(a) - l1 = "--- %s\t%s\n" % ("a/" + fn, ad) + l1 = "--- %s%s" % ("a/" + fn, datetag(ad)) if b is None: - l2 = "+++ %s\t%s\n" % ("/dev/null", epoch) + l2 = '+++ /dev/null%s' % datetag(epoch) else: - l2 = "+++ %s\t%s\n" % ("b/" + fn, bd) + l2 = "+++ %s%s" % ("b/" + fn, datetag(bd)) l3 = "@@ -1,%d +0,0 @@\n" % len(a) l = [l1, l2, l3] + ["-" + e for e in a] else: @@ -79,8 +82,8 @@ def unidiff(a, ad, b, bd, fn, r=None, op l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, opts=opts)) if not l: return "" # difflib uses a space, rather than a tab - l[0] = "%s\t%s\n" % (l[0][:-2], ad) - l[1] = "%s\t%s\n" % (l[1][:-2], bd) + l[0] = "%s%s" % (l[0][:-2], datetag(ad)) + l[1] = "%s%s" % (l[1][:-2], datetag(bd)) for ln in xrange(len(l)): if l[ln][-1] != '\n': diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -8,7 +8,7 @@ from node import * from i18n import gettext as _ from demandload import * -demandload(globals(), "util os tempfile") +demandload(globals(), "errno util os tempfile") def fmerge(f, local, other, ancestor): """merge executable flags""" @@ -168,7 +168,8 @@ def update(repo, node, branchmerge=False repo.ui.debug(_(" updating permissions for %s\n") % f) util.set_exec(repo.wjoin(f), m2.execf(f)) else: - if fmerge(f, m1, m2, ma) != m1.execf(f): + mode = fmerge(f, m1, m2, ma) + if mode != m1.execf(f): repo.ui.debug(_(" updating permissions for %s\n") % f) util.set_exec(repo.wjoin(f), mode) diff --git a/mercurial/packagescan.py b/mercurial/packagescan.py --- a/mercurial/packagescan.py +++ b/mercurial/packagescan.py @@ -26,6 +26,7 @@ def demandload(scope, modules): foo import foo foo bar import foo, bar foo.bar import foo.bar + foo@bar import foo as bar foo:bar from foo import bar foo:bar,quux from foo import bar, quux foo.bar:quux from foo.bar import quux""" @@ -38,6 +39,9 @@ def demandload(scope, modules): except: module = m fromlist = [] + as_ = None + if '@' in module: + module, as_ = module.split('@') mod = __import__(module, scope, scope, fromlist) if fromlist == []: # mod is only the top package, but we need all packages @@ -46,7 +50,9 @@ def demandload(scope, modules): mn = comp[0] while True: # mn and mod.__name__ might not be the same - scope[mn] = mod + if not as_: + as_ = mn + scope[as_] = mod requiredmodules[mod.__name__] = 1 if len(comp) == i: break mod = getattr(mod,comp[i]) diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -182,7 +182,7 @@ def readgitpatch(patchname): return (dopatch, gitpatches) -def dogitpatch(patchname, gitpatches): +def dogitpatch(patchname, gitpatches, cwd=None): """Preprocess git patch so that vanilla patch can handle it""" pf = file(patchname) pfline = 1 @@ -196,7 +196,7 @@ def dogitpatch(patchname, gitpatches): if not p.copymod: continue - copyfile(p.oldpath, p.path) + copyfile(p.oldpath, p.path, basedir=cwd) # rewrite patch hunk while pfline < p.lineno: @@ -227,23 +227,20 @@ def patch(patchname, ui, strip=1, cwd=No """apply the patch to the working directory. a list of patched files is returned""" - (dopatch, gitpatches) = readgitpatch(patchname) + # helper function + def __patch(patchname): + """patch and updates the files and fuzz variables""" + files = {} + fuzz = False - files = {} - fuzz = False - if dopatch: - if dopatch == 'filter': - patchname = dogitpatch(patchname, gitpatches) - patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') + patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), + 'patch') args = [] if cwd: args.append('-d %s' % util.shellquote(cwd)) fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip, util.shellquote(patchname))) - if dopatch == 'filter': - False and os.unlink(patchname) - for line in fp: line = line.rstrip() ui.note(line + '\n') @@ -264,11 +261,24 @@ def patch(patchname, ui, strip=1, cwd=No ui.warn(pf + '\n') printed_file = True ui.warn(line + '\n') - code = fp.close() if code: raise util.Abort(_("patch command failed: %s") % util.explain_exit(code)[0]) + return files, fuzz + + (dopatch, gitpatches) = readgitpatch(patchname) + + if dopatch: + if dopatch == 'filter': + patchname = dogitpatch(patchname, gitpatches, cwd=cwd) + try: + files, fuzz = __patch(patchname) + finally: + if dopatch == 'filter': + os.unlink(patchname) + else: + files, fuzz = {}, False for gp in gitpatches: files[gp.path] = (gp.op, gp) diff --git a/mercurial/sshrepo.py b/mercurial/sshrepo.py --- a/mercurial/sshrepo.py +++ b/mercurial/sshrepo.py @@ -34,9 +34,10 @@ class sshrepository(remoterepository): if create: try: self.validate_repo(ui, sshcmd, args, remotecmd) - return # the repo is good, nothing more to do except hg.RepoError: pass + else: + raise hg.RepoError(_("repository %s already exists") % path) cmd = '%s %s "%s init %s"' cmd = cmd % (sshcmd, args, remotecmd, self.path) @@ -52,6 +53,9 @@ class sshrepository(remoterepository): return self._url def validate_repo(self, ui, sshcmd, args, remotecmd): + # cleanup up previous run + self.cleanup() + cmd = '%s %s "%s -R %s serve --stdio"' cmd = cmd % (sshcmd, args, remotecmd, self.path) @@ -90,7 +94,7 @@ class sshrepository(remoterepository): if not l: break self.ui.status(_("remote: "), l) - def __del__(self): + def cleanup(self): try: self.pipeo.close() self.pipei.close() @@ -101,6 +105,8 @@ class sshrepository(remoterepository): except: pass + __del__ = cleanup + def do_cmd(self, cmd, **args): self.ui.debug(_("sending %s command\n") % cmd) self.pipeo.write("%s\n" % cmd) diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -12,13 +12,14 @@ demandload(globals(), "ConfigParser mdif class ui(object): def __init__(self, verbose=False, debug=False, quiet=False, - interactive=True, traceback=False, parentui=None, - readhooks=[]): + interactive=True, traceback=False, parentui=None): self.overlay = {} if parentui is None: # this is the parent of all ui children self.parentui = None - self.readhooks = list(readhooks) + self.readhooks = [] + self.trusted_users = {} + self.trusted_groups = {} self.cdata = ConfigParser.SafeConfigParser() self.readconfig(util.rcpath()) @@ -36,7 +37,9 @@ class ui(object): else: # parentui may point to an ui object which is already a child self.parentui = parentui.parentui or parentui - self.readhooks = list(parentui.readhooks or readhooks) + self.readhooks = parentui.readhooks[:] + self.trusted_users = parentui.trusted_users.copy() + self.trusted_groups = parentui.trusted_groups.copy() parent_cdata = self.parentui.cdata self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults()) # make interpolation work @@ -51,7 +54,7 @@ class ui(object): def updateopts(self, verbose=False, debug=False, quiet=False, interactive=True, traceback=False, config=[]): self.quiet = (self.quiet or quiet) and not verbose and not debug - self.verbose = (self.verbose or verbose) or debug + self.verbose = ((self.verbose or verbose) or debug) and not self.quiet self.debugflag = (self.debugflag or debug) self.interactive = (self.interactive and interactive) self.traceback = self.traceback or traceback @@ -72,7 +75,22 @@ class ui(object): fn = [fn] for f in fn: try: - self.cdata.read(f) + fp = open(f) + except IOError: + continue + if ((self.trusted_users or self.trusted_groups) and + '*' not in self.trusted_users and + '*' not in self.trusted_groups): + st = util.fstat(fp) + user = util.username(st.st_uid) + group = util.groupname(st.st_gid) + if (user not in self.trusted_users and + group not in self.trusted_groups): + self.warn(_('not reading file %s from untrusted ' + 'user %s, group %s\n') % (f, user, group)) + continue + try: + self.cdata.readfp(fp, f) except ConfigParser.ParsingError, inst: raise util.Abort(_("Failed to parse %s\n%s") % (f, inst)) # translate paths relative to root (or home) into absolute paths @@ -81,9 +99,19 @@ class ui(object): for name, path in self.configitems("paths"): if path and "://" not in path and not os.path.isabs(path): self.cdata.set("paths", name, os.path.join(root, path)) + 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 for hook in self.readhooks: hook(self) + def addreadhook(self, hook): + self.readhooks.append(hook) + def setconfig(self, section, name, val): self.overlay[(section, name)] = val diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -15,7 +15,7 @@ platform-specific details from the core. from i18n import gettext as _ from demandload import * demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile") -demandload(globals(), "os threading time") +demandload(globals(), "os threading time pwd grp") # used by parsedate defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M', @@ -509,6 +509,38 @@ def getuser(): raise Abort(_('user name not available - set USERNAME ' 'environment variable')) +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: + # force an ImportError if there's no module pwd + getpwuid = pwd.getpwuid + if uid is None: + uid = os.getuid() + try: + return 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: + # force an ImportError if there's no module grp + getgrgid = grp.getgrgid + if gid is None: + gid = os.getgid() + try: + return getgrgid(gid)[0] + except KeyError: + return str(gid) + except ImportError: + return None + # Platform specific variants if os.name == 'nt': demandload(globals(), "msvcrt") diff --git a/templates/map-gitweb b/templates/map-gitweb --- a/templates/map-gitweb +++ b/templates/map-gitweb @@ -8,7 +8,7 @@ error = error-gitweb.tmpl naventry = '#label|escape# ' navshortentry = '#label|escape# ' filedifflink = '#file|escape# ' -filenodelink = '#file|escape#file | revisions' +filenodelink = '#file|escape#file | annotate | revisions' fileellipses = '...' changelogentry = changelogentry-gitweb.tmpl searchentry = changelogentry-gitweb.tmpl @@ -19,12 +19,12 @@ manifestfileentry = ' #linenumber# #line|escape#' -annotateline = '#author|obfuscate#@#rev##line|escape#' -difflineplus = '
#line|escape#
' -difflineminus = '
#line|escape#
' -difflineat = '
#line|escape#
' -diffline = '
#line|escape#
' +fileline = '
   #linenumber# #line|escape#
' +annotateline = '#author|obfuscate#@#rev#
#line|escape#
' +difflineplus = '
#line|escape#
' +difflineminus = '
#line|escape#
' +difflineat = '
#line|escape#
' +diffline = '
#line|escape#
' changelogparent = 'parent #rev#:#node|short#' changesetparent = 'parent#node|short#' filerevparent = 'parent:#node|short#' @@ -37,7 +37,7 @@ filerevchild = ' fileannotatechild = 'child:#node|short#' tags = tags-gitweb.tmpl tagentry = '#date|age# ago#tag|escape#changeset | changelog | manifest' -diffblock = '#lines#' +diffblock = '
#lines#
' changelogtag = 'tag:#tag|escape#' changesettag = 'tag#tag|escape#' filediffparent = 'parent #rev#:#node|short#' @@ -46,5 +46,5 @@ filediffchild = 'c filelogchild = 'child #rev#: #node|short#' shortlog = shortlog-gitweb.tmpl shortlogentry = '#date|age# ago#author##desc|strip|firstline|escape#changeset | manifest' -filelogentry = '#date|age# ago#desc|strip|firstline|escape# annotate #rename%filelogrename#' +filelogentry = '#date|age# ago#desc|strip|firstline|escape#file | annotate #rename%filelogrename#' archiveentry = ' | #type|escape# ' diff --git a/templates/static/style-gitweb.css b/templates/static/style-gitweb.css --- a/templates/static/style-gitweb.css +++ b/templates/static/style-gitweb.css @@ -47,3 +47,4 @@ a.rss_logo { text-align:center; text-decoration:none; } a.rss_logo:hover { background-color:#ee5500; } +pre { margin: 0; } diff --git a/tests/README b/tests/README --- a/tests/README +++ b/tests/README @@ -31,3 +31,65 @@ writing tests: use hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" to strip dates + +- You can append your own hgrc settings to the file that the environment + variable HGRCPATH points to. This file is cleared before running a test. + +You also need to be careful that the tests are portable from one platform +to another. You're probably working on Linux, where the GNU toolchain has +more (or different) functionality than on MacOS, *BSD, Solaris, AIX, etc. +While testing on all platforms is the only sure-fire way to make sure that +you've written portable code, here's a list of problems that have been +found and fixed in the tests. Another, more comprehensive list may be +found in the GNU Autoconf manual, online here: + + http://www.gnu.org/software/autoconf/manual/html_node/Portable-Shell.html + +sh: + +The Bourne shell is a very basic shell. /bin/sh on Linux is typically +bash, which even in Bourne-shell mode has many features that Bourne shells +on other Unix systems don't have (and even on Linux /bin/sh isn't +guaranteed to be bash). You'll need to be careful about constructs that +seem ubiquitous, but are actually not available in the least common +denominator. While using another shell (ksh, bash explicitly, posix shell, +etc.) explicitly may seem like another option, these may not exist in a +portable location, and so are generally probably not a good idea. You may +find that rewriting the test in python will be easier. + +- don't use pushd/popd; save the output of "pwd" and use "cd" in place of + the pushd, and cd back to the saved pwd instead of popd. + +- don't use math expressions like let, (( ... )), or $(( ... )); use "expr" + instead. + +grep: + +- don't use the -q option; redirect stdout to /dev/null instead. + +- don't use extended regular expressions with grep; use egrep instead, and + don't escape any regex operators. + +sed: + +- make sure that the beginning-of-line matcher ("^") is at the very + beginning of the expression -- it may not be supported inside parens. + +echo: + +- echo may interpret "\n" and print a newline; use printf instead if you + want a literal "\n" (backslash + n). + +false: + +- false is guaranteed only to return a non-zero value; you cannot depend on + it being 1. On Solaris in particular, /bin/false returns 255. Rewrite + your test to not depend on a particular return value, or create a + temporary "false" executable, and call that instead. + +diff: + +- don't use the -N option. There's no particularly good workaround short + of writing a reasonably complicated replacement script, but substituting + gdiff for diff if you can't rewrite the test not to need -N will probably + do. diff --git a/tests/run-tests.py b/tests/run-tests.py --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -211,6 +211,10 @@ def run_one(test): sys.stdout.write('.') sys.stdout.flush() + # create a fresh hgrc + hgrc = file(HGRCPATH, 'w+') + hgrc.close() + err = os.path.join(TESTDIR, test+".err") ref = os.path.join(TESTDIR, test+".out") @@ -319,11 +323,11 @@ os.environ['TZ'] = 'GMT' os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"' os.environ["HGUSER"] = "test" -os.environ["HGRCPATH"] = "" TESTDIR = os.environ["TESTDIR"] = os.getcwd() HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.") DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') +HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc') vlog("# Using TESTDIR", TESTDIR) vlog("# Using HGTMP", HGTMP) diff --git a/tests/test-abort-checkin b/tests/test-abort-checkin --- a/tests/test-abort-checkin +++ b/tests/test-abort-checkin @@ -1,8 +1,7 @@ #!/bin/sh -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "mq=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "mq=" >> $HGRCPATH cat > $HGTMP/false < badext.py +abspath=`pwd`/badext.py + +echo '[extensions]' >> $HGRCPATH +echo "badext = $abspath" >> $HGRCPATH + +hg -q help help diff --git a/tests/test-bad-extension.out b/tests/test-bad-extension.out new file mode 100644 --- /dev/null +++ b/tests/test-bad-extension.out @@ -0,0 +1,4 @@ +*** failed to import extension badext: invalid syntax (badext.py, line 1) +hg help [COMMAND] + +show help for a command, extension, or list of commands diff --git a/tests/test-bisect b/tests/test-bisect --- a/tests/test-bisect +++ b/tests/test-bisect @@ -2,9 +2,8 @@ set -e -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "hbisect=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "hbisect=" >> $HGRCPATH echo % init hg init diff --git a/tests/test-confused-revert b/tests/test-confused-revert --- a/tests/test-confused-revert +++ b/tests/test-confused-revert @@ -13,7 +13,7 @@ echo "%%% should show a removed and b ad hg status echo "reverting..." -hg revert +hg revert --all echo "%%% should show b unknown and a back to normal" hg status @@ -42,10 +42,10 @@ echo "%%% should show a removed and b ad hg status echo "%%% revert should fail" -hg revert +hg revert --all echo "%%% revert should be ok now" -hg revert -r2 +hg revert -r2 --all echo "%%% should show b unknown and a marked modified (merged)" hg status diff --git a/tests/test-extdiff b/tests/test-extdiff --- a/tests/test-extdiff +++ b/tests/test-extdiff @@ -1,8 +1,7 @@ #!/bin/sh -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "extdiff=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "extdiff=" >> $HGRCPATH hg init a cd a @@ -14,9 +13,9 @@ if [ $? -ne 0 ]; then fi hg extdiff -o -Nr $opt -echo "[extdiff]" >> $HGTMP/.hgrc -echo "cmd.falabala=echo" >> $HGTMP/.hgrc -echo "opts.falabala=diffing" >> $HGTMP/.hgrc +echo "[extdiff]" >> $HGRCPATH +echo "cmd.falabala=echo" >> $HGRCPATH +echo "opts.falabala=diffing" >> $HGRCPATH hg falabala diff --git a/tests/test-fetch b/tests/test-fetch --- a/tests/test-fetch +++ b/tests/test-fetch @@ -1,8 +1,7 @@ #!/bin/sh -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "fetch=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "fetch=" >> $HGRCPATH hg init a echo a > a/a diff --git a/tests/test-http b/tests/test-http --- a/tests/test-http +++ b/tests/test-http @@ -11,7 +11,7 @@ cat hg1.pid hg2.pid >> $DAEMON_PIDS echo % clone via stream http_proxy= hg clone --uncompressed http://localhost:20059/ copy 2>&1 | \ - sed -e 's/[0-9][0-9.]*/XXX/g' + sed -e 's/[0-9][0-9.]*/XXX/g' -e 's/[KM]\(B\/sec\)/X\1/' hg verify -R copy echo % try to clone via stream, should use pull instead diff --git a/tests/test-http-proxy b/tests/test-http-proxy --- a/tests/test-http-proxy +++ b/tests/test-http-proxy @@ -15,7 +15,7 @@ sleep 2 echo %% url for proxy, stream http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone --uncompressed http://localhost:20059/ b | \ - sed -e 's/[0-9][0-9.]*/XXX/g' + sed -e 's/[0-9][0-9.]*/XXX/g' -e 's/[KM]\(B\/sec\)/X\1/' cd b hg verify cd .. diff --git a/tests/test-http-proxy.out b/tests/test-http-proxy.out --- a/tests/test-http-proxy.out +++ b/tests/test-http-proxy.out @@ -2,7 +2,7 @@ adding a %% url for proxy, stream streaming all changes XXX files to transfer, XXX bytes of data -transferred XXX bytes in XXX seconds (XXX KB/sec) +transferred XXX bytes in XXX seconds (XXX XB/sec) XXX files updated, XXX files merged, XXX files removed, XXX files unresolved checking changesets checking manifests diff --git a/tests/test-http.out b/tests/test-http.out --- a/tests/test-http.out +++ b/tests/test-http.out @@ -2,7 +2,7 @@ adding foo % clone via stream streaming all changes XXX files to transfer, XXX bytes of data -transferred XXX bytes in XXX seconds (XXX KB/sec) +transferred XXX bytes in XXX seconds (XXX XB/sec) XXX files updated, XXX files merged, XXX files removed, XXX files unresolved checking changesets checking manifests diff --git a/tests/test-init b/tests/test-init --- a/tests/test-init +++ b/tests/test-init @@ -27,6 +27,9 @@ hg init local echo this > local/foo hg ci --cwd local -A -m "init" -d "1000000 0" +echo "#test failure" +hg init local + echo "# init+push to remote2" hg init -e ./dummyssh ssh://user@dummy/remote2 hg incoming -R remote2 local @@ -35,6 +38,12 @@ hg push -R local -e ./dummyssh ssh://use echo "# clone to remote1" hg clone -e ./dummyssh local ssh://user@dummy/remote1 +echo "# init to existing repo" +hg init -e ./dummyssh ssh://user@dummy/remote1 + +echo "# clone to existing repo" +hg clone -e ./dummyssh local ssh://user@dummy/remote1 + echo "# output of dummyssh" cat dummylog diff --git a/tests/test-init.out b/tests/test-init.out --- a/tests/test-init.out +++ b/tests/test-init.out @@ -1,6 +1,9 @@ # creating 'local' adding foo +#test failure +abort: repository local already exists! # init+push to remote2 +remote: abort: repository remote2 not found! changeset: 0:c4e059d443be tag: tip user: test @@ -14,20 +17,25 @@ remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files # clone to remote1 +remote: abort: repository remote1 not found! searching for changes -remote: abort: repository remote1 not found! remote: adding changesets remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files +# init to existing repo +abort: repository ssh://user@dummy/remote1 already exists! +# clone to existing repo +abort: repository ssh://user@dummy/remote1 already exists! # output of dummyssh Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5: Got arguments 1:user@dummy 2:hg init remote2 3: 4: 5: Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5: Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5: Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5: +Got arguments 1:user@dummy 2:hg init remote1 3: 4: 5: Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5: -Got arguments 1:user@dummy 2:hg init remote1 3: 4: 5: +Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5: Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5: # comparing repositories 0:c4e059d443be diff --git a/tests/test-merge-revert b/tests/test-merge-revert --- a/tests/test-merge-revert +++ b/tests/test-merge-revert @@ -15,7 +15,7 @@ hg update -C 0 hg id echo "changed file1" >> file1 hg id -hg revert +hg revert --all hg diff hg status hg id @@ -29,11 +29,11 @@ HGMERGE=merge hg update hg diff hg status hg id -hg revert +hg revert --all hg diff hg status hg id -hg revert -r tip +hg revert -r tip --all hg diff hg status hg id diff --git a/tests/test-merge-revert2 b/tests/test-merge-revert2 --- a/tests/test-merge-revert2 +++ b/tests/test-merge-revert2 @@ -16,7 +16,7 @@ hg update -C 0 hg id echo "changed file1" >> file1 hg id -hg revert --no-backup +hg revert --no-backup --all hg diff hg status hg id @@ -31,11 +31,11 @@ hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.- -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" -e "s/\(>>>>>>>\) .*/\1/" hg status hg id -hg revert --no-backup +hg revert --no-backup --all hg diff hg status hg id -hg revert -r tip --no-backup +hg revert -r tip --no-backup --all hg diff hg status hg id diff --git a/tests/test-mq b/tests/test-mq --- a/tests/test-mq +++ b/tests/test-mq @@ -1,8 +1,7 @@ #!/bin/sh -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "mq=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "mq=" >> $HGRCPATH echo % help hg help mq @@ -127,7 +126,28 @@ hg ci -Ama hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/' hg unbundle .hg/strip-backup/* -cat >>$HGTMP/.hgrc < a +hg ci -Ama -d'0 0' +hg qnew -mfoo foo +echo a >> a +hg qrefresh +mkdir b +cd b +echo f > f +hg add f +hg qrefresh +sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" ../.hg/patches/foo +echo % hg qrefresh . +hg qrefresh . +sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" ../.hg/patches/foo +hg status + +cat >>$HGRCPATH <> $HGTMP/.hgrc -echo "mq=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "mq=" >> $HGRCPATH hg init hg qinit diff --git a/tests/test-mq-qdiff b/tests/test-mq-qdiff --- a/tests/test-mq-qdiff +++ b/tests/test-mq-qdiff @@ -1,8 +1,7 @@ #!/bin/sh -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "mq=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "mq=" >> $HGRCPATH echo % init hg init a diff --git a/tests/test-mq-qnew-twice b/tests/test-mq-qnew-twice --- a/tests/test-mq-qnew-twice +++ b/tests/test-mq-qnew-twice @@ -1,8 +1,7 @@ #!/bin/sh -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "mq=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "mq=" >> $HGRCPATH hg init a cd a diff --git a/tests/test-mq-qrefresh-replace-log-message b/tests/test-mq-qrefresh-replace-log-message --- a/tests/test-mq-qrefresh-replace-log-message +++ b/tests/test-mq-qrefresh-replace-log-message @@ -1,9 +1,8 @@ #!/bin/sh # Environement setup for MQ -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "mq=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "mq=" >> $HGRCPATH #Repo init hg init diff --git a/tests/test-mq-qsave b/tests/test-mq-qsave --- a/tests/test-mq-qsave +++ b/tests/test-mq-qsave @@ -1,8 +1,7 @@ #!/bin/sh -HGRCPATH=$HGTMP/.hgrc; export HGRCPATH -echo "[extensions]" >> $HGTMP/.hgrc -echo "mq=" >> $HGTMP/.hgrc +echo "[extensions]" >> $HGRCPATH +echo "mq=" >> $HGRCPATH hg init a cd a diff --git a/tests/test-mq.out b/tests/test-mq.out --- a/tests/test-mq.out +++ b/tests/test-mq.out @@ -127,6 +127,30 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy) +% cd b; hg qrefresh +adding a +foo + +diff -r cb9a9f314b8b a +--- a/a ++++ b/a +@@ -1,1 +1,2 @@ a + a ++a +diff -r cb9a9f314b8b b/f +--- /dev/null ++++ b/b/f +@@ -0,0 +1,1 @@ ++f +% hg qrefresh . +foo + +diff -r cb9a9f314b8b b/f +--- /dev/null ++++ b/b/f +@@ -0,0 +1,1 @@ ++f +M a new file diff --git a/new b/new diff --git a/tests/test-nested-repo b/tests/test-nested-repo --- a/tests/test-nested-repo +++ b/tests/test-nested-repo @@ -14,6 +14,6 @@ hg add b/x echo '# should print A b/x' hg st echo '# should forget b/x' -hg revert +hg revert --all echo '# should print nothing' hg st b diff --git a/tests/test-remove b/tests/test-remove --- a/tests/test-remove +++ b/tests/test-remove @@ -9,7 +9,7 @@ hg commit -m 1 -d "1000000 0" hg remove rm foo hg remove foo -hg revert +hg revert --all rm foo hg remove --after hg commit -m 2 -d "1000000 0" diff --git a/tests/test-revert b/tests/test-revert --- a/tests/test-revert +++ b/tests/test-revert @@ -31,7 +31,7 @@ echo %% should show a b c e ls echo %% should verbosely save backup to e.orig echo z > e -hg revert -v +hg revert --all -v echo %% should say no changes needed hg revert a echo %% should say file not managed @@ -46,9 +46,9 @@ echo z > z hg add z hg st echo %% should add a, forget z -hg revert -r0 +hg revert --all -r0 echo %% should forget a -hg revert -rtip +hg revert --all -rtip rm -f a *.orig echo %% should silently add a hg revert -r0 a @@ -56,7 +56,7 @@ hg st a hg update -C chmod +x c -hg revert +hg revert --all echo %% should print non-executable test -x c || echo non-executable @@ -64,7 +64,7 @@ chmod +x c hg commit -d '1000001 0' -m exe chmod -x c -hg revert +hg revert --all echo %% should print executable test -x c && echo executable @@ -78,6 +78,15 @@ hg commit -d '2 0' -m a hg update 0 mkdir b echo b > b/b + +echo % should fail - no arguments hg revert -rtip +echo % should succeed +hg revert --all -rtip + +echo %% issue332 +hg ci -A -m b -d '1000001 0' +echo foobar > b/b +hg revert b true diff --git a/tests/test-revert-unknown b/tests/test-revert-unknown --- a/tests/test-revert-unknown +++ b/tests/test-revert-unknown @@ -13,7 +13,7 @@ hg ci -m "2" -d "1000000 0" echo %% Should show unknown hg status -hg revert -r 0 +hg revert -r 0 --all echo %% Should show unknown and b removed hg status echo %% Should show a and unknown diff --git a/tests/test-revert.out b/tests/test-revert.out --- a/tests/test-revert.out +++ b/tests/test-revert.out @@ -54,4 +54,10 @@ executable %% issue 241 adding a 1 files updated, 0 files merged, 0 files removed, 0 files unresolved +% should fail - no arguments +abort: no files or directories specified; use --all to revert the whole repo +% should succeed reverting a +%% issue332 +adding b/b +reverting b/b diff --git a/tests/test-ssh b/tests/test-ssh --- a/tests/test-ssh +++ b/tests/test-ssh @@ -38,7 +38,7 @@ cd .. echo "# clone remote via stream" hg clone -e ./dummyssh --uncompressed ssh://user@dummy/remote local-stream 2>&1 | \ - sed -e 's/[0-9][0-9.]*/XXX/g' + sed -e 's/[0-9][0-9.]*/XXX/g' -e 's/[KM]\(B\/sec\)/X\1/' cd local-stream hg verify cd .. diff --git a/tests/test-ssh.out b/tests/test-ssh.out --- a/tests/test-ssh.out +++ b/tests/test-ssh.out @@ -2,7 +2,7 @@ # clone remote via stream streaming all changes XXX files to transfer, XXX bytes of data -transferred XXX bytes in XXX seconds (XXX KB/sec) +transferred XXX bytes in XXX seconds (XXX XB/sec) XXX files updated, XXX files merged, XXX files removed, XXX files unresolved checking changesets checking manifests diff --git a/tests/test-strict b/tests/test-strict new file mode 100755 --- /dev/null +++ b/tests/test-strict @@ -0,0 +1,18 @@ +#!/bin/sh + +hg init + +echo a > a +hg ci -d '0 0' -Ama + +hg an a + +echo "[ui]" >> $HGRCPATH +echo "strict=True" >> $HGRCPATH + +hg an a +hg annotate a + +echo % should succeed - up is an alias, not an abbreviation + +hg up diff --git a/tests/test-strict.out b/tests/test-strict.out new file mode 100644 --- /dev/null +++ b/tests/test-strict.out @@ -0,0 +1,26 @@ +adding a +0: a +hg: unknown command 'an' +Mercurial Distributed SCM + +basic commands (use "hg help" for the full list or option "-v" for details): + + add add the specified files on the next commit + annotate show changeset information per file line + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + init create a new repository in the given directory + log show revision history of entire repository or files + parents show the parents of the working dir or revision + pull pull changes from the specified source + push push changes to the specified destination + remove remove the specified files on the next commit + revert revert files or dirs to their states as of some revision + serve export the repository via HTTP + status show changed files in the working directory + update update or merge working directory +0: a +% should succeed - up is an alias, not an abbreviation +0 files updated, 0 files merged, 0 files removed, 0 files unresolved diff --git a/tests/test-trusted.py b/tests/test-trusted.py new file mode 100755 --- /dev/null +++ b/tests/test-trusted.py @@ -0,0 +1,112 @@ +#!/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'): + # 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() + u = ui.ui(parentui=parentui) + u.readconfig('.hg/hgrc') + for name, path in u.configitems('paths'): + 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.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) diff --git a/tests/test-trusted.py.out b/tests/test-trusted.py.out new file mode 100644 --- /dev/null +++ b/tests/test-trusted.py.out @@ -0,0 +1,67 @@ +# same user, same group +global = /some/path +local = /another/path + +# same user, different group +global = /some/path +local = /another/path + +# different user, same group +not reading file .hg/hgrc from untrusted user abc, group bar +global = /some/path + +# different user, same group, but we trust the group +global = /some/path +local = /another/path + +# different user, different group +not reading file .hg/hgrc from untrusted user abc, group def +global = /some/path + +# different user, different group, but we trust the user +global = /some/path +local = /another/path + +# different user, different group, but we trust the group +global = /some/path +local = /another/path + +# different user, different group, but we trust the user and the group +global = /some/path +local = /another/path + +# we trust all users +# different user, different group +global = /some/path +local = /another/path + +# we trust all groups +# different user, different group +global = /some/path +local = /another/path + +# we trust all users and groups +# different user, different group +global = /some/path +local = /another/path + +# we don't get confused by users and groups with the same name +# different user, different group +not reading file .hg/hgrc from untrusted user abc, group def +global = /some/path + +# list of user names +# different user, different group, but we trust the user +global = /some/path +local = /another/path + +# list of group names +# different user, different group, but we trust the group +global = /some/path +local = /another/path + +# Can't figure out the name of the user running this process +# different user, different group +global = /some/path +local = /another/path +