[sysadmin-bin: 76/168] Split utility functions out of gnome-post-receive-email



commit fff03b2a96c08103d83a0371696b440ed2bd297c
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Wed Apr 15 18:31:15 2009 -0400

    Split utility functions out of gnome-post-receive-email
    
    In order to enable writing further commit hooks in python, split
    git and general utilities out of gnome-post-receive-email into
    separate modules git.py and util.py.

 .gitignore               |    1 +
 git.py                   |  196 ++++++++++++++++++++++++++++
 gnome-post-receive-email |  315 +---------------------------------------------
 util.py                  |  160 +++++++++++++++++++++++
 4 files changed, 363 insertions(+), 309 deletions(-)
---
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/git.py b/git.py
new file mode 100644
index 0000000..19f8c4a
--- /dev/null
+++ b/git.py
@@ -0,0 +1,196 @@
+# Utility functions for git
+#
+# Copyright (C) 2008  Owen Taylor
+# Copyright (C) 2009  Red Hat, Inc
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, If not, see
+# http://www.gnu.org/licenses/.
+#
+# (These are adapted from git-bz)
+
+import os
+import re
+from subprocess import Popen, PIPE
+import sys
+
+from util import die, split_lines
+
+# Clone of subprocess.CalledProcessError (not in Python 2.4)
+class CalledProcessError(Exception):
+    def __init__(self, returncode, cmd):
+        self.returncode = returncode
+        self.cmd = cmd
+
+    def __str__(self):
+        return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+
+NULL_REVISION = "0000000000000000000000000000000000000000"
+
+# Run a git command
+#    Non-keyword arguments are passed verbatim as command line arguments
+#    Keyword arguments are turned into command line options
+#       <name>=True => --<name>
+#       <name>='<str>' => --<name>=<str>
+#    Special keyword arguments:
+#       _quiet: Discard all output even if an error occurs
+#       _interactive: Don't capture stdout and stderr
+#       _input=<str>: Feed <str> to stdinin of the command
+#       _outfile=<file): Use <file> as the output file descriptor
+#       _split_lines: Return an array with one string per returned line
+#
+def git_run(command, *args, **kwargs):
+    to_run = ['git', command.replace("_", "-")]
+
+    interactive = False
+    quiet = False
+    input = None
+    interactive = False
+    outfile = None
+    do_split_lines = False
+    for (k,v) in kwargs.iteritems():
+        if k == '_quiet':
+            quiet = True
+        elif k == '_interactive':
+            interactive = True
+        elif k == '_input':
+            input = v
+        elif k == '_outfile':
+            outfile = v
+        elif k == '_split_lines':
+            do_split_lines = True
+        elif v is True:
+            if len(k) == 1:
+                to_run.append("-" + k)
+            else:
+                to_run.append("--" + k.replace("_", "-"))
+        else:
+            to_run.append("--" + k.replace("_", "-") + "=" + v)
+
+    to_run.extend(args)
+
+    if outfile:
+        stdout = outfile
+    else:
+        if interactive:
+            stdout = None
+        else:
+            stdout = PIPE
+
+    if interactive:
+        stderr = None
+    else:
+        stderr = PIPE
+
+    if input != None:
+        stdin = PIPE
+    else:
+        stdin = None
+
+    process = Popen(to_run,
+                    stdout=stdout, stderr=stderr, stdin=stdin)
+    output, error = process.communicate(input)
+    if process.returncode != 0:
+        if not quiet and not interactive:
+            print >>sys.stderr, error,
+            print output,
+        raise CalledProcessError(process.returncode, " ".join(to_run))
+
+    if interactive or outfile:
+        return None
+    else:
+        if do_split_lines:
+            return split_lines(output.strip())
+        else:
+            return output.strip()
+
+# Wrapper to allow us to do git.<command>(...) instead of git_run()
+class Git:
+    def __getattr__(self, command):
+        def f(*args, **kwargs):
+            return git_run(command, *args, **kwargs)
+        return f
+
+git = Git()
+
+class GitCommit:
+    def __init__(self, id, subject):
+        self.id = id
+        self.subject = subject
+
+# Takes argument like 'git.rev_list()' and returns a list of commit objects
+def rev_list_commits(*args, **kwargs):
+    kwargs_copy = dict(kwargs)
+    kwargs_copy['pretty'] = 'format:%s'
+    kwargs_copy['_split_lines'] = True
+    lines = git.rev_list(*args, **kwargs_copy)
+    if (len(lines) % 2 != 0):
+        raise RuntimeException("git rev-list didn't return an even number of lines")
+
+    result = []
+    for i in xrange(0, len(lines), 2):
+        m = re.match("commit\s+([A-Fa-f0-9]+)", lines[i])
+        if not m:
+            raise RuntimeException("Can't parse commit it '%s'", lines[i])
+        commit_id = m.group(1)
+        subject = lines[i + 1]
+        result.append(GitCommit(commit_id, subject))
+
+    return result
+
+# Loads a single commit object by ID
+def load_commit(commit_id):
+    return rev_list_commits(commit_id + "^!")[0]
+
+# Return True if the commit has multiple parents
+def commit_is_merge(commit):
+    if isinstance(commit, basestring):
+        commit = load_commit(commit)
+
+    parent_count = 0
+    for line in git.cat_file("commit", commit.id, _split_lines=True):
+        if line == "":
+            break
+        if line.startswith("parent "):
+            parent_count += 1
+
+    return parent_count > 1
+
+# Return a short one-line summary of the commit
+def commit_oneline(commit):
+    if isinstance(commit, basestring):
+        commit = load_commit(commit)
+
+    return commit.id[0:7]+"... " + commit.subject[0:59]
+
+# Return the directory name with .git stripped as a short identifier
+# for the module
+def get_module_name():
+    try:
+        git_dir = git.rev_parse(git_dir=True, _quiet=True)
+    except CalledProcessError:
+        die("GIT_DIR not set")
+
+    # No emails for a repository in the process of being imported
+    if os.path.exists(os.path.join(git_dir, 'pending')):
+        return
+
+    # Use the directory name with .git stripped as a short identifier
+    absdir = os.path.abspath(git_dir)
+    projectshort = os.path.basename(absdir)
+    if projectshort.endswith(".git"):
+        projectshort = projectshort[:-4]
+
+    return projectshort
+
+
diff --git a/gnome-post-receive-email b/gnome-post-receive-email
index 016cfa3..1d55034 100755
--- a/gnome-post-receive-email
+++ b/gnome-post-receive-email
@@ -32,305 +32,15 @@
 import re
 import os
 import pwd
-from subprocess import Popen, PIPE
 import sys
-import tempfile
-import time
-
-# Utility functions for git
-# =========================
-
-# (These are adapted from git-bz)
-
-NULL_REVISION = "0000000000000000000000000000000000000000"
-
-# Run a git command
-#    Non-keyword arguments are passed verbatim as command line arguments
-#    Keyword arguments are turned into command line options
-#       <name>=True => --<name>
-#       <name>='<str>' => --<name>=<str>
-#    Special keyword arguments:
-#       _quiet: Discard all output even if an error occurs
-#       _interactive: Don't capture stdout and stderr
-#       _input=<str>: Feed <str> to stdinin of the command
-#       _outfile=<file): Use <file> as the output file descriptor
-#       _split_lines: Return an array with one string per returned line
-#
-def git_run(command, *args, **kwargs):
-    to_run = ['git', command.replace("_", "-")]
-
-    interactive = False
-    quiet = False
-    input = None
-    interactive = False
-    outfile = None
-    do_split_lines = False
-    for (k,v) in kwargs.iteritems():
-        if k == '_quiet':
-            quiet = True
-        elif k == '_interactive':
-            interactive = True
-        elif k == '_input':
-            input = v
-        elif k == '_outfile':
-            outfile = v
-        elif k == '_split_lines':
-            do_split_lines = True
-        elif v is True:
-            if len(k) == 1:
-                to_run.append("-" + k)
-            else:
-                to_run.append("--" + k.replace("_", "-"))
-        else:
-            to_run.append("--" + k.replace("_", "-") + "=" + v)
 
-    to_run.extend(args)
+script_path = os.path.realpath(os.path.abspath(sys.argv[0]))
+script_dir = os.path.dirname(script_path)
 
-    if outfile:
-        stdout = outfile
-    else:
-        if interactive:
-            stdout = None
-        else:
-            stdout = PIPE
+sys.path.insert(0, script_dir)
 
