[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: svn commit: r8539 - trunk/tools/hook-scripts

From: Branko Čibej <brane_at_xbc.nu>
Date: 2004-02-01 00:46:51 CET

C. Michael Pilato wrote:

>Branko Čibej <brane@xbc.nu> writes:
>
>
>
>>>>I think I saw a patch that did something like that the other day...
>>>>
>>>>
>>>>
>>>Do you mean that one:
>>>
>>>http://www.contactor.se/~dast/svn/archive-2004-01/0062.shtml
>>>
>>>
>>>
>>Yup, that's the one. Well Mike, do you want to make this a 1.0
>>candidate? :-)
>>
>>
>
>In a modified form, perhaps. I don't like that propchanges are
>triggered by '--revprop' which much be the first argument. I'd rather
>see a subcommand syntax like our binaries use:
>
> $ commit-mailer.py commit REPOS-DIR REVISION [CONFIGFILE]
> $ commit-mailer.py revprop REPOS-DIR REVISION AUTHOR PROPNAME [CONFIGFILE]
>
>But other than yet, I love the patch. Dunno if I wanna hold up a 1.0
>release for it. Also, there's a major downside to shipping *only*
>commit-mailer.py -- the bindings requirement.
>
>

Like this? It's "mailer.py commit" or "mailer.py propchange", the
subject_prefix parameter becomes commit_subject_prefix, and we grow a
new parameter propchange_subject_prefix.

Based on a patch by Bastian Blank (/bblank_at_thinkmo.de/
<mailto:bblank_at_thinkmo.de?Subject=Re:%20%5BPATCH%5D%20mailer.py%20-%20propchange>)

-- 
Brane Čibej   <brane_at_xbc.nu>   http://www.xbc.nu/brane/
Index: tools/hook-scripts/mailer/mailer.py
===================================================================
--- tools/hook-scripts/mailer/mailer.py	(revision 8546)
+++ tools/hook-scripts/mailer/mailer.py	(working copy)
@@ -32,130 +32,39 @@
 SEPARATOR = '=' * 78
 
 
-def main(pool, config_fname, repos_dir, rev):
+def main(pool, cmd, config_fname, repos_dir, rev, author, propname):
   repos = Repository(repos_dir, rev, pool)
-
   cfg = Config(config_fname, repos)
 
-  editor = svn.repos.RevisionChangeCollector(repos.fs_ptr, rev, pool)
-
-  e_ptr, e_baton = svn.delta.make_editor(editor, pool)
-  svn.repos.svn_repos_replay(repos.root_this, e_ptr, e_baton, pool)
-
-  # get all the changes and sort by path
-  changelist = editor.changes.items()
-  changelist.sort()
-
-  ### hunh. this code isn't actually needed for StandardOutput. refactor?
-  # collect the set of groups and the unique sets of params for the options
-  groups = { }
-  for path, change in changelist:
-    for (group, params) in cfg.which_groups(path):
-      # turn the params into a hashable object and stash it away
-      param_list = params.items()
-      param_list.sort()
-      groups[group, tuple(param_list)] = params
-
-  output = determine_output(cfg, repos, changelist)
-  output.generate(groups, pool)
-
-
-def determine_output(cfg, repos, changelist):
-  if cfg.is_set('general.mail_command'):
-    cls = PipeOutput
-  elif cfg.is_set('general.smtp_hostname'):
-    cls = SMTPOutput
+  if cmd == 'commit':
+    messenger = Commit(pool, cfg, repos)
+  elif cmd == 'propchange':
+    messenger = PropChange(pool, cfg, repos, author, propname)
   else:
-    cls = StandardOutput
+    raise UnknownSubcommand(cmd)
 
-  return cls(cfg, repos, changelist)
+  messenger.generate()
 
 
 class MailedOutput:
-  def __init__(self, cfg, repos, changelist):
+  def __init__(self, cfg, repos, prefix_param):
     self.cfg = cfg
     self.repos = repos
