Skip to content

Commit

Permalink
Merge branch 'master' of git://github.com/jpittman/buildbot
Browse files Browse the repository at this point in the history
* 'master' of git://github.com/jpittman/buildbot:
  Added OptionParser to manage command line options and args.  Also do some argument validation.
  • Loading branch information
Dustin J. Mitchell committed Mar 23, 2010
2 parents 3810328 + 37d2673 commit 9f73a5a
Showing 1 changed file with 168 additions and 45 deletions.
213 changes: 168 additions & 45 deletions contrib/svn_watcher.py
Expand Up @@ -4,7 +4,7 @@
# new revisions. It then uses the 'buildbot sendchange' command to deliver
# information about the Change to a (remote) buildmaster. It can be run from
# a cron job on a periodic basis, or can be told (with the 'watch' option) to
# automatically repeat its check every 10 minutes.
# automatically repeat its check every 10 minutes.

# This script does not store any state information, so to avoid spurious
# changes you must use the 'watch' option and let it run forever.
Expand All @@ -17,11 +17,15 @@
# 15.03.06 by John Pye
# 29.03.06 by Niklaus Giger, added support to run under windows,
# added invocation option
# 22.03.10 by Johnnie Pittman, added support for category and interval
# options.

import subprocess
import xml.dom.minidom
from xml.parsers.expat import ExpatError
import sys
import time
from optparse import OptionParser
import os


Expand All @@ -34,10 +38,80 @@ def getoutput(cmd):
return p.stdout.read()


def checkChanges(repo, master, verbose=False, oldRevision=-1):
def sendchange_cmd(master, revisionData):
cmd = [
"buildbot",
"sendchange",
"--master=%s" % master,
"--revision=%s" % revisionData['revision'],
"--username=%s" % revisionData['author'],
"--comments=%s" % revisionData['comments'],
]
if opts.category:
cmd.append("--category=%s" % opts.category)
for path in revisionData['paths']:
cmd.append(path)


if opts.verbose == True:
print cmd

return cmd

def parseChangeXML(raw_xml):
"""Parse the raw xml and return a dict with key pairs set.
Commmand we're parsing:
svn log --non-interactive --xml --verbose --limit=1 <repo url>
With an output that looks like this:
<?xml version="1.0"?>
<log>
<logentry revision="757">
<author>mwiggins</author>
<date>2009-11-11T17:16:48.012357Z</date>
<paths>
<path kind="" copyfrom-path="/trunk" copyfrom-rev="756" action="A">/tags/Latest</path>
</paths>
<msg>Updates/latest</msg>
</logentry>
</log>
"""

data = dict()

# parse the xml string and grab the first log entry.
try:
doc = xml.dom.minidom.parseString(raw_xml)
except ExpatError:
print "\nError: Got an empty response with an empty changeset.\n"
raise
log_entry = doc.getElementsByTagName("logentry")[0]

# grab the appropriate meta data we need
data['revision'] = log_entry.getAttribute("revision")
data['author'] = "".join([t.data for t in
log_entry.getElementsByTagName("author")[0].childNodes])
data['comments'] = "".join([t.data for t in
log_entry.getElementsByTagName("msg")[0].childNodes])

# grab the appropriate file paths that changed.
pathlist = log_entry.getElementsByTagName("paths")[0]
paths = []
for path in pathlist.getElementsByTagName("path"):
paths.append("".join([t.data for t in path.childNodes]))
data['paths'] = paths

return data


def checkChanges(repo, master, oldRevision=-1):
cmd = ["svn", "log", "--non-interactive", "--xml", "--verbose",
"--limit=1", repo]
if verbose == True:

if opts.verbose == True:
print "Getting last revision of repository: " + repo

if sys.platform == 'win32':
Expand All @@ -47,61 +121,110 @@ def checkChanges(repo, master, verbose=False, oldRevision=-1):
else:
xml1 = getoutput(cmd)

if verbose == True:
if opts.verbose == True:
print "XML\n-----------\n"+xml1+"\n\n"

doc = xml.dom.minidom.parseString(xml1)
el = doc.getElementsByTagName("logentry")[0]
revision = el.getAttribute("revision")
author = "".join([t.data for t in
el.getElementsByTagName("author")[0].childNodes])
comments = "".join([t.data for t in
el.getElementsByTagName("msg")[0].childNodes])
revisionData = parseChangeXML(xml1)

pathlist = el.getElementsByTagName("paths")[0]
paths = []
for p in pathlist.getElementsByTagName("path"):
paths.append("".join([t.data for t in p.childNodes]))

if verbose == True:
if opts.verbose == True:
print "PATHS"
print paths

if revision != oldRevision:
cmd = ["buildbot", "sendchange", "--master=%s"%master,
"--revision=%s"%revision, "--username=%s"%author,
"--comments=%s"%comments]
cmd += paths
print revisionData['paths']

if verbose == True:
print cmd
if revisionData['revision'] != oldRevision:

cmd = sendchange_cmd(master, revisionData)

if sys.platform == 'win32':
f = win32pipe.popen(cmd)
print time.strftime("%H.%M.%S ") + "Revision "+revision+ ": "+ \
''.join(f.readlines())
pretty_time = time.strftime("%H.%M.%S ")
print "%s Revision %s: %s" % (pretty_time, revisionData['revision'],
''.join(f.readlines()))
f.close()
else:
xml1 = getoutput(cmd)
else:
print time.strftime("%H.%M.%S ") + \
"nothing has changed since revision "+revision

return revision

pretty_time = time.strftime("%H.%M.%S ")
print "%s nothing has changed since revision %s" % (pretty_time,
revisionData['revision'])

return revisionData['revision']

def build_parser():
usagestr = "%prog [options] <repo url> <buildbot master:port>"
parser = OptionParser(usage=usagestr)

parser.add_option(
"-c", "--category", dest="category", action="store", default="",
help="""Store a category name to be associated with sendchange msg."""
)

parser.add_option(
"-i", "--interval", dest="interval", action="store", default=0,
help="Implies watch option and changes the time in minutes to the value specified.",
)

parser.add_option(
"-v", "--verbose", dest="verbose", action="store_true", default=False,
help="Enables more information to be presented on the command line.",
)

parser.add_option(
"", "--watch", dest="watch", action="store_true", default=False,
help="Automatically check the repo url every 10 minutes.",
)

return parser

def validate_args(args):
"""Validate our arguments and exit if we don't have what we want."""
if not args:
print "\nError: No arguments were specified.\n"
parser.print_help()
sys.exit(1)
elif len(args) > 2:
print "\nToo many arguments specified.\n"
parser.print_help()
sys.exit(2)


if __name__ == '__main__':
if len(sys.argv) == 4 and sys.argv[3] == 'watch':

# build our parser and validate our args
parser = build_parser()
(opts, args) = parser.parse_args()
validate_args(args)
if opts.interval:
try:
int(opts.interval)
except ValueError:
print "\nError: Value of the interval option must be a number."
parser.print_help()
sys.exit(3)

# grab what we need
repo_url = args[0]
bbmaster = args[1]

# if watch is specified, run until stopped
if opts.watch or opts.interval:
oldRevision = -1
print "Watching for changes in repo "+ sys.argv[1] +\
" master " + sys.argv[2]
print "Watching for changes in repo %s for master %s." % (repo_url, bbmaster)
while 1:
oldRevision = checkChanges(
sys.argv[1], sys.argv[2], False, oldRevision)
time.sleep(10*60) # Check the repository every 10 minutes

elif len(sys.argv) == 3:
checkChanges(sys.argv[1], sys.argv[2], True)
else:
print os.path.basename(
sys.argv[0]) + ": http://host/path/to/repo master:port [watch]"
try:
oldRevision = checkChanges(repo_url, bbmaster, oldRevision)
except ExpatError:
# had an empty changeset. Trapping the exception and moving on.
pass
try:
if opts.interval:
# Check the repository every interval in minutes the user specified.
time.sleep(int(opts.interval) * 60)
else:
# Check the repository every 10 minutes
time.sleep(10*60)
except KeyboardInterrupt:
print "\nReceived interrupt via keyboard. Shutting Down."
sys.exit(0)

# default action if watch isn't specified
checkChanges(repo_url, bbmaster)

0 comments on commit 9f73a5a

Please sign in to comment.