[sysadmin-bin: 76/168] Split utility functions out of gnome-post-receive-email
- From: Andrea Veri <av src gnome org>
- To: gnome-sysadmin gnome org,commits-list gnome org
- Subject: [sysadmin-bin: 76/168] Split utility functions out of gnome-post-receive-email
- Date: Thu, 24 May 2012 19:57:50 +0000 (UTC)
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]