-    self.changelist = changelist
+    self.prefix_param = prefix_param
 
-    # figure out the changed directories
-    dirs = { }
-    for path, change in changelist:
-      if change.item_kind == svn.core.svn_node_dir:
-        dirs[path] = None
+  def start(self, group, params, override_author = None):
+    self.to_addr = self.cfg.get('to_addr', group, params)
+    author = self.cfg.get('from_addr', group, params)
+    if not author:
+      if override_author:
+        author = override_author
       else:
-        idx = string.rfind(path, '/')
-        if idx == -1:
-          dirs[''] = None
-        else:
-          dirs[path[:idx]] = None
-
-    dirlist = dirs.keys()
-
-    # figure out the common portion of all the dirs. note that there is
-    # no "common" if only a single dir was changed, or the root was changed.
-    if len(dirs) == 1 or dirs.has_key(''):
-      commondir = ''
-    else:
-      common = string.split(dirlist.pop(), '/')
-      for d in dirlist:
-        parts = string.split(d, '/')
-        for i in range(len(common)):
-          if i == len(parts) or common[i] != parts[i]:
-            del common[i:]
-            break
-      commondir = string.join(common, '/')
-      if commondir:
-        # strip the common portion from each directory
-        l = len(commondir) + 1
-        dirlist = [ ]
-        for d in dirs.keys():
-          if d == commondir:
-            dirlist.append('.')
-          else:
-            dirlist.append(d[l:])
-      else:
-        # nothing in common, so reset the list of directories
-        dirlist = dirs.keys()
-
-    # compose the basic subject line. later, we can prefix it.
-    dirlist.sort()
-    dirlist = string.join(dirlist)
-    if commondir:
-      self.subject = 'r%d - in %s: %s' % (repos.rev, commondir, dirlist)
-    else:
-      self.subject = 'r%d - %s' % (repos.rev, dirlist)
-
-  def generate(self, groups, pool):
-    "Generate email for the various groups and option-params."
-
-    ### these groups need to be further compressed. if the headers and
-    ### body are the same across groups, then we can have multiple To:
-    ### addresses. SMTPOutput holds the entire message body in memory,
-    ### so if the body doesn't change, then it can be sent N times
-    ### rather than rebuilding it each time.
-
-    subpool = svn.util.svn_pool_create(pool)
-
-    for (group, param_tuple), params in groups.items():
-      self.start(group, params)
-
-      # generate the content for this group and set of params
-      generate_content(self, self.cfg, self.repos, self.changelist,
-                       group, params, subpool)
-
-      self.finish()
-      svn.util.svn_pool_clear(subpool)
-
-    svn.util.svn_pool_destroy(subpool)
-
-  def start(self, group, params):
-    self.to_addr = self.cfg.get('to_addr', group, params)
-    self.from_addr = self.cfg.get('from_addr', group, params) or \
-                     self.repos.author or 'no_author'
+        author = self.repos.author or 'no_author'
+    self.from_addr = author
     self.reply_to = self.cfg.get('reply_to', group, params)
 
   def mail_headers(self, group, params):
-    prefix = self.cfg.get('subject_prefix', group, params)
+    prefix = self.cfg.get(self.prefix_param, group, params)
     if prefix:
       subject = prefix + ' ' + self.subject
     else:
@@ -174,18 +83,15 @@
 class SMTPOutput(MailedOutput):
   "Deliver a mail message to an MTA using SMTP."
 
-  def __init__(self, cfg, repos, changelist):
-    MailedOutput.__init__(self, cfg, repos, changelist)
+  def start(self, group, params, **args):
+    MailedOutput.start(self, group, params, **args)
 
-  def start(self, group, params):
-    MailedOutput.start(self, group, params)
-
     self.buffer = cStringIO.StringIO()
     self.write = self.buffer.write
 
     self.write(self.mail_headers(group, params))
 