-    if interactive:
-        stderr = None
-    else:
-        stderr = PIPE
-
-    if input != None:
-        stdin = PIPE
-    else:
-        stdin = None
-
-    process = Popen(to_run,
-                    stdout=stdout, stderr=stderr, stdin=stdin)
-    output, error = process.communicate(input)
-    if process.returncode != 0:
-        if not quiet and not interactive:
-            print >>sys.stderr, error,
-            print output,
-        raise CalledProcessError(process.returncode, " ".join(to_run))
-
-    if interactive or outfile:
-        return None
-    else:
-        if do_split_lines:
-            return split_lines(output.strip())
-        else:
-            return output.strip()
-
-# Wrapper to allow us to do git.<command>(...) instead of git_run()
-class Git:
-    def __getattr__(self, command):
-        def f(*args, **kwargs):
-            return git_run(command, *args, **kwargs)
-        return f
-
-git = Git()
-
-class GitCommit:
-    def __init__(self, id, subject):
-        self.id = id
-        self.subject = subject
-
-# Takes argument like 'git.rev_list()' and returns a list of commit objects
-def rev_list_commits(*args, **kwargs):
-    kwargs_copy = dict(kwargs)
-    kwargs_copy['pretty'] = 'format:%s'
-    kwargs_copy['_split_lines'] = True
-    lines = git.rev_list(*args, **kwargs_copy)
-    if (len(lines) % 2 != 0):
-        raise RuntimeException("git rev-list didn't return an even number of lines")
-
-    result = []
-    for i in xrange(0, len(lines), 2):
-        m = re.match("commit\s+([A-Fa-f0-9]+)", lines[i])
-        if not m:
-            raise RuntimeException("Can't parse commit it '%s'", lines[i])
-        commit_id = m.group(1)
-        subject = lines[i + 1]
-        result.append(GitCommit(commit_id, subject))
-
-    return result
-
-# Loads a single commit object by ID
-def load_commit(commit_id):
-    return rev_list_commits(commit_id + "^!")[0]
-
-# Return True if the commit has multiple parents
-def commit_is_merge(commit):
-    if isinstance(commit, basestring):
-        commit = load_commit(commit)
-
-    parent_count = 0
-    for line in git.cat_file("commit", commit.id, _split_lines=True):
-        if line == "":
-            break
-        if line.startswith("parent "):
-            parent_count += 1
-
-    return parent_count > 1
-
-# Return a short one-line summary of the commit
-def commit_oneline(commit):
-    if isinstance(commit, basestring):
-        commit = load_commit(commit)
-
-    return commit.id[0:7]+"... " + commit.subject[0:59]
-
-######################################################################
-
-# General Utility
-# ===============
-
-# Clone of subprocess.CalledProcessError (not in Python 2.4)
-class CalledProcessError(Exception):
-    def __init__(self, returncode, cmd):
-        self.returncode = returncode
-        self.cmd = cmd
-
-    def __str__(self):
-        return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
-
-def die(message):
-    print >>sys.stderr, message
-    sys.exit(1)
-
-# This cleans up our generation code by allowing us to use the same indentation
-# for the first line and subsequent line of a multi-line string
-def s(str):
-    start = 0
-    end = len(str)
-    if len(str) > 0 and str[0] == '\n':
-        start += 1
-    if len(str) > 1 and str[end - 1] == '\n':
-        end -= 1
-
-    return str[start:end]
-
-# Used to split the output of a command that outputs one result per line
-def split_lines(str):
-    if str == "":
-        return []
-    else:
-        return str.split("\n")
-
-# How long to wait between mails (in seconds); the idea of waiting
-# is to try to make the sequence of mails we send out in order
-# actually get delivered in order. The waiting is done in a forked
-# subprocess and doesn't stall completion of the main script.
-EMAIL_DELAY = 5
-
-# Some line that can never appear in any email we send out
-EMAIL_BOUNDARY="---@@@--- gnome-post-receive-email ---@@@---\n"
-
-# Run in subprocess
-def do_send_emails(email_in):
-    email_files = []
-    current_file = None
-    last_line = None
-
-    # Read emails from the input pipe and write each to a file
-    for line in email_in:
-        if current_file is None:
-            current_file, filename = tempfile.mkstemp(suffix=".mail", prefix="gnome-post-receive-email-")
-            email_files.append(filename)
-
-        if line == EMAIL_BOUNDARY:
-            # Strip the last line if blank; see comment when writing
-            # the email boundary for rationale
-            if last_line.strip() != "":
-                os.write(current_file, last_line)
-            last_line = None
-            os.close(current_file)
-            current_file = None
-        else:
-            if last_line is not None:
-                os.write(current_file, last_line)
-            last_line = line
-
-    if current_file is not None:
-        if last_line is not None:
-            os.write(current_file, last_line)
-        os.close(current_file)
-
-    # We're done interacting with the parent process, the rest happens
-    # asynchronously; send out the emails one by one and remove the
-    # temporary files
-    for i, filename in enumerate(email_files):
-        if i != 0:
-            time.sleep(EMAIL_DELAY)
-
-        f = open(filename, "r")
-        process = Popen(["/usr/sbin/sendmail", "-t"],
-                        stdout=None, stderr=None, stdin=f)
-        process.wait()
-        f.close()
-
-        os.remove(filename)
-
-email_file = None
-
-# Start a new outgoing email; returns a file object that the
-# email should be written to. Call end_email() when done
-def start_email():
-    global email_file
-    if email_file is None:
-        email_pipe = os.pipe()
-        pid = os.fork()
-        if pid == 0:
-            # The child
-
-            os.close(email_pipe[1])
-            email_in = os.fdopen(email_pipe[0])
-
-            # Redirect stdin/stdout/stderr to/from /dev/null
-            devnullin = os.open("/dev/null", os.O_RDONLY)
-            os.close(0)
-            os.dup2(devnullin, 0)
-
-            devnullout = os.open("/dev/null", os.O_WRONLY)
-            os.close(1)
-            os.dup2(devnullout, 1)
-            os.close(2)
-            os.dup2(devnullout, 2)
-            os.close(devnullout)
-
-            # Fork again to daemonize
-            if os.fork() > 0:
-                sys.exit(0)
-
-            try:
-                do_send_emails(email_in)
-            except Exception:
-                import syslog
-                import traceback
-
-                syslog.openlog(os.path.basename(sys.argv[0]))
-                syslog.syslog(syslog.LOG_ERR, "Unexpected exception sending mail")
-                for line in traceback.format_exc().strip().split("\n"):
-                    syslog.syslog(syslog.LOG_ERR, line)
-
-            sys.exit(0)
-
-        email_file = os.fdopen(email_pipe[1], "w")
-    else:
-        # The email might not end with a newline, so add one. We'll
-        # strip the last line, if blank, when emails, so the net effect
-        # is to add a newline to messages without one
-        email_file.write("\n")
-        email_file.write(EMAIL_BOUNDARY)
-
-    return email_file
-
-# Finish an email started with start_email
-def end_email():
-    global email_file
-    email_file.flush()
-
-######################################################################
+from git import *
+from util import die, split_lines, strip_string as s, start_email, end_email
 
 # When we put a git subject into the Subject: line, where to truncate
 SUBJECT_MAX_SUBJECT_CHARS = 100
