You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2013/10/15 10:52:18 UTC

svn commit: r1532250 [37/37] - in /subversion/branches/cache-server: ./ build/ build/ac-macros/ build/generator/ build/generator/swig/ build/generator/templates/ contrib/client-side/emacs/ contrib/hook-scripts/ contrib/server-side/fsfsfixer/ contrib/se...

Modified: subversion/branches/cache-server/tools/dist/release.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/dist/release.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/dist/release.py (original)
+++ subversion/branches/cache-server/tools/dist/release.py Tue Oct 15 08:52:06 2013
@@ -66,12 +66,33 @@ except ImportError:
     import ezt
 
 
+try:
+    subprocess.check_output
+except AttributeError:
+    def check_output(cmd):
+        proc = subprocess.Popen(['svn', 'list', dist_dev_url],
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+        (stdout, stderr) = proc.communicate()
+        rc = proc.wait()
+        if rc or stderr:
+            logging.error('%r failed with stderr %r', cmd, stderr)
+            raise subprocess.CalledProcessError(rc, cmd)
+        return stdout
+    subprocess.check_output = check_output
+    del check_output
+
 # Our required / recommended release tool versions by release branch
 tool_versions = {
   'trunk' : {
-            'autoconf' : '2.68',
-            'libtool'  : '2.4',
-            'swig'     : '2.0.4',
+            'autoconf' : '2.69',
+            'libtool'  : '2.4.2',
+            'swig'     : '2.0.9',
+  },
+  '1.8' : {
+            'autoconf' : '2.69',
+            'libtool'  : '2.4.2',
+            'swig'     : '2.0.9',
   },
   '1.7' : {
             'autoconf' : '2.68',
@@ -99,7 +120,7 @@ extns = ['zip', 'tar.gz', 'tar.bz2']
 # Utility functions
 
 class Version(object):
-    regex = re.compile('(\d+).(\d+).(\d+)(?:-(?:(rc|alpha|beta)(\d+)))?')
+    regex = re.compile(r'(\d+).(\d+).(\d+)(?:-(?:(rc|alpha|beta)(\d+)))?')
 
     def __init__(self, ver_str):
         # Special case the 'trunk-nightly' version
@@ -155,7 +176,7 @@ class Version(object):
         else:
             return self.pre_num < that.pre_num
 
-    def __str(self):
+    def __str__(self):
         if self.pre:
             if self.pre == 'nightly':
                 return 'nightly'
@@ -168,11 +189,7 @@ class Version(object):
 
     def __repr__(self):
 
-        return "Version('%s')" % self.__str()
-
-    def __str__(self):
-        return self.__str()
-
+        return "Version(%s)" % repr(str(self))
 
 def get_prefix(base_dir):
     return os.path.join(base_dir, 'prefix')
@@ -183,6 +200,13 @@ def get_tempdir(base_dir):
 def get_deploydir(base_dir):
     return os.path.join(base_dir, 'deploy')
 
+def get_target(args):
+    "Return the location of the artifacts"
+    if args.target:
+        return args.target
+    else:
+        return get_deploydir(args.base_dir)
+
 def get_tmpldir():
     return os.path.join(os.path.abspath(sys.path[0]), 'templates')
 
@@ -194,8 +218,7 @@ def get_tmplfile(filename):
         return urllib2.urlopen(repos + '/trunk/tools/dist/templates/' + filename)
 
 def get_nullfile():
-    # This is certainly not cross platform
-    return open('/dev/null', 'w')
+    return open(os.path.devnull, 'w')
 
 def run_script(verbose, script):
     if verbose:
@@ -371,12 +394,7 @@ def compare_changes(repos, branch, revis
     mergeinfo_cmd = ['svn', 'mergeinfo', '--show-revs=eligible',
                      repos + '/trunk/CHANGES',
                      repos + '/' + branch + '/' + 'CHANGES']
-    proc = subprocess.Popen(mergeinfo_cmd, stdout=subprocess.PIPE,
-                            stderr=subprocess.PIPE)
-    (stdout, stderr) = proc.communicate()
-    rc = proc.wait()
-    if stderr:
-      raise RuntimeError('svn mergeinfo failed: %s' % stderr)
+    stdout = subprocess.check_output(mergeinfo_cmd)
     if stdout:
       # Treat this as a warning since we are now putting entries for future
       # minor releases in CHANGES on trunk.
@@ -463,15 +481,11 @@ def sign_candidates(args):
     def sign_file(filename):
         asc_file = open(filename + '.asc', 'a')
         logging.info("Signing %s" % filename)
-        proc = subprocess.Popen(['gpg', '-ba', '-o', '-', filename],
-                              stdout=asc_file)
-        proc.wait()
+        proc = subprocess.check_call(['gpg', '-ba', '-o', '-', filename],
+                                     stdout=asc_file)
         asc_file.close()
 
-    if args.target:
-        target = args.target
-    else:
-        target = get_deploydir(args.base_dir)
+    target = get_target(args)
 
     for e in extns:
         filename = os.path.join(target, 'subversion-%s.%s' % (args.version, e))
@@ -488,17 +502,17 @@ def sign_candidates(args):
 def post_candidates(args):
     'Post candidate artifacts to the dist development directory.'
 
+    target = get_target(args)
+
     logging.info('Importing tarballs to %s' % dist_dev_url)
     svn_cmd = ['svn', 'import', '-m',
                'Add %s candidate release artifacts' % args.version.base,
                '--auto-props', '--config-option',
                'config:auto-props:*.asc=svn:eol-style=native;svn:mime-type=text/plain',
-               get_deploydir(args.base_dir), dist_dev_url]
+               target, dist_dev_url]
     if (args.username):
         svn_cmd += ['--username', args.username]
-    proc = subprocess.Popen(svn_cmd)
-    (stdout, stderr) = proc.communicate()
-    proc.wait()
+    subprocess.check_call(svn_cmd)
 
 #----------------------------------------------------------------------
 # Create tag
@@ -513,6 +527,7 @@ def create_tag(args):
     else:
         branch = secure_repos + '/branches/%d.%d.x' % (args.version.major,
                                                        args.version.minor)
+    target = get_target(args)
 
     tag = secure_repos + '/tags/' + str(args.version)
 
@@ -521,13 +536,63 @@ def create_tag(args):
     if (args.username):
         svnmucc_cmd += ['--username', args.username]
     svnmucc_cmd += ['cp', str(args.revnum), branch, tag]
-    svnmucc_cmd += ['put', os.path.join(get_deploydir(args.base_dir),
-                                        'svn_version.h.dist'),
+    svnmucc_cmd += ['put', os.path.join(target, 'svn_version.h.dist' + '-' +
+                                        str(args.version)),
                     tag + '/subversion/include/svn_version.h']
 
     # don't redirect stdout/stderr since svnmucc might ask for a password
-    proc = subprocess.Popen(svnmucc_cmd)
-    proc.wait()
+    subprocess.check_call(svnmucc_cmd)
+
+    if not args.version.is_prerelease():
+        logging.info('Bumping revisions on the branch')
+        def replace_in_place(fd, startofline, flat, spare):
+            """In file object FD, replace FLAT with SPARE in the first line
+            starting with STARTOFLINE."""
+
+            fd.seek(0, os.SEEK_SET)
+            lines = fd.readlines()
+            for i, line in enumerate(lines):
+                if line.startswith(startofline):
+                    lines[i] = line.replace(flat, spare)
+                    break
+            else:
+                raise RuntimeError('Definition of %r not found' % startofline)
+
+            fd.seek(0, os.SEEK_SET)
+            fd.writelines(lines)
+            fd.truncate() # for current callers, new value is never shorter.
+
+        new_version = Version('%d.%d.%d' %
+                              (args.version.major, args.version.minor,
+                               args.version.patch + 1))
+
+        def file_object_for(relpath):
+            fd = tempfile.NamedTemporaryFile()
+            url = branch + '/' + relpath
+            fd.url = url
+            subprocess.check_call(['svn', 'cat', '%s@%d' % (url, args.revnum)],
+                                  stdout=fd)
+            return fd
+
+        svn_version_h = file_object_for('subversion/include/svn_version.h')
+        replace_in_place(svn_version_h, '#define SVN_VER_PATCH ',
+                         str(args.version.patch), str(new_version.patch))
+
+        STATUS = file_object_for('STATUS')
+        replace_in_place(STATUS, 'Status of ',
+                         str(args.version), str(new_version))
+
+        svn_version_h.seek(0, os.SEEK_SET)
+        STATUS.seek(0, os.SEEK_SET)
+        subprocess.check_call(['svnmucc', '-r', str(args.revnum),
+                               '-m', 'Post-release housekeeping: '
+                                     'bump the %s branch to %s.'
+                               % (branch.split('/')[-1], str(new_version)),
+                               'put', svn_version_h.name, svn_version_h.url,
+                               'put', STATUS.name, STATUS.url,
+                              ])
+        del svn_version_h
+        del STATUS
 
 #----------------------------------------------------------------------
 # Clean dist
@@ -535,13 +600,7 @@ def create_tag(args):
 def clean_dist(args):
     'Clean the distribution directory of all but the most recent artifacts.'
 
-    proc = subprocess.Popen(['svn', 'list', dist_release_url],
-                            stdout=subprocess.PIPE,
-                            stderr=subprocess.PIPE)
-    (stdout, stderr) = proc.communicate()
-    proc.wait()
-    if stderr:
-      raise RuntimeError(stderr)
+    stdout = subprocess.check_output(['svn', 'list', dist_release_url])
 
     filenames = stdout.split('\n')
     tar_gz_archives = []
@@ -570,8 +629,7 @@ def clean_dist(args):
                 svnmucc_cmd += ['rm', dist_release_url + '/' + filename]
 
     # don't redirect stdout/stderr since svnmucc might ask for a password
-    proc = subprocess.Popen(svnmucc_cmd)
-    proc.wait()
+    subprocess.check_call(svnmucc_cmd)
 
 #----------------------------------------------------------------------
 # Move to dist
@@ -579,13 +637,7 @@ def clean_dist(args):
 def move_to_dist(args):
     'Move candidate artifacts to the distribution directory.'
 
-    proc = subprocess.Popen(['svn', 'list', dist_dev_url],
-                            stdout=subprocess.PIPE,
-                            stderr=subprocess.PIPE)
-    (stdout, stderr) = proc.communicate()
-    proc.wait()
-    if stderr:
-      raise RuntimeError(stderr)
+    stdout = subprocess.check_output(['svn', 'list', dist_dev_url])
 
     filenames = []
     for entry in stdout.split('\n'):
@@ -603,8 +655,7 @@ def move_to_dist(args):
 
     # don't redirect stdout/stderr since svnmucc might ask for a password
     logging.info('Moving release artifacts to %s' % dist_release_url)
-    proc = subprocess.Popen(svnmucc_cmd)
-    proc.wait()
+    subprocess.check_call(svnmucc_cmd)
 
 #----------------------------------------------------------------------
 # Write announcements
@@ -631,10 +682,7 @@ def write_news(args):
 def get_sha1info(args, replace=False):
     'Return a list of sha1 info for the release'
 
-    if args.target:
-        target = args.target
-    else:
-        target = get_deploydir(args.base_dir)
+    target = get_target(args)
 
     sha1s = glob.glob(os.path.join(target, 'subversion*-%s*.sha1' % args.version))
 
@@ -708,10 +756,7 @@ def get_siginfo(args, quiet=False):
         import _gnupg as gnupg
     gpg = gnupg.GPG()
 
-    if args.target:
-        target = args.target
-    else:
-        target = get_deploydir(args.base_dir)
+    target = get_target(args)
 
     good_sigs = {}
     fingerprints = {}
@@ -842,6 +887,9 @@ def main():
                     help='''The release label, such as '1.7.0-alpha1'.''')
     subparser.add_argument('--username',
                     help='''Username for ''' + dist_repos + '''.''')
+    subparser.add_argument('--target',
+                    help='''The full path to the directory containing
+                            release artifacts.''')
 
     # Setup the parser for the create-tag subcommand
     subparser = subparsers.add_parser('create-tag',
@@ -855,6 +903,9 @@ def main():
                     help='''The branch to base the release on.''')
     subparser.add_argument('--username',
                     help='''Username for ''' + secure_repos + '''.''')
+    subparser.add_argument('--target',
+                    help='''The full path to the directory containing
+                            release artifacts.''')
 
     # The clean-dist subcommand
     subparser = subparsers.add_parser('clean-dist',

Modified: subversion/branches/cache-server/tools/dist/templates/download.ezt
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/dist/templates/download.ezt?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/dist/templates/download.ezt (original)
+++ subversion/branches/cache-server/tools/dist/templates/download.ezt Tue Oct 15 08:52:06 2013
@@ -1,4 +1,4 @@
-<p style="font-size: 150%; text-align: center;">Subversion [version]</p>
+<p style="font-size: 150%; text-align: center;">Apache Subversion [version]</p>
 <table class="centered">
 <tr>
   <th>File</th>

Modified: subversion/branches/cache-server/tools/examples/blame.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/examples/blame.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/examples/blame.py (original)
+++ subversion/branches/cache-server/tools/examples/blame.py Tue Oct 15 08:52:06 2013
@@ -91,9 +91,9 @@ def blame(path, filename, rev=None):
 #    print ''.join(diffresult)
 #  print annotresult
   for x in range(len(annotresult.keys())):
-     sys.stdout.write("Line %d (rev %d):%s" % (x,
-                                               annotresult[x][0],
-                                               annotresult[x][1]))
+     sys.stdout.write("Line %d (r%d):%s" % (x,
+                                            annotresult[x][0],
+                                            annotresult[x][1]))
 
 def usage():
   print("USAGE: blame.py [-r REV] repos-path file")

Modified: subversion/branches/cache-server/tools/hook-scripts/mailer/mailer.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/hook-scripts/mailer/mailer.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/hook-scripts/mailer/mailer.py (original)
+++ subversion/branches/cache-server/tools/hook-scripts/mailer/mailer.py Tue Oct 15 08:52:06 2013
@@ -236,16 +236,30 @@ class MailedOutput(OutputBase):
                                and self.reply_to[2] == ']':
       self.reply_to = self.reply_to[3:]
 
+  def _rfc2047_encode(self, hdr):
+    # Return the result of splitting HDR into tokens (on space
+    # characters), encoding (per RFC2047) each token as necessary, and
+    # slapping 'em back to together again.
+    from email.Header import Header
+    
+    def _maybe_encode_header(hdr_token):
+      try:
+        hdr_token.encode('ascii')
+        return hdr_token
+      except UnicodeError:
+        return Header(hdr_token, 'utf-8').encode()
+
+    return ' '.join(map(_maybe_encode_header, hdr.split()))
+
   def mail_headers(self, group, params):
     from email import Utils
-    subject = self.make_subject(group, params)
-    try:
-      subject.encode('ascii')
-    except UnicodeError:
-      from email.Header import Header
-      subject = Header(subject, 'utf-8').encode()
-    hdrs = 'From: %s\n'    \
-           'To: %s\n'      \
+
+    subject  = self._rfc2047_encode(self.make_subject(group, params))
+    from_hdr = self._rfc2047_encode(self.from_addr)
+    to_hdr   = self._rfc2047_encode(', '.join(self.to_addrs))
+
+    hdrs = 'From: %s\n' \
+           'To: %s\n' \
            'Subject: %s\n' \
            'Date: %s\n' \
            'Message-ID: %s\n' \
@@ -256,7 +270,7 @@ class MailedOutput(OutputBase):
            'X-Svn-Commit-Author: %s\n' \
            'X-Svn-Commit-Revision: %d\n' \
            'X-Svn-Commit-Repository: %s\n' \
-           % (self.from_addr, ', '.join(self.to_addrs), subject,
+           % (from_hdr, to_hdr, subject,
               Utils.formatdate(), Utils.make_msgid(), group,
               self.repos.author or 'no_author', self.repos.rev,
               os.path.basename(self.repos.repos_dir))

Modified: subversion/branches/cache-server/tools/server-side/fsfs-stats.c
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/fsfs-stats.c?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/fsfs-stats.c (original)
+++ subversion/branches/cache-server/tools/server-side/fsfs-stats.c Tue Oct 15 08:52:06 2013
@@ -28,6 +28,7 @@
 #include <apr_file_io.h>
 #include <apr_poll.h>
 
+#include "svn_private_config.h"
 #include "svn_pools.h"
 #include "svn_diff.h"
 #include "svn_io.h"
@@ -270,6 +271,12 @@ typedef struct fs_fs_t
   /* history of sizes of changed nodes */
   histogram_t node_size_histogram;
 
+  /* history of representation sizes */
+  histogram_t added_rep_size_histogram;
+
+  /* history of sizes of changed nodes */
+  histogram_t added_node_size_histogram;
+
   /* history of unused representations */
   histogram_t unused_rep_histogram;
 
@@ -486,6 +493,7 @@ add_to_histogram(histogram_t *histogram,
 
 /* Update data aggregators in FS with this representation of type KIND, on-
  * disk REP_SIZE and expanded node size EXPANDED_SIZE for PATH in REVSION.
+ * PLAIN_ADDED indicates whether the node has a deltification predecessor.
  */
 static void
 add_change(fs_fs_t *fs,
@@ -493,7 +501,8 @@ add_change(fs_fs_t *fs,
            apr_int64_t expanded_size,
            svn_revnum_t revision,
            const char *path,
-           rep_kind_t kind)
+           rep_kind_t kind,
+           svn_boolean_t plain_added)
 {
   /* identify largest reps */
   if (rep_size >= fs->largest_changes->min_size)
@@ -522,6 +531,12 @@ add_change(fs_fs_t *fs,
   add_to_histogram(&fs->rep_size_histogram, rep_size);
   add_to_histogram(&fs->node_size_histogram, expanded_size);
 
+  if (plain_added)
+    {
+      add_to_histogram(&fs->added_rep_size_histogram, rep_size);
+      add_to_histogram(&fs->added_node_size_histogram, expanded_size);
+    }
+
   /* specific histograms by type */
   switch (kind)
     {
@@ -1240,7 +1255,7 @@ parse_dir(fs_fs_t *fs,
       next = current ? strchr(++current, '\n') : NULL;
       if (next == NULL)
         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-           _("Corrupt directory representation in rev %ld at offset %ld"),
+           _("Corrupt directory representation in r%ld at offset %ld"),
                                  representation->revision,
                                  (long)representation->offset);
 
@@ -1288,6 +1303,7 @@ read_noderev(fs_fs_t *fs,
   representation_t *props = NULL;
   apr_size_t start_offset = offset;
   svn_boolean_t is_dir = FALSE;
+  svn_boolean_t has_predecessor = FALSE;
   const char *path = "???";
 
   scratch_pool = svn_pool_create(scratch_pool);
@@ -1348,15 +1364,17 @@ read_noderev(fs_fs_t *fs,
         }
       else if (key_matches(&key, "cpath"))
         path = value.data;
+      else if (key_matches(&key, "pred"))
+        has_predecessor = TRUE;
     }
 
   /* record largest changes */
   if (text && text->ref_count == 1)
     add_change(fs, (apr_int64_t)text->size, (apr_int64_t)text->expanded_size,
-               text->revision, path, text->kind);
+               text->revision, path, text->kind, !has_predecessor);
   if (props && props->ref_count == 1)
     add_change(fs, (apr_int64_t)props->size, (apr_int64_t)props->expanded_size,
-               props->revision, path, props->kind);
+               props->revision, path, props->kind, !has_predecessor);
 
   /* if this is a directory and has not been processed, yet, read and
    * process it recursively */
@@ -1573,7 +1591,9 @@ read_revisions(fs_fs_t **fs,
                                             svn_cache__get_global_membuffer_cache(),
                                             NULL, NULL,
                                             sizeof(window_cache_key_t),
-                                            "", FALSE, pool));
+                                            "",
+                                            SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
+                                            FALSE, pool));
 
   /* read all packed revs */
   for ( revision = start_revision
@@ -2043,6 +2063,7 @@ print_stats(fs_fs_t *fs,
   printf(_("%20s bytes in %12s representations total\n"
            "%20s bytes in %12s directory representations\n"
            "%20s bytes in %12s file representations\n"
+           "%20s bytes in %12s representations of added file nodes\n"
            "%20s bytes in %12s directory property representations\n"
            "%20s bytes in %12s file property representations\n"
            "%20s bytes in header & footer overhead\n"),
@@ -2052,6 +2073,8 @@ print_stats(fs_fs_t *fs,
          svn__i64toa_sep(dir_rep_stats.total.count, ',', pool),
          svn__i64toa_sep(file_rep_stats.total.packed_size, ',', pool),
          svn__i64toa_sep(file_rep_stats.total.count, ',', pool),
+         svn__i64toa_sep(fs->added_rep_size_histogram.total.sum, ',', pool),
+         svn__i64toa_sep(fs->added_rep_size_histogram.total.count, ',', pool),
          svn__i64toa_sep(dir_prop_rep_stats.total.packed_size, ',', pool),
          svn__i64toa_sep(dir_prop_rep_stats.total.count, ',', pool),
          svn__i64toa_sep(file_prop_rep_stats.total.packed_size, ',', pool),
@@ -2169,13 +2192,13 @@ int main(int argc, const char *argv[])
   svn_err = read_revisions(&fs, repo_path, start_revision, memsize, pool);
   printf("\n");
 
-  print_stats(fs, pool);
-
   if (svn_err)
     {
       svn_handle_error2(svn_err, stdout, FALSE, ERROR_TAG);
       return 2;
     }
 
+  print_stats(fs, pool);
+
   return 0;
 }

Modified: subversion/branches/cache-server/tools/server-side/svn-populate-node-origins-index.c
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svn-populate-node-origins-index.c?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svn-populate-node-origins-index.c (original)
+++ subversion/branches/cache-server/tools/server-side/svn-populate-node-origins-index.c Tue Oct 15 08:52:06 2013
@@ -77,7 +77,8 @@ index_revision_adds(int *count, svn_fs_t
 
   *count = 0;
   SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
-  SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
+  SVN_ERR(svn_fs_paths_changed3(&changes, root,
+                                svn_move_behavior_explicit_moves, pool));
 
   /* No paths changed in this revision?  Nothing to do.  */
   if (apr_hash_count(changes) == 0)
@@ -94,7 +95,9 @@ index_revision_adds(int *count, svn_fs_t
       apr_hash_this(hi, &path, NULL, &val);
       change = val;
       if ((change->change_kind == svn_fs_path_change_add)
-          || (change->change_kind == svn_fs_path_change_replace))
+          || (change->change_kind == svn_fs_path_change_replace)
+          || (change->change_kind == svn_fs_path_change_move)
+          || (change->change_kind == svn_fs_path_change_movereplace))
         {
           if (! (change->copyfrom_path
                             && SVN_IS_VALID_REVNUM(change->copyfrom_rev)))

Modified: subversion/branches/cache-server/tools/server-side/svn-rep-sharing-stats.c
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svn-rep-sharing-stats.c?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svn-rep-sharing-stats.c (original)
+++ subversion/branches/cache-server/tools/server-side/svn-rep-sharing-stats.c Tue Oct 15 08:52:06 2013
@@ -33,6 +33,7 @@
 #include "../../subversion/libsvn_fs_fs/fs_fs.h"
 /* for svn_fs_fs__id_* (used in assertions only) */
 #include "../../subversion/libsvn_fs_fs/id.h"
+#include "../../subversion/libsvn_fs_fs/cached_data.h"
 
 #include "private/svn_cmdline_private.h"
 
@@ -95,7 +96,8 @@ check_lib_versions(void)
     };
   SVN_VERSION_DEFINE(my_version);
 
-  return svn_error_trace(svn_ver_check_list(&my_version, checklist));
+  return svn_error_trace(svn_ver_check_list2(&my_version, checklist,
+                                             svn_ver_equal));
 }
 
 
@@ -182,7 +184,8 @@ struct key_t
 /* What we need to know about a rep. */
 struct value_t
 {
-  svn_checksum_t *sha1_checksum;
+  svn_checksum_t checksum;
+  unsigned char sha1_digest[APR_SHA1_DIGESTSIZE];
   apr_uint64_t refcount;
 };
 
@@ -200,7 +203,7 @@ static svn_error_t *record(apr_hash_t *r
    * exist or doesn't have the checksum we are after.  (The latter case
    * often corresponds to node_rev->kind == svn_node_dir.)
    */
-  if (records == NULL || rep == NULL || rep->sha1_checksum == NULL)
+  if (records == NULL || rep == NULL || !rep->has_sha1)
     return SVN_NO_ERROR;
 
   /* Construct the key.
@@ -215,17 +218,19 @@ static svn_error_t *record(apr_hash_t *r
   if ((value = apr_hash_get(records, key, sizeof(*key))))
     {
       /* Paranoia. */
-      SVN_ERR_ASSERT(value->sha1_checksum != NULL);
-      SVN_ERR_ASSERT(svn_checksum_match(value->sha1_checksum,
-                                        rep->sha1_checksum));
+      SVN_ERR_ASSERT(memcmp(value->sha1_digest,
+                            rep->sha1_digest,
+                            sizeof(value->sha1_digest)));
       /* Real work. */
       value->refcount++;
     }
   else
     {
       value = apr_palloc(result_pool, sizeof(*value));
-      value->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, result_pool);
+      value->checksum.digest = value->sha1_digest;
+      value->checksum.kind = svn_checksum_sha1;
       value->refcount = 1;
+      memcpy(value->sha1_digest, rep->sha1_digest, sizeof(value->sha1_digest));
     }
 
   /* Store them. */
@@ -264,7 +269,9 @@ process_one_revision(svn_fs_t *fs,
 
   /* Get the changed paths. */
   SVN_ERR(svn_fs_revision_root(&rev_root, fs, revnum, scratch_pool));
-  SVN_ERR(svn_fs_paths_changed2(&paths_changed, rev_root, scratch_pool));
+  SVN_ERR(svn_fs_paths_changed3(&paths_changed, rev_root,
+                                svn_move_behavior_explicit_moves,
+                                scratch_pool));
 
   /* Iterate them. */
   /* ### use iterpool? */
@@ -340,7 +347,7 @@ pretty_print(const char *name,
       SVN_ERR(svn_cmdline_printf(scratch_pool, "%s %" APR_UINT64_T_FMT " %s\n",
                                  name, value->refcount,
                                  svn_checksum_to_cstring_display(
-                                   value->sha1_checksum,
+                                   &value->checksum,
                                    scratch_pool)));
     }
 

Modified: subversion/branches/cache-server/tools/server-side/svnauthz.c
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svnauthz.c?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svnauthz.c (original)
+++ subversion/branches/cache-server/tools/server-side/svnauthz.c Tue Oct 15 08:52:06 2013
@@ -59,7 +59,9 @@ static const apr_getopt_option_t options
   {"repository", svnauthz__repos, 1, ("repository authz name")},
   {"transaction", 't', 1, ("transaction id")},
   {"is", svnauthz__is, 1,
-    ("instead of outputing, tests if the access is ARG\n"
+    ("instead of outputting, test if the access is\n"
+     "                             "
+     "exactly ARG\n"
      "                             "
      "ARG can be one of the following values:\n"
      "                             "
@@ -67,10 +69,12 @@ static const apr_getopt_option_t options
      "                             "
      "    r    read-only access\n"
      "                             "
-     "   no    no access\n")
+     "   no    no access")
   },
-  {"groups-file", svnauthz__groups_file, 1, ("path to the global groups file")},
-  {"recursive", 'R', 0, ("recursive access to path")},
+  {"groups-file", svnauthz__groups_file, 1,
+   ("use the groups from file ARG")},
+  {"recursive", 'R', 0,
+   ("determine recursive access to PATH")},
   {0, 0, 0, 0}
 };
 
@@ -129,27 +133,32 @@ static const svn_opt_subcommand_desc2_t 
    {'t'} },
   {"accessof", subcommand_accessof, {0} /* no aliases */,
    ("Print or test the permissions set by an authz file.\n"
-    "usage: 1. svnauthz accessof [--username USER] [--groups-file GROUPS_FILE] TARGET\n"
-    "       2. svnauthz accessof [--username USER] [--groups-file GROUPS_FILE] \\\n"
-    "                            -t TXN REPOS_PATH FILE_PATH\n\n"
-    "  1. Prints the access of USER based on TARGET.\n"
+    "usage: 1. svnauthz accessof TARGET\n"
+    "       2. svnauthz accessof -t TXN REPOS_PATH FILE_PATH\n"
+    "\n"
+    "  1. Prints the access of USER to PATH based on authorization file at TARGET.\n"
     "     TARGET can be a path to a file or an absolute file:// URL to an authz\n"
-    "     file in a repository, but cannot be a repository relative URL (^/).\n\n"
-    "  2. Prints the access of USER based on authz file at FILE_PATH in the\n"
-    "     transaction TXN in the repository at REPOS_PATH.\n\n"
-    "  If the --username argument is omitted then access of an anonymous user\n"
-    "  will be printed.  If --path argument is omitted prints if any access\n"
-    "  to the repo is allowed.  If --groups-file is specified, the groups from\n"
-    "  GROUPS_FILE will be used.\n\n"
+    "     file in a repository, but cannot be a repository relative URL (^/).\n"
+    "\n"
+    "  2. Prints the access of USER to PATH based on authz file at FILE_PATH in the\n"
+    "     transaction TXN in the repository at REPOS_PATH.\n"
+    "\n"
+    "  USER is the argument to the --username option; if that option is not\n"
+    "  provided, then access of an anonymous user will be printed or tested.\n"
+    "\n"
+    "  PATH is the argument to the --path option; if that option is not provided,\n"
+    "  the maximal access to any path in the repository will be considered.\n"
+    "\n"
     "Outputs one of the following:\n"
     "     rw    write access (which also implies read)\n"
     "      r    read access\n"
-    "     no    no access\n\n"
+    "     no    no access\n"
+    "\n"
     "Returns:\n"
-    "    0   when syntax is OK and --is argument (if any) matches.\n"
+    "    0   when syntax is OK and '--is' argument (if any) matches.\n"
     "    1   when syntax is invalid.\n"
     "    2   operational error\n"
-    "    3   when --is argument doesn't match\n"
+    "    3   when '--is' argument doesn't match\n"
     ),
    {'t', svnauthz__username, svnauthz__path, svnauthz__repos, svnauthz__is,
     svnauthz__groups_file, 'R'} },

Modified: subversion/branches/cache-server/tools/server-side/svnpubsub/commit-hook.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svnpubsub/commit-hook.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svnpubsub/commit-hook.py (original)
+++ subversion/branches/cache-server/tools/server-side/svnpubsub/commit-hook.py Tue Oct 15 08:52:06 2013
@@ -19,8 +19,10 @@
 SVNLOOK="/usr/local/svn-install/current/bin/svnlook"
 #SVNLOOK="/usr/local/bin/svnlook"
 
+HOST="127.0.0.1"
+PORT=2069
+
 import sys
-import subprocess
 try:
     import simplejson as json
 except ImportError:
@@ -28,35 +30,32 @@ except ImportError:
 
 import urllib2
 
-HOST="127.0.0.1"
-PORT=2069
-
-def svncmd(cmd):
-    return subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
+import svnpubsub.util
 
-def svncmd_uuid(repo):
-    cmd = "%s uuid %s" % (SVNLOOK, repo)
-    p = svncmd(cmd)
-    return p.stdout.read().strip()
-
-def svncmd_info(repo, revision):
-    cmd = "%s info -r %s %s" % (SVNLOOK, revision, repo)
-    p = svncmd(cmd)
-    data = p.stdout.read().split("\n")
+def svnlook(cmd, **kwargs):
+    args = [SVNLOOK] + cmd
+    return svnpubsub.util.check_output(args, **kwargs)
+
+def svnlook_uuid(repo):
+    cmd = ["uuid", "--", repo]
+    return svnlook(cmd).strip()
+
+def svnlook_info(repo, revision):
+    cmd = ["info", "-r", revision, "--", repo]
+    data = svnlook(cmd, universal_newlines=True).split("\n")
     #print data
     return {'author': data[0].strip(),
             'date': data[1].strip(),
             'log': "\n".join(data[3:]).strip()}
 
-def svncmd_changed(repo, revision):
-    cmd = "%s changed -r %s %s" % (SVNLOOK, revision, repo)
-    p = svncmd(cmd)
+def svnlook_changed(repo, revision):
+    cmd = ["changed", "-r", revision, "--", repo]
+    lines = svnlook(cmd, universal_newlines=True).split("\n")
     changed = {}
-    while True:
-        line = p.stdout.readline()
-        if not line:
-            break
+    for line in lines:
         line = line.strip()
+        if not line:
+            continue
         (flags, filename) = (line[0:3], line[4:])
         changed[filename] = {'flags': flags}
     return changed
@@ -71,23 +70,23 @@ def do_put(body):
 
 def main(repo, revision):
     revision = revision.lstrip('r')
-    i = svncmd_info(repo, revision)
+    i = svnlook_info(repo, revision)
     data = {'type': 'svn',
             'format': 1,
             'id': int(revision),
             'changed': {},
-            'repository': svncmd_uuid(repo),
+            'repository': svnlook_uuid(repo),
             'committer': i['author'],
             'log': i['log'],
             'date': i['date'],
             }
-    data['changed'].update(svncmd_changed(repo, revision))
+    data['changed'].update(svnlook_changed(repo, revision))
     body = json.dumps(data)
     do_put(body)
 
 if __name__ == "__main__":
-    if len(sys.argv) != 3:
-        print "invalid args"
-        sys.exit(0)
+    if len(sys.argv) not in (3, 4):
+        sys.stderr.write("invalid args\n")
+        sys.exit(1)
 
-    main(sys.argv[1], sys.argv[2])
+    main(*sys.argv[1:3])

Modified: subversion/branches/cache-server/tools/server-side/svnpubsub/daemonize.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svnpubsub/daemonize.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svnpubsub/daemonize.py (original)
+++ subversion/branches/cache-server/tools/server-side/svnpubsub/daemonize.py Tue Oct 15 08:52:06 2013
@@ -24,6 +24,7 @@ import os
 import signal
 import sys
 import time
+import multiprocessing  # requires Python 2.6
 
 
 # possible return values from Daemon.daemonize()
@@ -50,11 +51,11 @@ class Daemon(object):
   def daemonize_exit(self):
     try:
       result = self.daemonize()
-    except (ChildFailed, DaemonFailed) as e:
+    except (ChildFailed, DaemonFailed), e:
       # duplicate the exit code
       sys.exit(e.code)
     except (ChildTerminatedAbnormally, ChildForkFailed,
-            DaemonTerminatedAbnormally, DaemonForkFailed) as e:
+            DaemonTerminatedAbnormally, DaemonForkFailed), e:
       sys.stderr.write('ERROR: %s\n' % e)
       sys.exit(1)
     except ChildResumedIncorrectly:
@@ -71,29 +72,41 @@ class Daemon(object):
     # in original process. daemon is up and running. we're done.
 
   def daemonize(self):
-    # fork off a child that can detach itself from this process.
-    try:
-      pid = os.fork()
-    except OSError as e:
-      raise ChildForkFailed(e.errno, e.strerror)
-
-    if pid > 0:
-      # we're in the parent. let's wait for the child to finish setting
-      # things up -- on our exit, we want to ensure the child is accepting
-      # connections.
-      cpid, status = os.waitpid(pid, 0)
-      assert pid == cpid
-      if os.WIFEXITED(status):
-        code = os.WEXITSTATUS(status)
-        if code:
-          raise ChildFailed(code)
-        return DAEMON_RUNNING
-
-      # the child did not exit cleanly.
-      raise ChildTerminatedAbnormally(status)
+    ### review error situations. map to backwards compat. ??
+    ### be mindful of daemonize_exit().
+    ### we should try and raise ChildFailed / ChildTerminatedAbnormally.
+    ### ref: older revisions. OR: remove exceptions.
+
+    child_is_ready = multiprocessing.Event()
+    child_completed = multiprocessing.Event()
+
+    p = multiprocessing.Process(target=self._first_child,
+                                args=(child_is_ready, child_completed))
+    p.start()
+    
+    # Wait for the child to finish setting things up (in case we need
+    # to communicate with it). It will only exit when ready.
+    ### use a timeout here! (parameterized, of course)
+    p.join()
+
+    ### need to propagate errors, to adjust the return codes
+    if child_completed.is_set():
+      ### what was the exit status?
+      return DAEMON_COMPLETE
+    if child_is_ready.is_set():
+      return DAEMON_RUNNING
+
+    ### how did we get here?! the immediate child should not exit without
+    ### signalling ready/complete. some kind of error.
+    return DAEMON_STARTED
 
+  def _first_child(self, child_is_ready, child_completed):
     # we're in the child.
 
+    ### NOTE: the original design was a bit bunk. Exceptions raised from
+    ### this point are within the child processes. We need to signal the
+    ### errors to the parent in other ways.
+
     # decouple from the parent process
     os.chdir('/')
     os.umask(0)
@@ -102,56 +115,86 @@ class Daemon(object):
     # remember this pid so the second child can signal it.
     thispid = os.getpid()
 
-    # register a signal handler so the SIGUSR1 doesn't stop the process.
-    # this object will also record whether if got signalled.
-    daemon_accepting = SignalCatcher(signal.SIGUSR1)
-
-    # if the daemon process exits before sending SIGUSR1, then we need to see
-    # the problem. trap SIGCHLD with a SignalCatcher.
+    # if the daemon process exits before signalling readiness, then we
+    # need to see the problem. trap SIGCHLD with a SignalCatcher.
     daemon_exit = SignalCatcher(signal.SIGCHLD)
 
     # perform the second fork
     try:
       pid = os.fork()
-    except OSError as e:
+    except OSError, e:
+      ### this won't make it to the parent process
       raise DaemonForkFailed(e.errno, e.strerror)
 
     if pid > 0:
       # in the parent.
 
-      # we want to wait for the daemon to signal that it has created and
-      # bound the socket, and is (thus) ready for connections. if the
-      # daemon improperly exits before serving, we'll see SIGCHLD and the
-      # .pause will return.
-      ### we should add a timeout to this. allow an optional parameter to
-      ### specify the timeout, in case it takes a long time to start up.
-      signal.pause()
+
+      # Wait for the child to be ready for operation.
+      while True:
+        # The readiness event will invariably be signalled early/first.
+        # If it *doesn't* get signalled because the child has prematurely
+        # exited, then we will pause 10ms before noticing the exit. The
+        # pause is acceptable since that is aberrant/unexpected behavior.
+        ### is there a way to break this wait() on a signal such as SIGCHLD?
+        ### parameterize this wait, in case the app knows children may
+        ### fail quickly?
+        if child_is_ready.wait(timeout=0.010):
+          # The child signalled readiness. Yay!
+          break
+        if daemon_exit.signalled:
+          # Whoops. The child exited without signalling :-(
+          break
+        # Python 2.6 compat: .wait() may exit when set, but return None
+        if child_is_ready.is_set():
+          break
+        # A simple timeout. The child is taking a while to prepare. Go
+        # back and wait for readiness.
 
       if daemon_exit.signalled:
+        # Tell the parent that the child has exited.
+        ### we need to communicate the exit status, if possible.
+        child_completed.set()
+
         # reap the daemon process, getting its exit code. bubble it up.
         cpid, status = os.waitpid(pid, 0)
         assert pid == cpid
         if os.WIFEXITED(status):
           code = os.WEXITSTATUS(status)
           if code:
+            ### this won't make it to the parent process
             raise DaemonFailed(code)
+          ### this return value is ignored
           return DAEMON_NOT_RUNNING
 
         # the daemon did not exit cleanly.
+        ### this won't make it to the parent process
         raise DaemonTerminatedAbnormally(status)
 
-      if daemon_accepting.signalled:
-        # the daemon is up and running, so save the pid and return success.
-        if self.pidfile:
-          open(self.pidfile, 'w').write('%d\n' % pid)
-        return DAEMON_STARTED
+      # child_is_ready got asserted. the daemon is up and running, so
+      # save the pid and return success.
+      if self.pidfile:
+        # Be wary of symlink attacks
+        try:
+          os.remove(self.pidfile)
+        except OSError:
+          pass
+        fd = os.open(self.pidfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0444)
+        os.write(fd, '%d\n' % pid)
+        os.close(fd)
 
+      ### this return value is ignored
+      return DAEMON_STARTED
+
+      ### old code. what to do with this? throw ChildResumedIncorrectly
+      ### or just toss this and the exception.
       # some other signal popped us out of the pause. the daemon might not
       # be running.
+      ### this won't make it to the parent process
       raise ChildResumedIncorrectly()
 
-    # we're a deamon now. get rid of the final remnants of the parent.
-    # start by restoring default signal handlers
+    # we're a daemon now. get rid of the final remnants of the parent:
+    # restore the signal handlers and switch std* to the proper files.
     signal.signal(signal.SIGUSR1, signal.SIG_DFL)
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
     sys.stdout.flush()
@@ -169,30 +212,31 @@ class Daemon(object):
     so.close()
     se.close()
 
-    # TEST: don't release the parent immediately. the whole parent stack
-    #       should pause along with this sleep.
+    ### TEST: don't release the parent immediately. the whole parent stack
+    ###       should pause along with this sleep.
     #time.sleep(10)
 
     # everything is set up. call the initialization function.
     self.setup()
 
-    # sleep for one second before signalling. we want to make sure the
-    # parent has called signal.pause()
-    ### we should think of a better wait around the race condition.
-    time.sleep(1)
+    ### TEST: exit before signalling.
+    #sys.exit(0)
+    #sys.exit(1)
 
-    # okay. the daemon is ready. signal the parent to tell it we're set.
-    os.kill(thispid, signal.SIGUSR1)
+    # the child is now ready for parent/anyone to communicate with it.
+    child_is_ready.set()
 
     # start the daemon now.
     self.run()
 
     # The daemon is shutting down, so toss the pidfile.
-    try:
-      os.remove(self.pidfile)
-    except OSError:
-      pass
+    if self.pidfile:
+      try:
+        os.remove(self.pidfile)
+      except OSError:
+        pass
 
+    ### this return value is ignored
     return DAEMON_COMPLETE
 
   def setup(self):
@@ -202,6 +246,34 @@ class Daemon(object):
     raise NotImplementedError
 
 
+class _Detacher(Daemon):
+  def __init__(self, target, logfile='/dev/null', pidfile=None,
+               args=(), kwargs={}):
+    Daemon.__init__(self, logfile, pidfile)
+    self.target = target
+    self.args = args
+    self.kwargs = kwargs
+
+  def setup(self):
+    pass
+
+  def run(self):
+    self.target(*self.args, **self.kwargs)
+
+    
+def run_detached(target, *args, **kwargs):
+  """Simple function to run TARGET as a detached daemon.
+  
+  The additional arguments/keywords will be passed along. This function
+  does not return -- sys.exit() will be called as appropriate.
+  
+  (capture SystemExit if logging/reporting is necessary)
+  ### if needed, a variant of this func could be written to not exit
+  """
+  d = _Detacher(target, args=args, kwargs=kwargs)
+  d.daemonize_exit()
+
+
 class SignalCatcher(object):
   def __init__(self, signum):
     self.signalled = False

Modified: subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/client.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/client.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/client.py (original)
+++ subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/client.py Tue Oct 15 08:52:06 2013
@@ -62,7 +62,8 @@ class SvnpubsubClientException(Exception
 
 class Client(asynchat.async_chat):
 
-  def __init__(self, url, commit_callback, event_callback):
+  def __init__(self, url, commit_callback, event_callback,
+               metadata_callback = None):
     asynchat.async_chat.__init__(self)
 
     self.last_activity = time.time()
@@ -82,7 +83,8 @@ class Client(asynchat.async_chat):
 
     self.event_callback = event_callback
 
-    self.parser = JSONRecordHandler(commit_callback, event_callback)
+    self.parser = JSONRecordHandler(commit_callback, event_callback,
+                                    metadata_callback)
 
     # Wait for the end of headers. Then we start parsing JSON.
     self.set_terminator(b'\r\n\r\n')
@@ -126,36 +128,50 @@ class Client(asynchat.async_chat):
       self.ibuffer.append(data)
 
 
+class Notification(object):
+  def __init__(self, data):
+    self.__dict__.update(data)
+
+class Commit(Notification):
+  KIND = 'COMMIT'
+
+class Metadata(Notification):
+  KIND = 'METADATA'
+
+
 class JSONRecordHandler:
-  def __init__(self, commit_callback, event_callback):
+  def __init__(self, commit_callback, event_callback, metadata_callback):
     self.commit_callback = commit_callback
     self.event_callback = event_callback
+    self.metadata_callback = metadata_callback
+
+  EXPECTED_VERSION = 1
 
   def feed(self, record):
     obj = json.loads(record)
     if 'svnpubsub' in obj:
       actual_version = obj['svnpubsub'].get('version')
-      EXPECTED_VERSION = 1
-      if actual_version != EXPECTED_VERSION:
-        raise SvnpubsubClientException("Unknown svnpubsub format: %r != %d"
-                                       % (actual_format, expected_format))
+      if actual_version != self.EXPECTED_VERSION:
+        raise SvnpubsubClientException(
+          "Unknown svnpubsub format: %r != %d"
+          % (actual_version, self.EXPECTED_VERSION))
       self.event_callback('version', obj['svnpubsub']['version'])
     elif 'commit' in obj:
       commit = Commit(obj['commit'])
       self.commit_callback(commit)
     elif 'stillalive' in obj:
       self.event_callback('ping', obj['stillalive'])
-
-
-class Commit(object):
-  def __init__(self, commit):
-    self.__dict__.update(commit)
+    elif 'metadata' in obj and self.metadata_callback:
+      metadata = Metadata(obj['metadata'])
+      self.metadata_callback(metadata)
 
 
 class MultiClient(object):
-  def __init__(self, urls, commit_callback, event_callback):
+  def __init__(self, urls, commit_callback, event_callback,
+               metadata_callback = None):
     self.commit_callback = commit_callback
     self.event_callback = event_callback
+    self.metadata_callback = metadata_callback
 
     # No target time, as no work to do
     self.target_time = 0
@@ -185,9 +201,15 @@ class MultiClient(object):
   def _add_channel(self, url):
     # Simply instantiating the client will install it into the global map
     # for processing in the main event loop.
-    Client(url,
-           functools.partial(self.commit_callback, url),
-           functools.partial(self._reconnect, url))
+    if self.metadata_callback:
+      Client(url,
+             functools.partial(self.commit_callback, url),
+             functools.partial(self._reconnect, url),
+             functools.partial(self.metadata_callback, url))
+    else:
+      Client(url,
+             functools.partial(self.commit_callback, url),
+             functools.partial(self._reconnect, url))
 
   def _check_stale(self):
     now = time.time()

Modified: subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/server.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/server.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/server.py (original)
+++ subversion/branches/cache-server/tools/server-side/svnpubsub/svnpubsub/server.py Tue Oct 15 08:52:06 2013
@@ -25,20 +25,27 @@
 # Instead of using a complicated XMPP/AMPQ/JMS/super messaging service,
 # we have simple HTTP GETs and PUTs to get data in and out.
 #
-# Currently supports both XML and JSON serialization.
+# Currently supports JSON serialization.
 #
 # Example Sub clients:
-#   curl -sN http://127.0.0.1:2069/commits
-#   curl -sN http://127.0.0.1:2069/commits/svn/*
-#   curl -sN http://127.0.0.1:2069/commits/svn
-#   curl -sN http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68
-#   curl -sN http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
+#   curl -sN  http://127.0.0.1:2069/commits
+#   curl -sN 'http://127.0.0.1:2069/commits/svn/*'
+#   curl -sN  http://127.0.0.1:2069/commits/svn
+#   curl -sN 'http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68'
+#   curl -sN  http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
 #
-#   URL is built into 2 parts:
-#       /commits/${optional_type}/${optional_repository}
+#   curl -sN  http://127.0.0.1:2069/metadata
+#   curl -sN 'http://127.0.0.1:2069/metadata/svn/*'
+#   curl -sN  http://127.0.0.1:2069/metadata/svn
+#   curl -sN 'http://127.0.0.1:2069/metadata/*/13f79535-47bb-0310-9956-ffa450edef68'
+#   curl -sN  http://127.0.0.1:2069/metadata/svn/13f79535-47bb-0310-9956-ffa450edef68
 #
-#   If the type is included in the URL, you will only get commits of that type.
-#   The type can be * and then you will receive commits of any type.
+#   URLs are constructed from 3 parts:
+#       /${notification}/${optional_type}/${optional_repository}
+#
+#   Notifications can be sent for commits or metadata (e.g., revprop) changes.
+#   If the type is included in the URL, you will only get notifications of that type.
+#   The type can be * and then you will receive notifications of any type.
 #
 #   If the repository is included in the URL, you will only receive
 #   messages about that repository.  The repository can be * and then you
@@ -71,7 +78,7 @@ from twisted.python import log
 
 import time
 
-class Commit:
+class Notification(object):
     def __init__(self, r):
         self.__dict__.update(r)
         if not self.check_value('repository'):
@@ -86,7 +93,16 @@ class Commit:
     def check_value(self, k):
         return hasattr(self, k) and self.__dict__[k]
 
-    def render_commit(self):
+    def render(self):
+        raise NotImplementedError
+
+    def render_log(self):
+        raise NotImplementedError
+
+class Commit(Notification):
+    KIND = 'COMMIT'
+
+    def render(self):
         obj = {'commit': {}}
         obj['commit'].update(self.__dict__)
         return json.dumps(obj)
@@ -96,20 +112,32 @@ class Commit:
             paths_changed = " %d paths changed" % len(self.changed)
         except:
             paths_changed = ""
-        return "%s:%s repo '%s' id '%s'%s" % (self.type,
-                                  self.format,
-                                  self.repository,
-                                  self.id,
-                                  paths_changed)
+        return "commit %s:%s repo '%s' id '%s'%s" % (
+            self.type, self.format, self.repository, self.id,
+            paths_changed)
+
+class Metadata(Notification):
+    KIND = 'METADATA'
+
+    def render(self):
+        obj = {'metadata': {}}
+        obj['metadata'].update(self.__dict__)
+        return json.dumps(obj)
+
+    def render_log(self):
+        return "metadata %s:%s repo '%s' id '%s' revprop '%s'" % (
+            self.type, self.format, self.repository, self.id,
+            self.revprop['name'])
 
 
 HEARTBEAT_TIME = 15
 
 class Client(object):
-    def __init__(self, pubsub, r, type, repository):
+    def __init__(self, pubsub, r, kind, type, repository):
         self.pubsub = pubsub
         r.notifyFinish().addErrback(self.finished)
         self.r = r
+        self.kind = kind
         self.type = type
         self.repository = repository
         self.alive = True
@@ -123,11 +151,14 @@ class Client(object):
         except ValueError:
             pass
 
-    def interested_in(self, commit):
-        if self.type and self.type != commit.type:
+    def interested_in(self, notification):
+        if self.kind != notification.KIND:
             return False
 
-        if self.repository and self.repository != commit.repository:
+        if self.type and self.type != notification.type:
+            return False
+
+        if self.repository and self.repository != notification.repository:
             return False
 
         return True
@@ -152,7 +183,8 @@ class Client(object):
         self.r.write(str(input))
 
     def write_start(self):
-        self.r.setHeader('content-type', 'application/json')
+        self.r.setHeader('X-SVNPubSub-Version', '1')
+        self.r.setHeader('content-type', 'application/vnd.apache.vc-notify+json')
         self.write('{"svnpubsub": {"version": 1}}\n\0')
 
     def write_heartbeat(self):
@@ -163,6 +195,13 @@ class SvnPubSub(resource.Resource):
     isLeaf = True
     clients = []
 
+    __notification_uri_map = {'commits': Commit.KIND,
+                              'metadata': Metadata.KIND}
+
+    def __init__(self, notification_class):
+        resource.Resource.__init__(self)
+        self.__notification_class = notification_class
+
     def cc(self):
         return len(self.clients)
 
@@ -182,6 +221,11 @@ class SvnPubSub(resource.Resource):
             request.setResponseCode(400)
             return "Invalid path\n"
 
+        kind = self.__notification_uri_map.get(uri[1], None)
+        if kind is None:
+            request.setResponseCode(400)
+            return "Invalid path\n"
+
         if uri_len >= 3:
           type = uri[2]
 
@@ -194,17 +238,18 @@ class SvnPubSub(resource.Resource):
         if repository == '*':
           repository = None
 
-        c = Client(self, request, type, repository)
+        c = Client(self, request, kind, type, repository)
         self.clients.append(c)
         c.start()
         return twisted.web.server.NOT_DONE_YET
 
-    def notifyAll(self, commit):
-        data = commit.render_commit()
+    def notifyAll(self, notification):
+        data = notification.render()
 
-        log.msg("COMMIT: %s (%d clients)" % (commit.render_log(), self.cc()))
+        log.msg("%s: %s (%d clients)"
+                % (notification.KIND, notification.render_log(), self.cc()))
         for client in self.clients:
-            if client.interested_in(commit):
+            if client.interested_in(notification):
                 client.write_data(data)
 
     def render_PUT(self, request):
@@ -217,19 +262,23 @@ class SvnPubSub(resource.Resource):
         #import pdb;pdb.set_trace()
         #print "input: %s" % (input)
         try:
-            c = json.loads(input)
-            commit = Commit(c)
+            data = json.loads(input)
+            notification = self.__notification_class(data)
         except ValueError as e:
             request.setResponseCode(400)
-            log.msg("COMMIT: failed due to: %s" % str(e))
-            return str(e)
-        self.notifyAll(commit)
+            errstr = str(e)
+            log.msg("%s: failed due to: %s" % (notification.KIND, errstr))
+            return errstr
+        self.notifyAll(notification)
         return "Ok"
 
+
 def svnpubsub_server():
     root = resource.Resource()
-    s = SvnPubSub()
-    root.putChild("commits", s)
+    c = SvnPubSub(Commit)
+    m = SvnPubSub(Metadata)
+    root.putChild('commits', c)
+    root.putChild('metadata', m)
     return server.Site(root)
 
 if __name__ == "__main__":

Modified: subversion/branches/cache-server/tools/server-side/svnpubsub/svnwcsub.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svnpubsub/svnwcsub.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svnpubsub/svnwcsub.py (original)
+++ subversion/branches/cache-server/tools/server-side/svnpubsub/svnwcsub.py Tue Oct 15 08:52:06 2013
@@ -69,27 +69,22 @@ except ImportError:
 
 import daemonize
 import svnpubsub.client
-
-# check_output() is only available in Python 2.7. Allow us to run with
-# earlier versions
-try:
-    check_output = subprocess.check_output
-except AttributeError:
-    def check_output(args, env):  # note: we only use these two args
-        pipe = subprocess.Popen(args, stdout=subprocess.PIPE, env=env)
-        output, _ = pipe.communicate()
-        if pipe.returncode:
-            raise subprocess.CalledProcessError(pipe.returncode, args)
-        return output
+import svnpubsub.util
 
 assert hasattr(subprocess, 'check_call')
 def check_call(*args, **kwds):
-    """Wrapper around subprocess.check_call() that logs stderr upon failure."""
+    """Wrapper around subprocess.check_call() that logs stderr upon failure,
+    with an optional list of exit codes to consider non-failure."""
     assert 'stderr' not in kwds
+    if '__okayexits' in kwds:
+        __okayexits = kwds['__okayexits']
+        del kwds['__okayexits']
+    else:
+        __okayexits = set([0]) # EXIT_SUCCESS
     kwds.update(stderr=subprocess.PIPE)
     pipe = subprocess.Popen(*args, **kwds)
     output, errput = pipe.communicate()
-    if pipe.returncode:
+    if pipe.returncode not in __okayexits:
         cmd = args[0] if len(args) else kwds.get('args', '(no command)')
         # TODO: log stdout too?
         logging.error('Command failed: returncode=%d command=%r stderr=%r',
@@ -103,7 +98,7 @@ def check_call(*args, **kwds):
 def svn_info(svnbin, env, path):
     "Run 'svn info' on the target path, returning a dict of info data."
     args = [svnbin, "info", "--non-interactive", "--", path]
-    output = check_output(args, env=env).strip()
+    output = svnpubsub.util.check_output(args, env=env).strip()
     info = { }
     for line in output.split('\n'):
         idx = line.index(':')
@@ -303,6 +298,21 @@ class BackgroundWorker(threading.Thread)
 
         logging.info("updating: %s", wc.path)
 
+        ## Run the hook
+        HEAD = svn_info(self.svnbin, self.env, wc.url)['Revision']
+        if self.hook:
+            hook_mode = ['pre-update', 'pre-boot'][boot]
+            logging.info('running hook: %s at %s',
+                         wc.path, hook_mode)
+            args = [self.hook, hook_mode, wc.path, HEAD, wc.url]
+            rc = check_call(args, env=self.env, __okayexits=[0, 1])
+            if rc == 1:
+                # TODO: log stderr
+                logging.warn('hook denied update of %s at %s',
+                             wc.path, hook_mode)
+                return
+            del rc
+
         ### we need to move some of these args into the config. these are
         ### still specific to the ASF setup.
         args = [self.svnbin, 'switch',
@@ -313,12 +323,13 @@ class BackgroundWorker(threading.Thread)
                 '--config-option',
                 'config:miscellany:use-commit-times=on',
                 '--',
-                wc.url,
+                wc.url + '@' + HEAD,
                 wc.path]
         check_call(args, env=self.env)
 
         ### check the loglevel before running 'svn info'?
         info = svn_info(self.svnbin, self.env, wc.path)
+        assert info['Revision'] == HEAD
         logging.info("updated: %s now at r%s", wc.path, info['Revision'])
 
         ## Run the hook
@@ -465,7 +476,15 @@ def handle_options(options):
     # Otherwise, we should write this (foreground) PID into the file.
     if options.pidfile and not options.daemon:
         pid = os.getpid()
-        open(options.pidfile, 'w').write('%s\n' % pid)
+        # Be wary of symlink attacks
+        try:
+            os.remove(options.pidfile)
+        except OSError:
+            pass
+        fd = os.open(options.pidfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL,
+                     0444)
+        os.write(fd, '%d\n' % pid)
+        os.close(fd)
         logging.info('pid %d written to %s', pid, options.pidfile)
 
     if options.gid:
@@ -525,7 +544,8 @@ def main(args):
 
     # We manage the logfile ourselves (along with possible rotation). The
     # daemon process can just drop stdout/stderr into /dev/null.
-    d = Daemon('/dev/null', options.pidfile, options.umask, bdec)
+    d = Daemon('/dev/null', os.path.abspath(options.pidfile),
+               options.umask, bdec)
     if options.daemon:
         # Daemonize the process and call sys.exit() with appropriate code
         d.daemonize_exit()

Modified: subversion/branches/cache-server/tools/server-side/svnpubsub/watcher.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/tools/server-side/svnpubsub/watcher.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/tools/server-side/svnpubsub/watcher.py (original)
+++ subversion/branches/cache-server/tools/server-side/svnpubsub/watcher.py Tue Oct 15 08:52:06 2013
@@ -35,6 +35,9 @@ def _commit(url, commit):
   print('COMMIT: from %s' % url)
   pprint.pprint(vars(commit), indent=2)
 
+def _metadata(url, metadata):
+  print('METADATA: from %s' % url)
+  pprint.pprint(vars(metadata), indent=2)
 
 def _event(url, event_name, event_arg):
   if event_arg:
@@ -44,7 +47,7 @@ def _event(url, event_name, event_arg):
 
 
 def main(urls):
-  mc = svnpubsub.client.MultiClient(urls, _commit, _event)
+  mc = svnpubsub.client.MultiClient(urls, _commit, _event, _metadata)
   mc.run_forever()
 
 

Modified: subversion/branches/cache-server/win-tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/cache-server/win-tests.py?rev=1532250&r1=1532249&r2=1532250&view=diff
==============================================================================
--- subversion/branches/cache-server/win-tests.py (original)
+++ subversion/branches/cache-server/win-tests.py Tue Oct 15 08:52:06 2013
@@ -84,6 +84,8 @@ def _usage_exit():
   print("  --disable-bulk-updates : Disable bulk updates on HTTP server")
   print("  --ssl-cert             : Path to SSL server certificate to trust.")
   print("  --javahl               : Run the javahl tests instead of the normal tests")
+  print("  --swig=language        : Run the swig perl/python/ruby tests instead of")
+  print("                           the normal tests")
   print("  --list                 : print test doc strings only")
   print("  --milestone-filter=RE  : RE is a regular expression pattern that (when")
   print("                           used with --list) limits the tests listed to")
@@ -108,29 +110,24 @@ CMDLINE_TEST_SCRIPT_NATIVE_PATH = CMDLIN
 sys.path.insert(0, os.path.join('build', 'generator'))
 sys.path.insert(1, 'build')
 
-import gen_win
+import gen_win_dependencies
+import gen_base
 version_header = os.path.join('subversion', 'include', 'svn_version.h')
 cp = configparser.ConfigParser()
 cp.read('gen-make.opts')
-gen_obj = gen_win.GeneratorBase('build.conf', version_header,
-                                cp.items('options'))
+gen_obj = gen_win_dependencies.GenDependenciesBase('build.conf', version_header,
+                                                   cp.items('options'))
 all_tests = gen_obj.test_progs + gen_obj.bdb_test_progs \
           + gen_obj.scripts + gen_obj.bdb_scripts
 client_tests = [x for x in all_tests if x.startswith(CMDLINE_TEST_SCRIPT_PATH)]
 
-svn_dlls = []
-for section in gen_obj.sections.values():
-  if section.options.get("msvc-export"):
-    dll_basename = section.name + "-" + str(gen_obj.version) + ".dll"
-    svn_dlls.append(os.path.join("subversion", section.name, dll_basename))
-
 opts, args = my_getopt(sys.argv[1:], 'hrdvqct:pu:f:',
                        ['release', 'debug', 'verbose', 'quiet', 'cleanup',
                         'test=', 'url=', 'svnserve-args=', 'fs-type=', 'asp.net-hack',
                         'httpd-dir=', 'httpd-port=', 'httpd-daemon',
                         'httpd-server', 'http-short-circuit', 'httpd-no-log',
                         'disable-http-v2', 'disable-bulk-updates', 'help',
-                        'fsfs-packing', 'fsfs-sharding=', 'javahl',
+                        'fsfs-packing', 'fsfs-sharding=', 'javahl', 'swig=',
                         'list', 'enable-sasl', 'bin=', 'parallel',
                         'config-file=', 'server-minor-version=', 'log-level=',
                         'log-to-stdout', 'mode-filter=', 'milestone-filter=',
@@ -156,6 +153,7 @@ http_bulk_updates = True
 list_tests = None
 milestone_filter = None
 test_javahl = None
+test_swig = None
 enable_sasl = None
 svn_bin = None
 parallel = None
@@ -216,6 +214,11 @@ for opt, val in opts:
     fsfs_packing = 1
   elif opt == '--javahl':
     test_javahl = 1
+  elif opt == '--swig':
+    if val not in ['perl', 'python', 'ruby']:
+      sys.stderr.write('Running \'%s\' swig tests not supported (yet).\n' 
+                        % (val,))
+    test_swig = val
   elif opt == '--list':
     list_tests = 1
   elif opt == '--milestone-filter':
@@ -289,12 +292,18 @@ def create_target_dir(dirname):
       print("mkdir: %s" % tgt_dir)
     os.makedirs(tgt_dir)
 
-def copy_changed_file(src, tgt):
+def copy_changed_file(src, tgt=None, to_dir=None, cleanup=True):
   if not os.path.isfile(src):
     print('Could not find ' + src)
     sys.exit(1)
-  if os.path.isdir(tgt):
-    tgt = os.path.join(tgt, os.path.basename(src))
+
+  if to_dir and not tgt:
+    tgt = os.path.join(to_dir, os.path.basename(src))
+  elif not tgt or (tgt and to_dir):
+    raise RuntimeError("Using 'tgt' *or* 'to_dir' is required" % (tgt,))
+  elif tgt and os.path.isdir(tgt):
+    raise RuntimeError("'%s' is a directory. Use to_dir=" % (tgt,))
+
   if os.path.exists(tgt):
     assert os.path.isfile(tgt)
     if filecmp.cmp(src, tgt):
@@ -306,57 +315,35 @@ def copy_changed_file(src, tgt):
     print("copy: %s" % src)
     print("  to: %s" % tgt)
   shutil.copy(src, tgt)
-  return 1
 
-def copy_execs(baton, dirname, names):
-  copied_execs = baton
-  for name in names:
-    if not name.endswith('.exe'):
-      continue
-    src = os.path.join(dirname, name)
-    tgt = os.path.join(abs_builddir, dirname, name)
-    create_target_dir(dirname)
-    if copy_changed_file(src, tgt):
-      copied_execs.append(tgt)
+  if cleanup:
+    copied_execs.append(tgt)
 
 def locate_libs():
   "Move DLLs to a known location and set env vars"
 
-  dlls = []
-
-  # look for APR 1.x dll's and use those if found
-  apr_test_path = os.path.join(gen_obj.apr_path, objdir, 'libapr-1.dll')
-  if os.path.exists(apr_test_path):
-    suffix = "-1"
-  else:
-    suffix = ""
-
-  if cp.has_option('options', '--with-static-apr'):
-    dlls.append(os.path.join(gen_obj.apr_path, objdir,
-                             'libapr%s.dll' % (suffix)))
-    dlls.append(os.path.join(gen_obj.apr_util_path, objdir,
-                             'libaprutil%s.dll' % (suffix)))
-
-  if gen_obj.libintl_path is not None:
-    dlls.append(os.path.join(gen_obj.libintl_path, 'bin', 'intl3_svn.dll'))
-
-  if gen_obj.bdb_lib is not None:
-    partial_path = os.path.join(gen_obj.bdb_path, 'bin', gen_obj.bdb_lib)
-    if objdir == 'Debug':
-      dlls.append(partial_path + 'd.dll')
-    else:
-      dlls.append(partial_path + '.dll')
-
-  if gen_obj.sasl_path is not None:
-    dlls.append(os.path.join(gen_obj.sasl_path, 'lib', 'libsasl.dll'))
+  debug = (objdir == 'Debug')
+  
+  for lib in gen_obj._libraries.values():
+
+    if debug:
+      name, dir = lib.debug_dll_name, lib.debug_dll_dir
+    else:
+      name, dir = lib.dll_name, lib.dll_dir
+      
+    if name and dir:
+      src = os.path.join(dir, name)
+      if os.path.exists(src):
+        copy_changed_file(src, to_dir=abs_builddir, cleanup=False)
 
-  for dll in dlls:
-    copy_changed_file(dll, abs_objdir)
 
   # Copy the Subversion library DLLs
-  if not cp.has_option('options', '--disable-shared'):
-    for svn_dll in svn_dlls:
-      copy_changed_file(os.path.join(abs_objdir, svn_dll), abs_objdir)
+  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+    if isinstance(i, gen_base.TargetLib) and i.msvc_export:
+      src = os.path.join(abs_objdir, i.filename)
+      if os.path.isfile(src):
+        copy_changed_file(src, to_dir=abs_builddir,
+                          cleanup=False)
 
   # Copy the Apache modules
   if run_httpd and cp.has_option('options', '--with-httpd'):
@@ -367,11 +354,11 @@ def locate_libs():
     mod_dontdothat_path = os.path.join(abs_objdir, 'tools', 'server-side',
                                         'mod_dontdothat', 'mod_dontdothat.so')
 
-    copy_changed_file(mod_dav_svn_path, abs_objdir)
-    copy_changed_file(mod_authz_svn_path, abs_objdir)
-    copy_changed_file(mod_dontdothat_path, abs_objdir)
+    copy_changed_file(mod_dav_svn_path, to_dir=abs_builddir, cleanup=False)
+    copy_changed_file(mod_authz_svn_path, to_dir=abs_builddir, cleanup=False)
+    copy_changed_file(mod_dontdothat_path, to_dir=abs_builddir, cleanup=False)
 
-  os.environ['PATH'] = abs_objdir + os.pathsep + os.environ['PATH']
+  os.environ['PATH'] = abs_builddir + os.pathsep + os.environ['PATH']
 
 def fix_case(path):
     path = os.path.normpath(path)
@@ -396,7 +383,7 @@ class Svnserve:
     self.path = os.path.join(abs_objdir,
                              'subversion', 'svnserve', self.name)
     self.root = os.path.join(abs_builddir, CMDLINE_TEST_SCRIPT_NATIVE_PATH)
-    self.proc_handle = None
+    self.proc = None
 
   def __del__(self):
     "Stop svnserve when the object is deleted"
@@ -414,26 +401,18 @@ class Svnserve:
     else:
       args = [self.name] + self.args
     print('Starting %s %s' % (self.kind, self.name))
-    try:
-      import win32process
-      import win32con
-      args = ' '.join([self._quote(x) for x in args])
-      self.proc_handle = (
-        win32process.CreateProcess(self._quote(self.path), args,
-                                   None, None, 0,
-                                   win32con.CREATE_NEW_CONSOLE,
-                                   None, None, win32process.STARTUPINFO()))[0]
-    except ImportError:
-      os.spawnv(os.P_NOWAIT, self.path, args)
+
+    self.proc = subprocess.Popen([self.path] + args[1:])
 
   def stop(self):
-    if self.proc_handle is not None:
+    if self.proc is not None:
       try:
-        import win32process
         print('Stopping %s' % self.name)
-        win32process.TerminateProcess(self.proc_handle, 0)
+        self.proc.poll();
+        if self.proc.returncode is None:
+          self.proc.kill();
         return
-      except ImportError:
+      except AttributeError:
         pass
     print('Svnserve.stop not implemented')
 
@@ -456,7 +435,7 @@ class Httpd:
       self.bulkupdates_option = 'off'
 
     self.service = service
-    self.proc_handle = None
+    self.proc = None
     self.path = os.path.join(self.httpd_dir, 'bin', self.name)
 
     if short_circuit:
@@ -600,6 +579,12 @@ class Httpd:
 
   def _create_dontdothat_file(self):
     "Create empty mime.types file"
+    # If the tests have not previously been run or were cleaned
+    # up, then 'svn-test-work' does not exist yet.
+    parent_dir = os.path.dirname(self.dontdothat_file)
+    if not os.path.exists(parent_dir):
+      os.makedirs(parent_dir)
+
     fp = open(self.dontdothat_file, 'w')
     fp.write('[recursive-actions]\n')
     fp.write('/ = deny\n')
@@ -610,7 +595,7 @@ class Httpd:
     return 'LoadModule ' + name + " " + self._quote(full_path) + '\n'
 
   def _svn_module(self, name, path):
-    full_path = os.path.join(self.abs_objdir, path)
+    full_path = os.path.join(self.abs_builddir, path)
     return 'LoadModule ' + name + ' ' + self._quote(full_path) + '\n'
 
   def _svn_repo(self, name):
@@ -674,46 +659,33 @@ class Httpd:
     "Start HTTPD as daemon"
     print('Starting httpd as daemon')
     print(self.httpd_args)
-    try:
-      import win32process
-      import win32con
-      args = ' '.join([self._quote(x) for x in self.httpd_args])
-      self.proc_handle = (
-        win32process.CreateProcess(self._quote(self.path), args,
-                                   None, None, 0,
-                                   win32con.CREATE_NEW_CONSOLE,
-                                   None, None, win32process.STARTUPINFO()))[0]
-    except ImportError:
-      os.spawnv(os.P_NOWAIT, self.path, self.httpd_args)
+    self.proc = subprocess.Popen([self.path] + self.httpd_args[1:])
 
   def _stop_daemon(self):
     "Stop the HTTPD daemon"
-    if self.proc_handle is not None:
+    if self.proc is not None:
       try:
-        import win32process
         print('Stopping %s' % self.name)
-        win32process.TerminateProcess(self.proc_handle, 0)
+        self.proc.poll();
+        if self.proc.returncode is None:
+          self.proc.kill();
         return
-      except ImportError:
+      except AttributeError:
         pass
     print('Httpd.stop_daemon not implemented')
 
 # Move the binaries to the test directory
+create_target_dir(abs_builddir)
 locate_libs()
 if create_dirs:
-  old_cwd = os.getcwd()
-  try:
-    os.chdir(abs_objdir)
-    baton = copied_execs
-    for dirpath, dirs, files in os.walk('subversion'):
-      copy_execs(baton, dirpath, files)
-    for dirpath, dirs, files in os.walk('tools/server-side'):
-      copy_execs(baton, dirpath, files)
-  except:
-    os.chdir(old_cwd)
-    raise
-  else:
-    os.chdir(old_cwd)
+  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+    if isinstance(i, gen_base.TargetExe):
+      src = os.path.join(abs_objdir, i.filename)
+
+      if os.path.isfile(src):
+        dst = os.path.join(abs_builddir, i.filename)
+        create_target_dir(os.path.dirname(dst))
+        copy_changed_file(src, dst)
 
 # Create the base directory for Python tests
 create_target_dir(CMDLINE_TEST_SCRIPT_NATIVE_PATH)
@@ -771,7 +743,7 @@ else:
   print('Testing %s configuration on %s' % (objdir, repo_loc))
 sys.path.insert(0, os.path.join(abs_srcdir, 'build'))
 
-if not test_javahl:
+if not test_javahl and not test_swig:
   import run_tests
   if log_to_stdout:
     log_file = None
@@ -799,7 +771,7 @@ if not test_javahl:
     raise
   else:
     os.chdir(old_cwd)
-else:
+elif test_javahl:
   failed = False
   args = (
           'java.exe',
@@ -837,6 +809,148 @@ else:
   if (r != 0):
     print('[Test runner reported failure]')
     failed = True
+elif test_swig == 'perl':
+  failed = False
+  swig_dir = os.path.join(abs_builddir, 'swig')
+  swig_pl_dir = os.path.join(swig_dir, 'p5lib')
+  swig_pl_svn = os.path.join(swig_pl_dir, 'SVN')
+  swig_pl_auto_svn = os.path.join(swig_pl_dir, 'auto', 'SVN')
+
+  create_target_dir(swig_pl_svn)
+
+  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+    if isinstance(i, gen_base.TargetSWIG) and i.lang == 'perl':
+      mod_dir = os.path.join(swig_pl_auto_svn, '_' + i.name[5:].capitalize())
+      create_target_dir(mod_dir)
+      copy_changed_file(os.path.join(abs_objdir, i.filename), to_dir=mod_dir)
+
+    elif isinstance(i, gen_base.TargetSWIGLib) and i.lang == 'perl':
+      copy_changed_file(os.path.join(abs_objdir, i.filename),
+                        to_dir=abs_builddir)
+
+  pm_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'perl',
+                        'native')
+
+  tests = []
+
+  for root, dirs, files in os.walk(pm_src):
+    for name in files:
+      if name.endswith('.pm'):
+        fn = os.path.join(root, name)
+        copy_changed_file(fn, to_dir=swig_pl_svn)
+      elif name.endswith('.t'):
+        tests.append(os.path.relpath(os.path.join(root, name), pm_src))
+
+  perl5lib = swig_pl_dir
+  if 'PERL5LIB' in os.environ:
+    perl5lib += os.pathsep + os.environ['PERL5LIB']
+
+  perl_exe = 'perl.exe'
+
+  print('-- Running Swig Perl tests --')
+  old_cwd = os.getcwd()
+  try:
+    os.chdir(pm_src)
+
+    os.environ['PERL5LIB'] = perl5lib
+    os.environ["SVN_DBG_NO_ABORT_ON_ERROR_LEAK"] = 'YES'
+
+    r = subprocess.call([
+              perl_exe,
+              '-MExtUtils::Command::MM',
+              '-e', 'test_harness()'
+              ] + tests)
+  finally:
+    os.chdir(old_cwd)
+
+  if (r != 0):
+    print('[Test runner reported failure]')
+    failed = True
+  sys.exit(1)
+elif test_swig == 'python':
+  failed = False
+  swig_dir = os.path.join(abs_builddir, 'swig')
+  swig_py_dir = os.path.join(swig_dir, 'pylib')
+  swig_py_libsvn = os.path.join(swig_py_dir, 'libsvn')
+  swig_py_svn = os.path.join(swig_py_dir, 'svn')
+
+  create_target_dir(swig_py_libsvn)
+  create_target_dir(swig_py_svn)
+
+  for i in gen_obj.graph.get_all_sources(gen_base.DT_INSTALL):
+    if (isinstance(i, gen_base.TargetSWIG)
+        or isinstance(i, gen_base.TargetSWIGLib)) and i.lang == 'python':
+
+      src = os.path.join(abs_objdir, i.filename)
+      copy_changed_file(src, to_dir=swig_py_libsvn)
+
+  py_src = os.path.join(abs_srcdir, 'subversion', 'bindings', 'swig', 'python')
+
+  for py_file in os.listdir(py_src):
+    if py_file.endswith('.py'):
+      copy_changed_file(os.path.join(py_src, py_file),
+                        to_dir=swig_py_libsvn)
+
+  py_src_svn = os.path.join(py_src, 'svn')
+  for py_file in os.listdir(py_src_svn):
+    if py_file.endswith('.py'):
+      copy_changed_file(os.path.join(py_src_svn, py_file),
+                        to_dir=swig_py_svn)
+
+  print('-- Running Swig Python tests --')
+
+  pythonpath = swig_py_dir
+  if 'PYTHONPATH' in os.environ:
+    pythonpath += os.pathsep + os.environ['PYTHONPATH']
+
+  python_exe = 'python.exe'
+  old_cwd = os.getcwd()
+  try:
+    os.environ['PYTHONPATH'] = pythonpath
+
+    r = subprocess.call([
+              python_exe,
+              os.path.join(py_src, 'tests', 'run_all.py')
+              ])
+  finally:
+    os.chdir(old_cwd)
+
+    if (r != 0):
+      print('[Test runner reported failure]')
+      failed = True
+
+elif test_swig == 'ruby':
+  failed = False
+
+  if 'ruby' not in gen_obj._libraries:
+    print('Ruby not found. Skipping Ruby tests')
+  else:
+    ruby_lib = gen_obj._libraries['ruby']
+
+    ruby_exe = 'ruby.exe'
+    ruby_subdir = os.path.join('subversion', 'bindings', 'swig', 'ruby')
+    ruby_args = [
+        '-I', os.path.join(abs_srcdir, ruby_subdir),
+        os.path.join(abs_srcdir, ruby_subdir, 'test', 'run-test.rb'),
+        '--verbose'
+      ]
+
+    print('-- Running Swig Ruby tests --')
+    old_cwd = os.getcwd()
+    try:
+      os.chdir(ruby_subdir)
+
+      os.environ["BUILD_TYPE"] = objdir
+      os.environ["SVN_DBG_NO_ABORT_ON_ERROR_LEAK"] = 'YES'
+      r = subprocess.call([ruby_exe] + ruby_args)
+    finally:
+      os.chdir(old_cwd)
+
+    sys.stdout.flush()
+    sys.stderr.flush()
+    if (r != 0):
+      print('[Test runner reported failure]')
+      failed = True
 
 # Stop service daemon, if any
 if daemon: