# HG changeset patch # User Brendan Cully # Date 1178654900 25200 # Node ID 94cb7561337d82c7cf202eb0d189a1febfb02e67 # Parent 0912d8df5e19edd18b61ae8aab9eb743c1aadd70# Parent 3b0f73edee572a5c1b909ec6cf8207c7656bf1c5 Fix test-patchbomb for crew diff --git a/contrib/hgsh/hgsh.c b/contrib/hgsh/hgsh.c --- a/contrib/hgsh/hgsh.c +++ b/contrib/hgsh/hgsh.c @@ -251,6 +251,33 @@ enum cmdline { /* + * attempt to verify that a directory is really a hg repo, by testing + * for the existence of a subdirectory. + */ +static int validate_repo(const char *repo_root, const char *subdir) +{ + char *abs_path; + struct stat st; + int ret; + + if (asprintf(&abs_path, "%s.hg/%s", repo_root, subdir) == -1) { + ret = -1; + goto bail; + } + + /* verify that we really are looking at valid repo. */ + + if (stat(abs_path, &st) == -1) { + ret = 0; + } else { + ret = 1; + } + +bail: + return ret; +} + +/* * paranoid wrapper, runs hg executable in server mode. */ static void serve_data(int argc, char **argv) @@ -259,7 +286,6 @@ static void serve_data(int argc, char ** char *repo, *repo_root; enum cmdline cmd; char *nargv[6]; - struct stat st; size_t repolen; int i; @@ -315,15 +341,23 @@ static void serve_data(int argc, char ** /* only hg init expects no repo. */ if (cmd != hg_init) { - char *abs_path; + int valid; - if (asprintf(&abs_path, "%s.hg/data", repo_root) == -1) { + valid = validate_repo(repo_root, "data"); + + if (valid == -1) { goto badargs; } + + if (valid == 0) { + valid = validate_repo(repo_root, "store"); - /* verify that we really are looking at valid repo. */ - - if (stat(abs_path, &st) == -1) { + if (valid == -1) { + goto badargs; + } + } + + if (valid == 0) { perror(repo); exit(EX_DATAERR); } diff --git a/contrib/mq.el b/contrib/mq.el --- a/contrib/mq.el +++ b/contrib/mq.el @@ -36,6 +36,16 @@ :type 'sexp :group 'mercurial) +(defcustom mq-edit-finish-hook nil + "Hook run before a patch description is finished up with." + :type 'sexp + :group 'mercurial) + +(defcustom mq-signoff-address nil + "Address with which to sign off on a patch." + :type 'string + :group 'mercurial) + ;;; Internal variables. @@ -62,10 +72,14 @@ (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 "=" 'mq-diff) (define-key mq-global-map "r" 'mq-refresh) (define-key mq-global-map "e" 'mq-refresh-edit) +(define-key mq-global-map "i" 'mq-new) (define-key mq-global-map "n" 'mq-next) +(define-key mq-global-map "o" 'mq-signoff) (define-key mq-global-map "p" 'mq-previous) +(define-key mq-global-map "s" 'mq-edit-series) (define-key mq-global-map "t" 'mq-top) (add-minor-mode 'mq-mode 'mq-mode) @@ -76,16 +90,17 @@ (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) +(define-key mq-edit-mode-map "\C-c\C-s" 'mq-signoff) ;;; Helper functions. -(defun mq-read-patch-name (&optional source prompt) +(defun mq-read-patch-name (&optional source prompt force) "Read a patch name to use with a command. May return nil, meaning \"use the default\"." (let ((patches (split-string (hg-chomp (hg-run0 (or source "qseries"))) "\n"))) - (when current-prefix-arg + (when force (completing-read (format "Patch%s: " (or prompt "")) (map 'list 'cons patches patches) nil @@ -120,7 +135,8 @@ May return nil, meaning \"use the defaul (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"))) + (interactive (list (mq-read-patch-name "qunapplied" " to push" + current-prefix-arg))) (let ((root (hg-root)) (prev-buf (current-buffer)) last-line ok) @@ -138,7 +154,8 @@ If PATCH is nil, push at most one patch. (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)) + (if (or (<= lines 1) + (and (equal lines 2) (string-match "Now at:" last-line))) (progn (kill-buffer (current-buffer)) (delete-window)) @@ -158,7 +175,8 @@ If PATCH is nil, push at most one patch. (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"))) + (interactive (list (mq-read-patch-name "qapplied" " to pop to" + current-prefix-arg))) (let ((root (hg-root)) last-line ok) (unless root @@ -192,13 +210,14 @@ If PATCH is nil, pop at most one patch." (message "Refreshing %s... done." patch) (error "Refreshing %s... %s" patch (hg-chomp (cdr ret))))))) -(defun mq-refresh () - "Refresh the topmost applied patch." - (interactive) +(defun mq-refresh (&optional git) + "Refresh the topmost applied patch. +With a prefix argument, generate a git-compatible patch." + (interactive "P") (let ((root (hg-root))) (unless root (error "Cannot refresh outside of a repository!")) - (mq-refresh-internal root))) + (apply 'mq-refresh-internal root (if git '("--git"))))) (defun mq-patch-info (cmd &optional msg) (let* ((ret (hg-run cmd)) @@ -231,6 +250,7 @@ This would become the active patch if po (unless (equal (mq-patch-info "qtop") mq-top) (error "Topmost patch has changed!")) (hg-sync-buffers hg-root) + (run-hooks 'mq-edit-finish-hook) (mq-refresh-internal hg-root "-m" (buffer-substring (point-min) (point-max))) (let ((buf mq-prev-buffer)) (kill-buffer nil) @@ -318,6 +338,69 @@ Key bindings (cd root))) (message "Type `C-c C-c' to finish editing and refresh the patch.")) +(defun mq-new (name) + "Create a new empty patch named NAME. +The patch is applied on top of the current topmost patch. +With a prefix argument, forcibly create the patch even if the working +directory is modified." + (interactive (list (mq-read-patch-name "qseries" " to create" t))) + (message "Creating patch...") + (let ((ret (if current-prefix-arg + (hg-run "qnew" "-f" name) + (hg-run "qnew" name)))) + (if (equal (car ret) 0) + (progn + (hg-update-mode-lines (buffer-file-name)) + (message "Creating patch... done.")) + (error "Creating patch... %s" (hg-chomp (cdr ret)))))) + +(defun mq-edit-series () + "Edit the MQ series file directly." + (interactive) + (let ((root (hg-root))) + (unless root + (error "Not in an MQ repository!")) + (find-file (concat root ".hg/patches/series")))) + +(defun mq-diff (&optional git) + "Display a diff of the topmost applied patch. +With a prefix argument, display a git-compatible diff." + (interactive "P") + (hg-view-output ((format "MQ: Diff of %s" (mq-patch-info "qtop"))) + (if git + (call-process (hg-binary) nil t nil "qdiff" "--git") + (call-process (hg-binary) nil t nil "qdiff")) + (diff-mode) + (font-lock-fontify-buffer))) + +(defun mq-signoff () + "Sign off on the current patch, in the style used by the Linux kernel. +If the variable mq-signoff-address is non-nil, it will be used, otherwise +the value of the ui.username item from your hgrc will be used." + (interactive) + (let ((was-editing (eq major-mode 'mq-edit-mode)) + signed) + (unless was-editing + (mq-refresh-edit)) + (save-excursion + (let* ((user (or mq-signoff-address + (hg-run0 "debugconfig" "ui.username"))) + (signoff (concat "Signed-off-by: " user))) + (if (search-forward signoff nil t) + (message "You have already signed off on this patch.") + (goto-char (point-max)) + (let ((case-fold-search t)) + (if (re-search-backward "^Signed-off-by: " nil t) + (forward-line 1) + (insert "\n"))) + (insert signoff) + (message "%s" signoff) + (setq signed t)))) + (unless was-editing + (if signed + (mq-edit-finish) + (mq-edit-kill))))) + (provide 'mq) diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -435,7 +435,27 @@ class queue: return (True, files, fuzz) def apply(self, repo, series, list=False, update_status=True, - strict=False, patchdir=None, merge=None, wlock=None): + strict=False, patchdir=None, merge=None, wlock=None, + all_files={}): + tr = repo.transaction() + try: + ret = self._apply(tr, repo, series, list, update_status, + strict, patchdir, merge, wlock, + all_files=all_files) + tr.close() + self.save_dirty() + return ret + except: + try: + tr.abort() + finally: + repo.reload() + repo.wreload() + raise + + def _apply(self, tr, repo, series, list=False, update_status=True, + strict=False, patchdir=None, merge=None, wlock=None, + all_files={}): # TODO unify with commands.py if not patchdir: patchdir = self.path @@ -443,7 +463,6 @@ class queue: if not wlock: wlock = repo.wlock() lock = repo.lock() - tr = repo.transaction() n = None for patchname in series: pushable, reason = self.pushable(patchname) @@ -468,6 +487,7 @@ class queue: message = '\n'.join(message) (patcherr, files, fuzz) = self.patch(repo, pf) + all_files.update(files) patcherr = not patcherr if merge and files: @@ -506,7 +526,6 @@ class queue: self.ui.warn("fuzz found when applying patch, stopping\n") err = 1 break - tr.close() self.removeundo(repo) return (err, n) @@ -860,10 +879,25 @@ class queue: else: end = self.series.index(patch, start) + 1 s = self.series[start:end] - if mergeq: - ret = self.mergepatch(repo, mergeq, s, wlock) - else: - ret = self.apply(repo, s, list, wlock=wlock) + all_files = {} + try: + if mergeq: + ret = self.mergepatch(repo, mergeq, s, wlock) + else: + ret = self.apply(repo, s, list, wlock=wlock, + all_files=all_files) + except: + self.ui.warn(_('cleaning up working directory...')) + node = repo.dirstate.parents()[0] + hg.revert(repo, node, None, wlock) + unknown = repo.status(wlock=wlock)[4] + # only remove unknown files that we know we touched or + # created while patching + for f in unknown: + if f in all_files: + util.unlink(repo.wjoin(f)) + self.ui.warn(_('done\n')) + raise top = self.applied[-1].name if ret[0]: self.ui.write("Errors during apply, please fix and refresh %s\n" % @@ -1837,7 +1871,6 @@ def push(ui, repo, patch=None, **opts): ui.warn("merging with queue at: %s\n" % mergeq.path) ret = q.push(repo, patch, force=opts['force'], list=opts['list'], mergeq=mergeq) - q.save_dirty() return ret def pop(ui, repo, patch=None, **opts): diff --git a/tests/test-patchbomb b/tests/test-patchbomb new file mode 100755 --- /dev/null +++ b/tests/test-patchbomb @@ -0,0 +1,17 @@ +#!/bin/sh + +echo "[extensions]" >> $HGRCPATH +echo "patchbomb=" >> $HGRCPATH + +hg init +echo a > a +hg commit -Ama -d '1 0' + +hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar tip | \ + sed -e 's/\(Message-Id:.*@\).*/\1/' + +echo b > b +hg commit -Amb -d '2 0' + +hg email --date '1970-1-1 0:2' -n -f quux -t foo -c bar -s test 0:tip | \ + sed -e 's/\(Message-Id:.*@\|In-Reply-To:.*@\).*/\1/' diff --git a/tests/test-patchbomb.out b/tests/test-patchbomb.out new file mode 100644 --- /dev/null +++ b/tests/test-patchbomb.out @@ -0,0 +1,134 @@ +adding a +hg email: option --date not recognized +hg email [OPTION]... [DEST]... + +send changesets by email + + By default, diffs are sent in the format generated by hg export, + one per message. The series starts with a "[PATCH 0 of N]" + introduction, which describes the series as a whole. + + Each patch email has a Subject line of "[PATCH M of N] ...", using + the first line of the changeset description as the subject text. + The message contains two or three body parts. First, the rest of + the changeset description. Next, (optionally) if the diffstat + program is installed, the result of running diffstat on the patch. + Finally, the patch itself, as generated by "hg export". + + With --outgoing, emails will be generated for patches not + found in the destination repository (or only those which are + ancestors of the specified revisions if any are provided) + + With --bundle, changesets are selected as for --outgoing, + but a single email containing a binary Mercurial bundle as an + attachment will be sent. + + Examples: + + hg email -r 3000 # send patch 3000 only + hg email -r 3000 -r 3001 # send patches 3000 and 3001 + hg email -r 3000:3005 # send patches 3000 through 3005 + hg email 3000 # send patch 3000 (deprecated) + + hg email -o # send all patches not in default + hg email -o DEST # send all patches not in DEST + hg email -o -r 3000 # send all ancestors of 3000 not in default + hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST + + hg email -b # send bundle of all patches not in default + hg email -b DEST # send bundle of all patches not in DEST + hg email -b -r 3000 # bundle of all ancestors of 3000 not in default + hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST + + Before using this command, you will need to enable email in your hgrc. + See the [email] section in hgrc(5) for details. + +options: + + -a --attach send patches as inline attachments + --bcc email addresses of blind copy recipients + -c --cc email addresses of copy recipients + -d --diffstat add diffstat output to messages + -g --git use git extended diff format + -f --from email address of sender + --plain omit hg patch header + -n --test print messages that would be sent + -m --mbox write messages to mbox file instead of sending them + -o --outgoing send changes not found in the target repository + -b --bundle send changes not in target as a binary bundle + -r --rev a revision to send + -s --subject subject of first message (intro or single patch) + -t --to email addresses of recipients + --force run even when remote repository is unrelated (with -b) + --base a base changeset to specify instead of a destination (with -b) + -e --ssh specify ssh command to use + --remotecmd specify hg command to run on the remote side + +use "hg -v help email" to show global options +adding b +hg email: option --date not recognized +hg email [OPTION]... [DEST]... + +send changesets by email + + By default, diffs are sent in the format generated by hg export, + one per message. The series starts with a "[PATCH 0 of N]" + introduction, which describes the series as a whole. + + Each patch email has a Subject line of "[PATCH M of N] ...", using + the first line of the changeset description as the subject text. + The message contains two or three body parts. First, the rest of + the changeset description. Next, (optionally) if the diffstat + program is installed, the result of running diffstat on the patch. + Finally, the patch itself, as generated by "hg export". + + With --outgoing, emails will be generated for patches not + found in the destination repository (or only those which are + ancestors of the specified revisions if any are provided) + + With --bundle, changesets are selected as for --outgoing, + but a single email containing a binary Mercurial bundle as an + attachment will be sent. + + Examples: + + hg email -r 3000 # send patch 3000 only + hg email -r 3000 -r 3001 # send patches 3000 and 3001 + hg email -r 3000:3005 # send patches 3000 through 3005 + hg email 3000 # send patch 3000 (deprecated) + + hg email -o # send all patches not in default + hg email -o DEST # send all patches not in DEST + hg email -o -r 3000 # send all ancestors of 3000 not in default + hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST + + hg email -b # send bundle of all patches not in default + hg email -b DEST # send bundle of all patches not in DEST + hg email -b -r 3000 # bundle of all ancestors of 3000 not in default + hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST + + Before using this command, you will need to enable email in your hgrc. + See the [email] section in hgrc(5) for details. + +options: + + -a --attach send patches as inline attachments + --bcc email addresses of blind copy recipients + -c --cc email addresses of copy recipients + -d --diffstat add diffstat output to messages + -g --git use git extended diff format + -f --from email address of sender + --plain omit hg patch header + -n --test print messages that would be sent + -m --mbox write messages to mbox file instead of sending them + -o --outgoing send changes not found in the target repository + -b --bundle send changes not in target as a binary bundle + -r --rev a revision to send + -s --subject subject of first message (intro or single patch) + -t --to email addresses of recipients + --force run even when remote repository is unrelated (with -b) + --base a base changeset to specify instead of a destination (with -b) + -e --ssh specify ssh command to use + --remotecmd specify hg command to run on the remote side + +use "hg -v help email" to show global options