changeset 4430:94cb7561337d

Fix test-patchbomb for crew
author Brendan Cully <brendan@kublai.com>
date Tue, 08 May 2007 13:08:20 -0700
parents 0912d8df5e19 (current diff) 3b0f73edee57 (diff)
children 8014159074a9
files hgext/mq.py tests/test-patchbomb.out
diffstat 5 files changed, 324 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- 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);
 	    }
--- 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)
 
--- 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):
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/'
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