Turns out that even in its unmaintained state, tailor is the best option
for svn-to-svn copies out there, if you don't have svnadmin access to
the destination... I did a recent project where I massaged the dump from
the source (the key bit was the Lump-handling code from svndumpfilter,
but replace the broken prop-handling there), loaded it into a scratch
repo, and then tailor that into the destination.

Problem was that this (like git-svn and some others) ignores properties
entirely.  Since svn:keywords and svn:eol-style (among others) change
what the content is checked out as, that wasn't good enough... 

The resulting hook is kind of evil, and incomplete, but useful enough
that I thought I'd share it with other tailor users.  The key bit of
evil is monkey-patching SvnWorkingDir._commit to run a list of
just-before-commit jobs -- before-commit is actually *too* soon, we need
to apply properties after all svn "add" and "mv" operations have been
done, but before the commit itself.  (See force_decorate_once below.)

The "incomplete" bits include
   * no support for *deletion* of properties - that would require
     looking at the destination tree and seeing what props are present
     only on the destination files/dirs (and handling renames within the same
     commit sanely.) In my use case, the only deletion was a typo that
     didn't have any impact on the files, so deleting it by hand
     afterwards was acceptable.
   * assumes that you're using svn on both sides - which is why this
     isn't suitable as a *patch*, only as a config-file hook (though
     adding a new just-before-commit-hook would be a useful patch, which
     would simplify this code.)
   * the svn hooks are just ad-hoc Popen calls; at very least they
     should use --xml to avoid quoting issues (but I knew in advance,
     from the dump, that there had never been any terribly interesting
     characters in prop names or values.)

I think this gives tailor a bit of a leg up over git-svn (which is
otherwise the "runner-up" in the comparison among the tools...)

#!/usr/bin/env tailor
# -*- python -*-
"""
...
[project]
...
before-commit = before
...
"""
def svn_proplist(path):
    from subprocess import Popen, PIPE
    svn = Popen(["svn", "proplist", "-q", path], stdout=PIPE)
    props, _ = svn.communicate()
    if svn.wait() != 0:
        sys.exit("proplist failed with %s" % svn.returncode)
    return [prop.strip() for prop in props.split()]

def svn_propget(path, propname):
    from subprocess import Popen, PIPE
    svn = Popen(["svn", "propget", propname, path], stdout=PIPE)
    propvalue, _ = svn.communicate()
    if svn.wait() != 0:
        sys.exit("propget failed with %s" % svn.returncode)
    assert propvalue.endswith("\n")
    propvalue = propvalue[:-1] # strip only the newline added by propget itself
    # we *could* use --xml for this, but the set of values we actually have is 
pretty limited
    return propvalue

def svn_propset(path, propname, propvalue):
    from subprocess import check_call
    check_call(["svn", "propset", propname, propvalue, path])

# actually we want a list of things to do pre-commit, and then make 
repository.svn.SvnWorkingDir._commit do them

def force_decorate_once(wd):
    if hasattr(wd.__class__, "_orig_commit"):
        return
    wd.__class__._orig_commit = wd.__class__._commit
    def wrapper(self, *args, **kwargs):
        for work in self.work_for_this_rev:
            work()
        self._orig_commit(*args, **kwargs)
    wd.__class__._commit = wrapper

def before(wd, changeset):
    import sys, os
    import functools
    # the runnable-config-file hack does something weird/wrong with scoping, 
but this works around it
    global svn_proplist, svn_propget, svn_propset, force_decorate_once
    proj = wd.repository.projectref()
    repo_root = wd.repository.rootdir
    srcdir =  proj.source.subdir
    targdir = proj.target.subdir

    work_for_this_rev = []
    force_decorate_once(wd)

    for cs_entry in changeset.entries:
        if cs_entry.action_kind != cs_entry.DELETED:
            changed_src =  os.path.join(repo_root, srcdir, cs_entry.name)
            changed_targ = os.path.join(repo_root, targdir, cs_entry.name)
            for propname in svn_proplist(changed_src):
                propvalue = svn_propget(changed_src, propname)
                work_for_this_rev.append(functools.partial(svn_propset, 
changed_targ, propname, propvalue))

    setattr(wd, "work_for_this_rev", work_for_this_rev)

    return True
_______________________________________________
Tailor mailing list
Tailor@lists.zooko.com
http://lists.zooko.com/mailman/listinfo/tailor

Reply via email to