--- a/contrib/hgsh/hgsh.c Tue May 08 11:42:48 2007 -0500
+++ b/contrib/hgsh/hgsh.c Tue May 08 13:08:20 2007 -0700
@@ -251,6 +251,33 @@
/*
+ * 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 @@
char *repo, *repo_root;
enum cmdline cmd;
char *nargv[6];
- struct stat st;
size_t repolen;
int i;
@@ -315,15 +341,23 @@
/* 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);
}
--- a/contrib/mq.el Tue May 08 11:42:48 2007 -0500
+++ b/contrib/mq.el Tue May 08 13:08:20 2007 -0700
@@ -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 @@
(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 (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 @@
(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 @@
(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 @@
(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 @@
(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)
--- a/hgext/mq.py Tue May 08 11:42:48 2007 -0500
+++ b/hgext/mq.py Tue May 08 13:08:20 2007 -0700
@@ -435,7 +435,27 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-patchbomb Tue May 08 13:08:20 2007 -0700
@@ -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/'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-patchbomb.out Tue May 08 13:08:20 2007 -0700
@@ -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