-  def run_diff(self, cmd):
+  def run(self, cmd):
     # we're holding everything in memory, so we may as well read the
     # entire diff into memory and stash that into the buffer
     pipe_ob = popen2.Popen3(cmd)
@@ -206,22 +112,19 @@
 class StandardOutput:
   "Print the commit message to stdout."
 
-  def __init__(self, cfg, repos, changelist):
+  def __init__(self, cfg, repos, prefix_param):
     self.cfg = cfg
     self.repos = repos
-    self.changelist = changelist
 
     self.write = sys.stdout.write
 
-  def generate(self, groups, pool):
-    "Generate the output; the groups are ignored."
+  def start(self, group, params, **args):
+    pass
 
-    # use the default group and no parameters
-    ### is that right?
-    generate_content(self, self.cfg, self.repos, self.changelist,
-                     None, { }, pool)
+  def finish(self):
+    pass
 
-  def run_diff(self, cmd):
+  def run(self, cmd):
     # flush our output to keep the parent/child output in sync
     sys.stdout.flush()
     sys.stderr.flush()
@@ -244,8 +147,8 @@
 class PipeOutput(MailedOutput):
   "Deliver a mail message to an MDA via a pipe."
 
-  def __init__(self, cfg, repos, changelist):
-    MailedOutput.__init__(self, cfg, repos, changelist)
+  def __init__(self, cfg, repos, prefix_param):
+    MailedOutput.__init__(self, cfg, repos, prefix_param)
 
     # figure out the command for delivery
     self.cmd = string.split(cfg.general.mail_command)
@@ -253,8 +156,8 @@
     # we want a descriptor to /dev/null for hooking up to the diffs' stdin
     self.null = os.open('/dev/null', os.O_RDONLY)
 
-  def start(self, group, params):
-    MailedOutput.start(self, group, params)
+  def start(self, group, params, **args):
+    MailedOutput.start(self, group, params, **args)
 
     ### gotta fix this. this is pretty specific to sendmail and qmail's
     ### mailwrapper program. should be able to use option param substitution
@@ -270,7 +173,7 @@
     # start writing out the mail message
     self.write(self.mail_headers(group, params))
 
-  def run_diff(self, cmd):
+  def run(self, cmd):
     # flush the buffers that write to the mailer. we're about to fork, and
     # we don't want data sitting in both copies of the buffer. we also
     # want to ensure the parts are delivered to the mailer in the right order.
@@ -311,6 +214,142 @@
     self.pipe.wait()
 
 
+class Messenger:
+  def __init__(self, pool, cfg, repos, prefix_param):
+    self.pool = pool
+    self.cfg = cfg
+    self.repos = repos
+    self.determine_output(cfg, repos, prefix_param)
+
+  def determine_output(self, cfg, repos, prefix_param):
+    if cfg.is_set('general.mail_command'):
+      cls = PipeOutput
+    elif cfg.is_set('general.smtp_hostname'):
+      cls = SMTPOutput
+    else:
+      cls = StandardOutput
+
+    self.output = cls(cfg, repos, prefix_param)
+
+
+class Commit(Messenger):
+  def __init__(self, pool, cfg, repos):
+    Messenger.__init__(self, pool, cfg, repos, 'commit_subject_prefix')
+
+    # get all the changes and sort by path
+    editor = svn.repos.RevisionChangeCollector(repos.fs_ptr, repos.rev,
+                                               self.pool)
+    e_ptr, e_baton = svn.delta.make_editor(editor, self.pool)
+    svn.repos.svn_repos_replay(repos.root_this, e_ptr, e_baton, self.pool)
+
+    self.changelist = editor.changes.items()
+    self.changelist.sort()
+
+    ### hunh. this code isn't actually needed for StandardOutput. refactor?
+    # collect the set of groups and the unique sets of params for the options
+    self.groups = { }
+    for path, change in self.changelist:
+      for (group, params) in self.cfg.which_groups(path):
+        # turn the params into a hashable object and stash it away
+        param_list = params.items()
+        param_list.sort()
+        self.groups[group, tuple(param_list)] = params
+
+    # figure out the changed directories
+    dirs = { }
+    for path, change in self.changelist:
+      if change.item_kind == svn.core.svn_node_dir:
+        dirs[path] = None
+      else:
+        idx = string.rfind(path, '/')
+        if idx == -1:
+          dirs[''] = None
+        else:
+          dirs[path[:idx]] = None
+
+    dirlist = dirs.keys()
+
+    # figure out the common portion of all the dirs. note that there is
+    # no "common" if only a single dir was changed, or the root was changed.
+    if len(dirs) == 1 or dirs.has_key(''):
+      commondir = ''
+    else:
+      common = string.split(dirlist.pop(), '/')
+      for d in dirlist:
+        parts = string.split(d, '/')
+        for i in range(len(common)):
+          if i == len(parts) or common[i] != parts[i]:
+            del common[i:]
+            break
+      commondir = string.join(common, '/')
+      if commondir:
+        # strip the common portion from each directory
+        l = len(commondir) + 1
+        dirlist = [ ]
+        for d in dirs.keys():
+          if d == commondir:
+            dirlist.append('.')
+          else:
+            dirlist.append(d[l:])
+      else:
+        # nothing in common, so reset the list of directories
+        dirlist = dirs.keys()
+
+    # compose the basic subject line. later, we can prefix it.
+    dirlist.sort()
+    dirlist = string.join(dirlist)
+    if commondir:
+      self.output.subject = 'r%d - in %s: %s' % (repos.rev, commondir, dirlist)
+    else:
+      self.output.subject = 'r%d - %s' % (repos.rev, dirlist)
+
+  def generate(self):
+    "Generate email for the various groups and option-params."
+
+    ### the groups need to be further compressed. if the headers and
+    ### body are the same across groups, then we can have multiple To:
+    ### addresses. SMTPOutput holds the entire message body in memory,
+    ### so if the body doesn't change, then it can be sent N times
+    ### rather than rebuilding it each time.
+
+    subpool = svn.util.svn_pool_create(self.pool)
+
+    for (group, param_tuple), params in self.groups.items():
+      self.output.start(group, params)
+
+      # generate the content for this group and set of params
+      generate_content(self.output, self.cfg, self.repos, self.changelist,
+                       group, params, subpool)
+
+      self.output.finish()
+      svn.util.svn_pool_clear(subpool)
+
+    svn.util.svn_pool_destroy(subpool)
+
+
+class PropChange(Messenger):
+  def __init__(self, pool, cfg, repos, author, propname):
+    Messenger.__init__(self, pool, cfg, repos, 'propchange_subject_prefix')
+    self.author = author
+    self.propname = propname
+
+    self.output.subject = 'r%d - %s' % (repos.rev, propname)
+
+
+  def generate(self):
+    self.output.start([], [], override_author = author)
+
+    self.output.write('Author: %s\nRevision: %s\nProperty Name: %s\n\n'
+                      % (self.author, self.repos.rev, self.propname))
+
+    propvalue = self.repos.get_rev_prop(self.propname)
+
+    self.output.write('New Property Value:\n')
+    self.output.write(propvalue)
+
+    self.output.finish()
+
+
 def generate_content(output, cfg, repos, changelist, group, params, pool):
 
   svndate = repos.get_rev_prop(svn.util.SVN_PROP_REVISION_DATE)
@@ -450,7 +489,7 @@
 
   src_fname, dst_fname = diff.get_files()
 
