notify: support revset selection for subscriptions
authorMichal Sznajder <michalsznajder@gmail.com>
Mon, 13 Aug 2012 22:42:10 +0200
changeset 17754 19e9bf7c0927
parent 17753 69d5078d760d
child 17755 bededd3f0735
notify: support revset selection for subscriptions A repo pattern for any notify configuration contains a glob matching the path to the repo. Additionally, it may now contain a revset spec, separated from the glob by '#'. Example: [reposubs] */widgets#branch(release) = qa-team@example.com This sends to ``qa-team@example.com`` whenever a changeset on the ``release`` branch triggers a notification in any repository ending in ``widgets``. This patch was completely done by David Champion <dgc@uchicago.edu> with me making tiny changes to his tests.
hgext/notify.py
tests/test-notify.t
--- a/hgext/notify.py	Mon Aug 13 21:50:45 2012 +0200
+++ b/hgext/notify.py	Mon Aug 13 22:42:10 2012 +0200
@@ -30,17 +30,22 @@
 multiple recipients to a single repository::
 
   [usersubs]
-  # key is subscriber email, value is a comma-separated list of repo glob
-  # patterns
+  # key is subscriber email, value is a comma-separated list of repo patterns
   user@host = pattern
 
   [reposubs]
-  # key is glob pattern, value is a comma-separated list of subscriber
-  # emails
+  # key is repo pattern, value is a comma-separated list of subscriber emails
   pattern = user@host
 
-Glob patterns are matched against absolute path to repository
-root.
+A ``pattern`` is a ``glob`` matching the absolute path to a repository,
+optionally combined with a revset expression. A revset expression, if
+present, is separated from the glob by a hash. Example::
+
+  [reposubs]
+  */widgets#branch(release) = qa-team@example.com
+
+This sends to ``qa-team@example.com`` whenever a changeset on the ``release``
+branch triggers a notification in any repository ending in ``widgets``.
 
 In order to place them under direct user management, ``[usersubs]`` and
 ``[reposubs]`` sections may be placed in a separate ``hgrc`` file and
@@ -217,14 +222,22 @@
         subs = set()
         for user, pats in self.ui.configitems('usersubs'):
             for pat in pats.split(','):
+                if '#' in pat:
+                    pat, revs = pat.split('#', 1)
+                else:
+                    revs = None
                 if fnmatch.fnmatch(self.repo.root, pat.strip()):
-                    subs.add(self.fixmail(user))
+                    subs.add((self.fixmail(user), revs))
         for pat, users in self.ui.configitems('reposubs'):
+            if '#' in pat:
+                pat, revs = pat.split('#', 1)
+            else:
+                revs = None
             if fnmatch.fnmatch(self.repo.root, pat):
                 for user in users.split(','):
-                    subs.add(self.fixmail(user))
-        return [mail.addressencode(self.ui, s, self.charsets, self.test)
-                for s in sorted(subs)]
+                    subs.add((self.fixmail(user), revs))
+        return [(mail.addressencode(self.ui, s, self.charsets, self.test), r)
+                for s, r in sorted(subs)]
 
     def node(self, ctx, **props):
         '''format one changeset, unless it is a suppressed merge.'''
@@ -243,6 +256,21 @@
     def send(self, ctx, count, data):
         '''send message.'''
 
+        # Select subscribers by revset
+        subs = set()
+        for sub, spec in self.subs:
+            if spec is None:
+                subs.add(sub)
+                continue
+            revs = self.repo.revs('%r and %d:', spec, ctx.rev())
+            if len(revs):
+                subs.add(sub)
+                continue
+        if len(subs) == 0:
+            self.ui.debug('notify: no subscribers to selected repo '
+                          'and revset\n')
+            return
+
         p = email.Parser.Parser()
         try:
             msg = p.parsestr(data)
@@ -292,7 +320,7 @@
             msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
                                  (ctx, int(time.time()),
                                   hash(self.repo.root), socket.getfqdn()))