@@ -1109,20 +819,7 @@ def main():
     global user_fullname
     global recipients
 
-    try:
-        git_dir = git.rev_parse(git_dir=True, _quiet=True)
-    except CalledProcessError:
-        die("GIT_DIR not set")
-
-    # No emails for a repository in the process of being imported
-    if os.path.exists(os.path.join(git_dir, 'pending')):
-        return
-
-    # Use the directory name with .git stripped as a short identifier
-    absdir = os.path.abspath(git_dir)
-    projectshort = os.path.basename(absdir)
-    if projectshort.endswith(".git"):
-        projectshort = projectshort[:-4]
+    projectshort = get_module_name()
 
     try:
         recipients=git.config("hooks.mailinglist", _quiet=True)
diff --git a/util.py b/util.py
new file mode 100644
index 0000000..ed775c8
--- /dev/null
+++ b/util.py
@@ -0,0 +1,160 @@
+# General Utility Functions used in our Git scripts
+#
+# Copyright (C) 2008  Owen Taylor
+# Copyright (C) 2009  Red Hat, Inc
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, If not, see
+# http://www.gnu.org/licenses/.
+
+import os
+import sys
+from subprocess import Popen
+import tempfile
+import time
+
+def die(message):
+    print >>sys.stderr, message
+    sys.exit(1)
+
+# Used to split the output of a command that outputs one result per line
+def split_lines(str):
+    if str == "":
+        return []
+    else:
+        return str.split("\n")
+
+# This cleans up our generation code by allowing us to use the same indentation
+# for the first line and subsequent line of a multi-line string
+def strip_string(str):
+    start = 0
+    end = len(str)
+    if len(str) > 0 and str[0] == '\n':
+        start += 1
+    if len(str) > 1 and str[end - 1] == '\n':
+        end -= 1
+
+    return str[start:end]
+
+# How long to wait between mails (in seconds); the idea of waiting
+# is to try to make the sequence of mails we send out in order
+# actually get delivered in order. The waiting is done in a forked
+# subprocess and doesn't stall completion of the main script.
+EMAIL_DELAY = 5
+
+# Some line that can never appear in any email we send out
+EMAIL_BOUNDARY="---@@@--- gnome-git-email ---@@@---\n"
+
+# Run in subprocess
+def _do_send_emails(email_in):
+    email_files = []
+    current_file = None
+    last_line = None
+
+    # Read emails from the input pipe and write each to a file
+    for line in email_in:
+        if current_file is None:
+            current_file, filename = tempfile.mkstemp(suffix=".mail", prefix="gnome-post-receive-email-")
+            email_files.append(filename)
+
+        if line == EMAIL_BOUNDARY:
+            # Strip the last line if blank; see comment when writing
+            # the email boundary for rationale
+            if last_line.strip() != "":
+                os.write(current_file, last_line)
+            last_line = None
+            os.close(current_file)
+            current_file = None
+        else:
+            if last_line is not None:
+                os.write(current_file, last_line)
+            last_line = line
+
+    if current_file is not None:
+        if last_line is not None:
+            os.write(current_file, last_line)
+        os.close(current_file)
+
+    # We're done interacting with the parent process, the rest happens
+    # asynchronously; send out the emails one by one and remove the
+    # temporary files
+    for i, filename in enumerate(email_files):
+        if i != 0:
+            time.sleep(EMAIL_DELAY)
+
+        f = open(filename, "r")
+        process = Popen(["/usr/sbin/sendmail", "-t"],
+                        stdout=None, stderr=None, stdin=f)
+        process.wait()
+        f.close()
+
+        os.remove(filename)
+
+email_file = None
+
+# Start a new outgoing email; returns a file object that the
+# email should be written to. Call end_email() when done
+def start_email():
+    global email_file
+    if email_file is None:
+        email_pipe = os.pipe()
+        pid = os.fork()
+        if pid == 0:
+            # The child
+
+            os.close(email_pipe[1])
+            email_in = os.fdopen(email_pipe[0])
+
+            # Redirect stdin/stdout/stderr to/from /dev/null
+            devnullin = os.open("/dev/null", os.O_RDONLY)
+            os.close(0)
+            os.dup2(devnullin, 0)
+
+            devnullout = os.open("/dev/null", os.O_WRONLY)
+            os.close(1)
+            os.dup2(devnullout, 1)
+            os.close(2)
+            os.dup2(devnullout, 2)
+            os.close(devnullout)
+
+            # Fork again to daemonize
+            if os.fork() > 0:
+                sys.exit(0)
+
+            try:
+                _do_send_emails(email_in)
+            except Exception:
+                import syslog
+                import traceback
+
+                syslog.openlog(os.path.basename(sys.argv[0]))
+                syslog.syslog(syslog.LOG_ERR, "Unexpected exception sending mail")
+                for line in traceback.format_exc().strip().split("\n"):
+                    syslog.syslog(syslog.LOG_ERR, line)
+
+            sys.exit(0)
+
+        email_file = os.fdopen(email_pipe[1], "w")
+    else:
+        # The email might not end with a newline, so add one. We'll
+        # strip the last line, if blank, when emails, so the net effect
+        # is to add a newline to messages without one
+        email_file.write("\n")
+        email_file.write(EMAIL_BOUNDARY)
+
+    return email_file
+
+# Finish an email started with start_email
+def end_email():
+    global email_file
+    email_file.flush()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]