-  output.run_diff(cfg.get_diff_cmd({
+  output.run(cfg.get_diff_cmd({
     'label_from' : label1,
     'label_to' : label2,
     'from' : src_fname,
@@ -615,7 +654,10 @@
 class MissingConfig(Exception):
   pass
 
+class UnknownSubcommand(Exception):
+  pass
 
+
 # enable True/False in older vsns of Python
 try:
   _unused = True
@@ -625,31 +667,53 @@
 
 
 if __name__ == '__main__':
-  if len(sys.argv) < 3 or len(sys.argv) > 4:
-    sys.stderr.write('USAGE: %s REPOS-DIR REVISION [CONFIG-FILE]\n'
-                     % sys.argv[0])
+  def usage():
+    sys.stderr.write(
+'''USAGE: %s commit     REPOS-DIR REVISION [CONFIG-FILE]
+       %s propchange REPOS-DIR REVISION AUTHOR PROPNAME [CONFIG-FILE]
+'''
+                     % (sys.argv[0], sys.argv[0]))
     sys.exit(1)
 
-  repos_dir = sys.argv[1]
-  revision = int(sys.argv[2])
+  if len(sys.argv) < 4:
+    usage()
 
-  if len(sys.argv) == 3:
+  cmd = sys.argv[1]
+  repos_dir = sys.argv[2]
+  revision = int(sys.argv[3])
+  config_fname = None
+  author = None
+  propname = None
+
+  if cmd == 'commit':
+    if len(sys.argv) > 5:
+      usage()
+    if len(sys.argv) > 4:
+      config_fname = sys.argv[4]
+  elif cmd == 'propchange':
+    if len(sys.argv) < 6 or len(sys.argv) > 7:
+      usage()
+    author = sys.argv[4]
+    propname = sys.argv[5]
+    if len(sys.argv) > 6:
+      config_fname = sys.argv[6]
+  else:
+    usage()
+
+  if config_fname is None:
     # default to REPOS-DIR/conf/mailer.conf
     config_fname = os.path.join(repos_dir, 'conf', 'mailer.conf')
     if not os.path.exists(config_fname):
       # okay. look for 'mailer.conf' as a sibling of this script
       config_fname = os.path.join(os.path.dirname(sys.argv[0]), 'mailer.conf')
-  else:
-    # the config file was explicitly provided
-    config_fname = sys.argv[3]
 
   if not os.path.exists(config_fname):
     raise MissingConfig(config_fname)
 
   ### run some validation on these params
-  svn.util.run_app(main, config_fname, repos_dir, revision)
+  svn.util.run_app(main, cmd, config_fname, repos_dir, revision,
+                   author, propname)
 
-
 # ------------------------------------------------------------------------
 # TODO
 #
@@ -676,4 +740,4 @@
 #       file(s) or DBM
 #   - put the commit author into the params dict  [DONE]
 #   - if the subject line gets too long, then trim it. configurable?
-#
+# * get rid of global functions that should properly be class methods
Index: tools/hook-scripts/mailer/mailer.conf.example
===================================================================
--- tools/hook-scripts/mailer/mailer.conf.example	(revision 8546)
+++ tools/hook-scripts/mailer/mailer.conf.example	(working copy)
@@ -132,9 +132,12 @@
 
 [defaults]
 
-# The default prefix for the Subject: header
-subject_prefix =
+# The default prefix for the Subject: header for commits
+commit_subject_prefix =
 
+# The default prefix for the Subject: header for propchanges
+propchange_subject_prefix =
+
 # The default From: and To: addresses for commit messages.  If the
 # from_addr is not specified or it is specified but there is no text
 # after the `=', then the revision's author is used as the from
@@ -170,7 +173,8 @@
 # # send notifications if any web pages are changed
 # for_paths = .*\.html
 # # set a custom prefix
-# subject_prefix = [commit]
+# commit_subject_prefix = [commit]
+# propchange_subject_prefix = [propchange]
 # # override the default, sending these elsewhere
 # to_addr = www-commits@example.com
 # # use the revision author as the from address
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sun Feb 1 00:47:08 2004

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.