-        msg['To'] = ', '.join(self.subs)
+        msg['To'] = ', '.join(sorted(subs))
 
         msgtext = msg.as_string()
         if self.test:
@@ -301,9 +329,9 @@
                 self.ui.write('\n')
         else:
             self.ui.status(_('notify: sending %d subscribers %d changes\n') %
-                           (len(self.subs), count))
+                           (len(subs), count))
             mail.sendmail(self.ui, util.email(msg['From']),
-                          self.subs, msgtext, mbox=self.mbox)
+                          subs, msgtext, mbox=self.mbox)
 
     def diff(self, ctx, ref=None):
 
--- a/tests/test-notify.t	Mon Aug 13 21:50:45 2012 +0200
+++ b/tests/test-notify.t	Mon Aug 13 22:42:10 2012 +0200
@@ -42,16 +42,22 @@
   repository:
   
     [usersubs]
-    # key is subscriber email, value is a comma-separated list of repo glob
-    # patterns
+    # key is subscriber email, value is a comma-separated list of repo patterns
     user@host = pattern
   
     [reposubs]
-    # key is glob pattern, value is a comma-separated list of subscriber
-    # emails
+    # key is repo pattern, value is a comma-separated list of subscriber emails
     pattern = user@host
   
-  Glob patterns are matched against absolute path to repository root.
+  A "pattern" is a "glob" matching the absolute path to a repository, optionally
+  combined with a revset expression. A revset expression, if present, is
+  separated from the glob by a hash. Example:
+  
+    [reposubs]
+    */widgets#branch(release) = qa-team@example.com
+  
+  This sends to "qa-team@example.com" whenever a changeset on the "release"
+  branch triggers a notification in any repository ending in "widgets".
   
   In order to place them under direct user management, "[usersubs]" and
   "[reposubs]" sections may be placed in a separate "hgrc" file and incorporated
@@ -473,3 +479,77 @@
   ononononononononononononononononononononononononononononononononononononono=
   nonononononononononononono
   
+ revset selection: send to address that matches branch and repo
+
+  $ cat << EOF >> $HGRCPATH
+  > [hooks]
+  > incoming.notify = python:hgext.notify.hook
+  > 
+  > [notify]
+  > sources = pull
+  > test = True
+  > diffstat = False
+  > maxdiff = 0
+  > 
+  > [reposubs]
+  > */a#branch(test) = will_no_be_send@example.com
+  > */b#branch(test) = notify@example.com
+  > EOF
+  $ hg --cwd a branch test
+  marked working directory as branch test
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo a >> a/a
+  $ hg --cwd a ci -m test -d '1 0'
+  $ hg --traceback --cwd b pull ../a | \
+  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  pulling from ../a
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  Content-Type: text/plain; charset="us-ascii"
+  MIME-Version: 1.0
+  Content-Transfer-Encoding: 7bit
+  X-Test: foo
+  Date: * (glob)
+  Subject: test
+  From: test@test.com
+  X-Hg-Notification: changeset fbbcbc516f2f
+  Message-Id: <hg.fbbcbc516f2f.*.*@*> (glob)
+  To: baz@test.com, foo@bar, notify@example.com
+  
+  changeset fbbcbc516f2f in b
+  description: test
+  (run 'hg update' to get a working copy)
+
+revset selection: don't send to address that waits for mails
+from different branch
+
+  $ hg --cwd a update default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo a >> a/a
+  $ hg --cwd a ci -m test -d '1 0'
+  $ hg --traceback --cwd b pull ../a | \
+  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  pulling from ../a
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (+1 heads)
+  Content-Type: text/plain; charset="us-ascii"
+  MIME-Version: 1.0
+  Content-Transfer-Encoding: 7bit
+  X-Test: foo
+  Date: * (glob)
+  Subject: test
+  From: test@test.com
+  X-Hg-Notification: changeset 38b42fa092de
+  Message-Id: <hg.38b42fa092de.*.*@*> (glob)
+  To: baz@test.com, foo@bar
+  
+  changeset 38b42fa092de in b
+  description: test
+  (run 'hg heads' to see heads)
+