Modified: subversion/branches/gpg-agent-password-store/tools/dist/collect_sigs.py URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/tools/dist/collect_sigs.py?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/tools/dist/collect_sigs.py (original) +++ subversion/branches/gpg-agent-password-store/tools/dist/collect_sigs.py Thu Dec 2 20:55:08 2010 @@ -1,14 +1,131 @@ #!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# +# A script intended to be useful in helping to collect signatures for a +# release. This is a pretty rough, and patches are welcome to improve it. +# +# Some thoughts about future improvement: +# * Display of per-file and per-release statistics +# * Make use of the python-gpg package (http://code.google.com/p/python-gnupg/) +# * Post to IRC when a new signature is collected +# - Since we don't want to have a long running bot, perhaps we could +# also patch wayita to accept and then echo a privmsg? +# + +import sys, os +import sqlite3 + +def make_config(): + 'Output a blank config file' + + if os.path.exists('config.py'): + print "'config.py' already exists!'" + sys.exit(1) + + conf = open('config.py', 'w') + conf.write("version = ''\n") + conf.write("sigdir = ''\n") + conf.write("filesdir = ''\n") + conf.close() + + print "'config.py' generated" + +def make_db(): + 'Initialize a blank database' + + db = sqlite3.connect('sigs.db') + db.execute(''' + CREATE TABLE signatures ( + keyid TEXT, filename TEXT, signature BLOB, + UNIQUE(keyid,filename) + ); +'''); + +# This function is web-facing +def generate_asc_files(target_dir='.'): + fds = {} + def _open(filename): + if not fds.has_key(filename): + fd = open(os.path.join(target_dir, filename + '.asc'), 'w') + fds[filename] = fd + return fds[filename] + + db = sqlite3.connect(os.path.join(target_dir, 'sigs.db')) + curs = db.cursor() + curs.execute('SELECT filename, signature FROM signatures;') + for filename, signature in curs: + fd = _open(filename) + fd.write(signature + "\n") + + for fd in fds.values(): + fd.flush() + fd.close() + +actions = { + 'make_config' : make_config, + 'make_db' : make_db, + 'make_asc' : generate_asc_files, +} + + +if __name__ == '__main__': + if len(sys.argv) > 1: + if sys.argv[1] in actions: + actions[sys.argv[1]]() + sys.exit(0) + + +# Stuff below this line is the web-facing side +# ====================================================================== + import cgi import cgitb cgitb.enable() -import sys, os, string, subprocess, re +import string, subprocess, re + +try: + sys.path.append(os.path.dirname(sys.argv[0])) + import config +except: + print 'Content-type: text/plain' + print + print 'Cannot find config file' + sys.exit(1) -version = '1.6.13' r = re.compile('\[GNUPG\:\] GOODSIG (\w*) (.*)') +def files(): + for f in os.listdir(config.filesdir): + if config.version in f and (f.endswith('.tar.gz') or f.endswith('.zip') or f.endswith('.tar.bz2')): + yield f + +def ordinal(N): + try: + return [None, 'first', 'second', 'third', 'fourth', 'fifth', 'sixth'][N] + except: + # Huh? We only have six files to sign. + return "%dth" % N + shell_content = ''' <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> @@ -17,43 +134,67 @@ shell_content = ''' <title>Signature collection for Subversion $version</title> </head> <body style="font-size: 14pt; text-align: justify; - background-color: #f0f0f0; padding: 0 5%"> -<p>This page is used to collect signatures for the proposed release of -Apache Subversion $version.</p> + background-color: #f0f0f0; padding: 0 5%%"> +<p>This page is used to collect <a href="%s/list">signatures</a> for the +proposed release of Apache Subversion $version.</p> $content </body> </html> -''' +''' % os.getenv('SCRIPT_NAME') -def default_page(): - c = ''' -<form method="post"> -File: <select name="filename"> -%s -</select> -<br/> -<p>Paste signature in the area below:<br/> -<textarea name="signature" rows="10" cols="80"></textarea> +signature_area = ''' +<hr/> +<form method="post" action="%s"> +<p>Paste one or more signatures in the area below:<br/> +<textarea name="signatures" rows="20" cols="80"></textarea> </p> <input type="submit" value="Submit" /> +<p>Any text not between the <tt>BEGIN PGP SIGNATURE</tt> +and <tt>END PGP SIGNATURE</tt> lines will be ignored.</p> </form> +<hr/> +''' % os.getenv('SCRIPT_NAME') + + + +def split(sigs): + lines = [] + for line in sigs.split('\n'): + if lines or '--BEGIN' in line: + lines.append(line) + if '--END' in line: + yield "\n".join(lines) + "\n" + lines = [] + +def list_signatures(): + db = sqlite3.connect(os.path.join(config.sigdir, 'sigs.db')) + template = ''' +<hr/> +<p>The following signature files are available:</p> +<p>%s</p> ''' - contents = [f for f in os.listdir('.') - if f.endswith('.tar.gz') or f.endswith('.zip') - or f.endswith('.tar.bz2')] - contents.sort() + lines = "" + curs = db.cursor() + curs.execute('''SELECT filename, COUNT(*) FROM signatures + GROUP BY filename ORDER BY filename''') + for filename, count in curs: + lines += '<a href="%s/%s.asc">%s.asc</a>: %d signature%s<br/>\n' \ + % (os.getenv('SCRIPT_NAME'), filename, filename, + count, ['s', ''][count == 1]) + return (template % lines) + signature_area + +def save_valid_sig(db, filename, keyid, signature): + db.execute('INSERT OR REPLACE INTO signatures VALUES (?,?,?);', + (keyid, filename, buffer(signature))) + db.commit() - options = '' - for f in contents: - options = options + '<option value="%s">%s</option>\n' % (f, f) + generate_asc_files(config.sigdir) - return c % options - - -def verify_sig(signature, filename): +def verify_sig_for_file(signature, filename): args = ['gpg', '--logger-fd', '1', '--no-tty', - '--status-fd', '2', '--verify', '-', filename] + '--status-fd', '2', '--verify', '-', + os.path.join(config.filesdir, filename)] gpg = subprocess.Popen(args, stdin=subprocess.PIPE, @@ -74,54 +215,124 @@ def verify_sig(signature, filename): for line in lines: match = r.search(line) if match: - keyid = match.group(1)[-8:] + keyid = match.group(1) user = match.group(2) - return (True, (keyid, user)) - + return (True, (filename, keyid, user)) -def process_sig(signature, filename): +def verify_sig(signature): + all_failures = "" + for filename in files(): + (verified, result) = verify_sig_for_file(signature, filename) + if verified: + return (verified, result) + else: + all_failures += "%s:\n[[[\n%s]]]\n\n" % (filename, result) + return (False, all_failures) + +def process_sigs(signatures): + success = ''' + <p style="color: green;">All %d signatures verified!</p> +''' + failure = ''' + <p style="color: red;">%d of %d signatures failed to verify; details below.</p> +''' c_verified = ''' <p style="color: green;">The signature is verified!</p> <p>Filename: <code>%s</code></p> <p>Key ID: <code>%s</code></p> <p>User: <code>%s</code></p> <p>This signature has been saved, and will be included as part of the - release signatures. Please send mail to - <a href="mailto:[email protected]">[email protected]</a> - acknowledging your successful signature.</p> + release signatures.</p> ''' c_unverified = ''' <p style="color: red;">The signature was not able to be verified!</p> - <p>Filename: <code>%s</code></p> + <p>Signature: <pre>%s</pre></p> <p>Reason:</p><pre>%s</pre> <p>Please talk to the release manager if this is in error.</p> ''' - (verified, result) = verify_sig(signature, filename) - - if verified: - return c_verified % (filename, result[0], result[1]) + outcomes = [] + N_sigs = 0 + N_verified = 0 + retval = '' + + # Verify + db = sqlite3.connect(os.path.join(config.sigdir, 'sigs.db')) + for signature in split(signatures): + N_sigs += 1 + (verified, result) = verify_sig(signature) + outcomes.append((verified, result)) + + if verified: + (filename, keyid, user) = result + save_valid_sig(db, filename, keyid, signature) + N_verified += 1 + + # Output header + if N_verified == N_sigs: + retval += success % N_sigs else: - return c_unverified % (filename, result) + retval += failure % (N_sigs-N_verified, N_sigs) + # Output details + N = 0 + for outcome in outcomes: + N += 1 + (verified, result) = outcome + retval += "<h1>Results for the %s signature</h1>" % ordinal(N) + if verified: + (filename, keyid, user) = result + retval += c_verified % (filename, keyid[-8:], user) + else: + retval += c_unverified % (signature, result) + + return retval + signature_area + + +def cat_signatures(basename): + # strip '.asc' extension + assert basename[:-4] in files() + + # cat + ascfile = os.path.join(config.sigdir, basename) + if os.path.exists(ascfile): + return (open(ascfile, 'r').read()) -def main(): - print "Content-Type: text/html" +def print_content_type(mimetype): + print "Content-Type: " + mimetype print +def main(): form = cgi.FieldStorage() - if 'signature' not in form: - content = default_page() - else: - content = process_sig(form['signature'].value, form['filename'].value) + pathinfo = os.getenv('PATH_INFO') + + # default value, to be changed below + content = signature_area + + if 'signatures' in form: + content = process_sigs(form['signatures'].value) + + elif pathinfo and pathinfo[1:]: + basename = pathinfo.split('/')[-1] + + if basename == 'list': + content = list_signatures() + + elif basename[:-4] in files(): + # early exit; bypass 'content' entirely + print_content_type('text/plain') + print cat_signatures(basename) + return # These are "global" values, not specific to our action. mapping = { - 'version' : version, + 'version' : config.version, 'content' : content, } + print_content_type('text/html') + template = string.Template(shell_content) print template.safe_substitute(mapping)
Modified: subversion/branches/gpg-agent-password-store/tools/dist/construct-rolling-environment.sh URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/tools/dist/construct-rolling-environment.sh?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/tools/dist/construct-rolling-environment.sh (original) +++ subversion/branches/gpg-agent-password-store/tools/dist/construct-rolling-environment.sh Thu Dec 2 20:55:08 2010 @@ -53,11 +53,11 @@ TEMPDIR=$BASEDIR/temp case $LOCATION in US) APACHE_MIRROR=http://www.pangex.com/pub/apache - SOURCEFORGE_MIRROR=http://internap.dl.sourceforge.net/sourceforge + SOURCEFORGE_MIRROR=softlayer ;; UK) APACHE_MIRROR=http://apache.rmplc.co.uk - SOURCEFORGE_MIRROR=http://kent.dl.sourceforge.net/sourceforge + SOURCEFORGE_MIRROR=kent ;; *) echo "Unknown LOCATION" >&2 @@ -77,7 +77,7 @@ setup() { create_prefix() { wget -nc http://ftp.gnu.org/gnu/autoconf/$AUTOCONF.tar.bz2 wget -nc http://ftp.gnu.org/gnu/libtool/$LIBTOOL.tar.gz - wget -nc $SOURCEFORGE_MIRROR/swig/$SWIG.tar.gz + wget -nc "http://sourceforge.net/projects/swig/files/swig/$SWIG/$SWIG.tar.gz/download?use_mirror=$SOURCEFORGE_MIRROR" tar jxvf $AUTOCONF.tar.bz2 cd $AUTOCONF Modified: subversion/branches/gpg-agent-password-store/tools/dist/dist.sh URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/tools/dist/dist.sh?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/tools/dist/dist.sh (original) +++ subversion/branches/gpg-agent-password-store/tools/dist/dist.sh Thu Dec 2 20:55:08 2010 @@ -229,6 +229,10 @@ rm -f "$DISTPATH/STATUS" # (See http://svn.haxx.se/dev/archive-2009-04/0166.shtml for discussion.) rm -rf "$DISTPATH/contrib" +# Remove notes/ from our distribution tarball. It's large, but largely +# blue-sky and out-of-date, and of questionable use to end users. +rm -rf "$DISTPATH/notes" + # Remove packages/ from the tarball. # (See http://svn.haxx.se/dev/archive-2009-12/0205.shtml) rm -rf "$DISTPATH/packages" Modified: subversion/branches/gpg-agent-password-store/tools/hook-scripts/mailer/mailer.py URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/tools/hook-scripts/mailer/mailer.py?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/tools/hook-scripts/mailer/mailer.py (original) +++ subversion/branches/gpg-agent-password-store/tools/hook-scripts/mailer/mailer.py Thu Dec 2 20:55:08 2010 @@ -866,12 +866,16 @@ class DiffGenerator: content = src_fname = dst_fname = None else: src_fname, dst_fname = diff.get_files() - content = DiffContent(self.cfg.get_diff_cmd(self.group, { - 'label_from' : label1, - 'label_to' : label2, - 'from' : src_fname, - 'to' : dst_fname, - })) + try: + content = DiffContent(self.cfg.get_diff_cmd(self.group, { + 'label_from' : label1, + 'label_to' : label2, + 'from' : src_fname, + 'to' : dst_fname, + })) + except OSError: + # diff command does not exist, try difflib.unified_diff() + content = DifflibDiffContent(label1, label2, src_fname, dst_fname) # return a data item for this diff return _data( @@ -890,6 +894,33 @@ class DiffGenerator: content=content, ) +def _classify_diff_line(line, seen_change): + # classify the type of line. + first = line[:1] + ltype = '' + if first == '@': + seen_change = True + ltype = 'H' + elif first == '-': + if seen_change: + ltype = 'D' + else: + ltype = 'F' + elif first == '+': + if seen_change: + ltype = 'A' + else: + ltype = 'T' + elif first == ' ': + ltype = 'C' + else: + ltype = 'U' + + if line[-2] == '\r': + line=line[0:-2] + '\n' # remove carriage return + + return line, ltype, seen_change + class DiffContent: "This is a generator-like object returning annotated lines of a diff." @@ -917,36 +948,42 @@ class DiffContent: self.pipe = None raise IndexError - # classify the type of line. - first = line[:1] - if first == '@': - self.seen_change = True - ltype = 'H' - elif first == '-': - if self.seen_change: - ltype = 'D' - else: - ltype = 'F' - elif first == '+': - if self.seen_change: - ltype = 'A' - else: - ltype = 'T' - elif first == ' ': - ltype = 'C' - else: - ltype = 'U' + line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change) + return _data( + raw=line, + text=line[1:-1], # remove indicator and newline + type=ltype, + ) - if line[-2] == '\r': - line=line[0:-2] + '\n' # remove carriage return +class DifflibDiffContent(): + "This is a generator-like object returning annotated lines of a diff." + + def __init__(self, label_from, label_to, from_file, to_file): + import difflib + self.seen_change = False + fromlines = open(from_file, 'U').readlines() + tolines = open(to_file, 'U').readlines() + self.diff = difflib.unified_diff(fromlines, tolines, + label_from, label_to) + + def __nonzero__(self): + # we always have some items + return True + def __getitem__(self, idx): + + try: + line = self.diff.next() + except StopIteration: + raise IndexError + + line, ltype, self.seen_change = _classify_diff_line(line, self.seen_change) return _data( raw=line, text=line[1:-1], # remove indicator and newline type=ltype, ) - class TextCommitRenderer: "This class will render the commit mail in plain text." Modified: subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.conf.example URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.conf.example?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.conf.example (original) +++ subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.conf.example Thu Dec 2 20:55:08 2010 @@ -9,6 +9,8 @@ # [groups] group1 = user1 user2 user3 +group2 = user4 user5 +supergroup = @group1 @group2 user6 # # Example repository control, showing allowed syntax. @@ -20,13 +22,13 @@ group1 = user1 user2 user3 # - line breaks are accepted # [example1 groups] -group2 = user9 user10 +group3 = user9 user10 [example1] trunk/.* = *(add,remove,update) @group1,user4,user5(update) user6,user7() trunk/.* = user8(add,update) -tags/[^/]+/ = @group2(add) +tags/[^/]+/ = @group3(add) branches/[^/]+/.* = *(add,remove,update) # Modified: subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.py URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.py?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.py (original) +++ subversion/branches/gpg-agent-password-store/tools/hook-scripts/svnperms.py Thu Dec 2 20:55:08 2010 @@ -128,7 +128,17 @@ class Permission: def parse_groups(self, groupsiter): for option, value in groupsiter: - self._group[option] = value.split() + groupusers = [] + for token in value.split(): + # expand nested groups in place; no forward decls + if token[0] == "@": + try: + groupusers.extend(self._group[token[1:]]) + except KeyError: + raise Error, "group '%s' not found" % token[1:] + else: + groupusers.append(token) + self._group[option] = groupusers def parse_perms(self, permsiter): for option, value in permsiter: Modified: subversion/branches/gpg-agent-password-store/tools/server-side/svn-rep-sharing-stats.c URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/tools/server-side/svn-rep-sharing-stats.c?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/tools/server-side/svn-rep-sharing-stats.c (original) +++ subversion/branches/gpg-agent-password-store/tools/server-side/svn-rep-sharing-stats.c Thu Dec 2 20:55:08 2010 @@ -329,12 +329,10 @@ pretty_print(const char *name, for (hi = apr_hash_first(scratch_pool, reps_ref_counts); hi; hi = apr_hash_next(hi)) { - const struct key_t *key; struct value_t *value; SVN_ERR(cancel_func(NULL)); - key = svn__apr_hash_index_key(hi); value = svn__apr_hash_index_val(hi); SVN_ERR(svn_cmdline_printf(scratch_pool, "%s %" APR_UINT64_T_FMT " %s\n", name, value->refcount, Modified: subversion/branches/gpg-agent-password-store/win-tests.py URL: http://svn.apache.org/viewvc/subversion/branches/gpg-agent-password-store/win-tests.py?rev=1041580&r1=1041579&r2=1041580&view=diff ============================================================================== --- subversion/branches/gpg-agent-password-store/win-tests.py (original) +++ subversion/branches/gpg-agent-password-store/win-tests.py Thu Dec 2 20:55:08 2010 @@ -226,7 +226,6 @@ if run_httpd: base_url = 'http://localhost:' + str(httpd_port) if base_url: - all_tests = client_tests repo_loc = 'remote repository ' + base_url + '.' if base_url[:4] == 'http': log = 'dav-tests.log'
