You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@subversion.apache.org by JamieEchlin <ja...@credit-suisse.com> on 2010/12/07 18:39:28 UTC

usage of svn_wc_diff

Hi,

Firstly I hope this is the right forum, it doesn't seem appropriate for the
dev forum.

Has anyone got an example of using svn_wc_diff (or 2/3/4 etc), preferably
from python? I can find zero examples on the interwebs. I can't work out
what I need to pass from the minimal doxygen docs... 

Basically I'm trying to do a working copy diff...  I know I can call svn but
I need this to work in an env where svn(.exe) might not be present, and also
it doesn't give me enough control for what I'm trying to do, which is
basically property diffs.

cheers, jamie

-- 
View this message in context: http://old.nabble.com/usage-of-svn_wc_diff-tp30399137p30399137.html
Sent from the Subversion Users mailing list archive at Nabble.com.

Re: usage of svn_wc_diff

Posted by Daniel Shahaf <d....@daniel.shahaf.name>.
[ Please don't delete all context and quotes messages. ]

JamieEchlin wrote on Wed, Dec 08, 2010 at 07:39:32 -0800:
> 
> Cheers, but I was trying to avoid using the svn executable, as I said I can't
> rely on it being present.
> 

Then use the API equivalent...

> The trouble with the 41-line doc string is that it tells me how to use the
> function, but not what all batons etc I need to pass it, which I was having
> trouble working out.

cancel_baton is an object you create that is passed to cancel_func()
whenever the latter is called (if it's called).  Similar for all other
batons...

The doc strings of the functions and of the callback types normally say
exactly which baton goes to which function (so you don't have to guess
that from their names).

Re: usage of svn_wc_diff

Posted by JamieEchlin <ja...@credit-suisse.com>.
Cheers, but I was trying to avoid using the svn executable, as I said I can't
rely on it being present.

The trouble with the 41-line doc string is that it tells me how to use the
function, but not what all batons etc I need to pass it, which I was having
trouble working out.
-- 
View this message in context: http://old.nabble.com/usage-of-svn_wc_diff-tp30399137p30406478.html
Sent from the Subversion Users mailing list archive at Nabble.com.

Re: usage of svn_wc_diff

Posted by Daniel Shahaf <d....@daniel.shahaf.name>.
JamieEchlin wrote on Tue, Dec 07, 2010 at 10:39:28 -0800:
> 
> Hi,
> 
> Firstly I hope this is the right forum, it doesn't seem appropriate for the
> dev forum.
> 

Yes, users@ is the right forum.

> Has anyone got an example of using svn_wc_diff (or 2/3/4 etc), preferably
> from python? I can find zero examples on the interwebs. I can't work out
> what I need to pass from the minimal doxygen docs... 
> 

The C API svn_wc_diff6() has a 41-line doc string. (but it's not
released yet, so refer to svn_wc_diff5()'s documentation)

Although you might prefer to use svn_client_diffN() instead. (currently
N=5 in trunk)

> Basically I'm trying to do a working copy diff...  I know I can call svn but
> I need this to work in an env where svn(.exe) might not be present, and also
> it doesn't give me enough control for what I'm trying to do, which is
> basically property diffs.
> 

svn diff --depth=empty **/

?

(where the shell expands **/ to "all directories under the current
directory"; find(1) could be used instead of this syntax)

> cheers, jamie
> 
> -- 
> View this message in context: http://old.nabble.com/usage-of-svn_wc_diff-tp30399137p30399137.html
> Sent from the Subversion Users mailing list archive at Nabble.com.
> 

Re: usage of svn_wc_diff

Posted by JamieEchlin <ja...@credit-suisse.com>.

Daniel Näslund wrote:
> 
> Hope, you'll be able to solve your problem. Here's a script that's a bit
> more clean than the previous one. I haven't tested it much at all but it
> appears to be able to display added, modified and deleted properties set
> on both dirs and files. The '###' lines represents TODO's.
> 

If anyone else uses this code, I'd just thought I'd mention belatedly that
you don't need a write lock to do the diff. So pass False to adm_open3.
Doing this speeds up execution by 12s on a working copy hosted on an NTFS
volume.

BTW, it seems svn and NTFS don't play nice:
http://superuser.com/questions/15192/bad-ntfs-performance, and this is easy
to demonstrate by comparing the lock/unlock performance on NTFS against a
FAT-formatted disk. Do the new format working copies in 1.7 fix this?

cheers, jamie
-- 
View this message in context: http://old.nabble.com/usage-of-svn_wc_diff-tp30399137p30642926.html
Sent from the Subversion Users mailing list archive at Nabble.com.


Re: usage of svn_wc_diff

Posted by JamieEchlin <ja...@credit-suisse.com>.
Seems to work perfectly! Thanks Daniel, that's exactly what I was looking
for.
jamie
-- 
View this message in context: http://old.nabble.com/usage-of-svn_wc_diff-tp30399137p30445649.html
Sent from the Subversion Users mailing list archive at Nabble.com.

Re: usage of svn_wc_diff

Posted by Daniel Näslund <da...@longitudo.com>.
On Wed, Dec 08, 2010 at 11:59:16PM -0800, JamieEchlin wrote:
> 
> Daniel, thank you very much for that, that's incredibly helpful. It
> definitely gives me somewhere to start.

Hope, you'll be able to solve your problem. Here's a script that's a bit
more clean than the previous one. I haven't tested it much at all but it
appears to be able to display added, modified and deleted properties set
on both dirs and files. The '###' lines represents TODO's.

Daniel


#!/usr/bin/env python

import sys
import os
import getopt
try:
    my_getopt = getopt.gnu_getop
except AttributeError:
    my_getopt = getopt.getopt

import svn.wc
import svn.core

EQUAL_STRING = (
"========================================================================") 
UNDER_STRING = (
"------------------------------------------------------------------------")

def usage_and_exit(retval):
    if retval:
        out = sys.stderr
    else:
        out = sys.stdout
    out.write("""Usage: %s TARGET

Options:
  --help (-h)       : Show this usage message

Display the difference between the BASE revision and the changes made in the
working copy. This is just a very basic example script. It doesn't properly
handle revisions or the problems involved in adjusting diff labels. At
present it always prints one diff header for each file or dir that has
property changes. If we'd check for more than property changes - we need to
keep track of when we have printed a diff header. Some files can have both
property and text changes.
""" % (os.path.basename(sys.argv[0])))
    sys.exit(retval)

def print_diff_headers(path, rev):
    print "Index: %s" % path
    print EQUAL_STRING
    print "--- %s\t (revision %d)" % (path, rev)
    ### Here we'd check rev2 against some constant that represents the "working
    ### copy revision" - it's probably -1.
    print "+++ %s\t (working copy)" % path

def props_changed(path, propchanges, originalprops):

    print "\nProperty changes on %s" % path
    print UNDER_STRING

    for (name, value) in propchanges.items():
        if originalprops is not None and name in originalprops:
            original_value = originalprops[name]
        else:
            original_value = None

        if not (value or original_value) or (value == original_value):
            continue

        if not original_value:
            print "Added: %s" % name
        elif not value:
            print "Deleted %s" % name
        else:
            print "Modified %s" % name

        ### We're ignoring special handling of svn:mergeinfo for now.

        ### Do we have to handle utf8 conversions here?
        if original_value is not None:
            print "   - %s" % original_value

        if value is not None:
            print "   + %s" % value

def content_changed(path, tmpfile1, tmpfile2, rev1, rev2, mimetype1,
                    mimetype2):
    ### TODO: Write this one
    pass

def has_regular_prop_changes(props):

    if not props:
        return False

    ### For some reason, svn_categorize_props() segfaults
    ### Shouldn't we only pass the regular_props to props_changed?
    for (name, value) in props.items():
        (kind, unused_prefix_len) = svn.core.svn_property_kind(name)
        if kind == svn.core.svn_prop_regular_kind:
            return True

    return False


class Callback(svn.wc.DiffCallbacks2):
    def file_changed(self, adm_access, path,
                     tmpfile1, tmpfile2, rev1, rev2,
                     mimetype1, mimetype2, propchanges,
                     originalprops):

        if has_regular_prop_changes(propchanges):
            print_diff_headers(path, rev1)
            props_changed(path, propchanges, originalprops)

        return (svn.wc.notify_state_unknown, svn.wc.notify_state_unknown)

    def file_added(self, adm_access, path, tmpfile1, tmpfile2,
                   rev1, rev2, mimetype1, mimetype2, propchanges,
                   originalprops):
        return (svn.wc.notify_state_unknown, svn.wc.notify_state_unknown)

    def file_deleted(self, adm_access, path, tmpfile1,
                     tmpfile2, mimetype1, mimetype2, originalprops):
            return svn.wc.notify_state_unknown

    def dir_added(self, adm_access, path, rev):
        return svn.wc.notify_state_unknown

    def dir_deleted(self, adm_access, path):
        return svn.wc.notify_state_unknown

    def dir_props_changed(self, adm_access, path,
                          propchanges, originalprops):

        if has_regular_prop_changes(propchanges):
            ### How fetch the revision here?
            rev = 42
            print_diff_headers(path, rev)
            props_changed(path, propchanges, originalprops)

        return svn.wc.notify_state_unknown

def main():
    diff_callbacks = Callback()
    depth = svn.core.svn_depth_infinity

    # Parse the options
    optlist, args = my_getopt(sys.argv[1:], "h", ['help'])
    for opt, arg in optlist:
        if opt == '--help' or opt == '-h':
            usage_and_exit(1)

    if len(args) != 1:
        usage_and_exit(1)

    if os.path.isdir(args[0]):
        target = ''
        wc_dir = args[0]
    else:
        target = os.path.basename(args[0])
        wc_dir = os.path.dirname(args[0])

    try:
        # Subversion wants all paths to be canonicalised, that involves
        # collapsing redundant "/./" elements, removing multiple adjacent
        # separator characters and removing trailing separator characters.
        canon_wc_dir = svn.core.svn_path_canonicalize(wc_dir)
        canon_target = svn.core.svn_path_canonicalize(target)

        # The C code that Subversion consists of, uses apr pools for allocation of
        # memory. The idea is that the pools in the swig bindings are
        # (mostly) optional and auto-managed in accordance with the lifetime
        # of the python objects themselves. They are needed in the places
        # I've used them, but later releases may not need them.
        pool = svn.core.Pool()

        # The wc, of svn_wc_adm_access_t type, is a data structure for
        # coordinating the access to the working copy administrative area
        # (the .svn folders). It is associated with the containing parent
        # dir. I find the adm_access approach a bit messy - you can specify
        # how far you wanna lock and multiple access_batons can form sets.
        # The 1.7 release will introduce a more intuitive way of accessing
        # the wc administrative area.
        wc = svn.wc.adm_open3(None, canon_wc_dir, 
                              True, # write_lock 
                              -1, # levels_to_lock,
                              None)

        svn.wc.svn_wc_diff4(wc, canon_target, diff_callbacks, depth, False, 
                            None, pool)
    except svn.core.SubversionException, ex:
        sys.stderr.write("ERROR: %s\n" % ex.message)

if __name__ == '__main__':
    main()

Re: usage of svn_wc_diff

Posted by JamieEchlin <ja...@credit-suisse.com>.
Daniel, thank you very much for that, that's incredibly helpful. It
definitely gives me somewhere to start.

cheers, jamie
-- 
View this message in context: http://old.nabble.com/usage-of-svn_wc_diff-tp30399137p30413850.html
Sent from the Subversion Users mailing list archive at Nabble.com.

Re: usage of svn_wc_diff

Posted by Daniel Näslund <da...@longitudo.com>.
On Tue, Dec 07, 2010 at 10:39:28AM -0800, JamieEchlin wrote:
> 
> Hi,
> 
> Firstly I hope this is the right forum, it doesn't seem appropriate for the
> dev forum.
> 
> Has anyone got an example of using svn_wc_diff (or 2/3/4 etc), preferably
> from python? I can find zero examples on the interwebs. I can't work out
> what I need to pass from the minimal doxygen docs... 
> 
> Basically I'm trying to do a working copy diff...  I know I can call svn but
> I need this to work in an env where svn(.exe) might not be present, and also
> it doesn't give me enough control for what I'm trying to do, which is
> basically property diffs.

This mail contains only fragments of code that I haven't tested much at
all. For examples of using the swig-python bindings, read
subversion/bindings/swig/python/tests/*.py. For an example in C of how
the diff callbacks are implemented read subversion/libsvn_client/diff.c.

Here's a really rough sketch [2] of how you might call svn_wc_diff4() with
the 1.6 swig bindings. It doesn't produce any diff output, for that
you'd have to call the diff library functions [1] with the data you've
collected through the diff callbacks. Note that the output for the
property diffs are not created through the diff library (they will be in
1.7 though, e.g. using regular unidiff format for both props and file
contents).

[1] Code for calling the diff library functions:
[[[
#!/usr/bin/env python

import getopt
import sys
import os
import svn.diff, svn.core, svn.client

def diff_with_pools(modified, original):
    pool = svn.core.Pool()
    diff = svn.diff.svn_diff_file_diff_2(original, modified, "",  pool)
    has_changes = svn.diff.contains_diffs(diff)
    print has_changes
    svn.diff.file_output_unified3(sys.stdout, diff, original, modified, 
                                  None, None, "", None, True)


if __name__ == '__main__':
    # Canonicalize the repository path
    modified = svn.core.svn_path_canonicalize(args[0])
    original = svn.core.svn_path_canonicalize(args[1])

    pool = svn.core.Pool()
    diff = svn.diff.svn_diff_file_diff_2(original, modified, "",  pool)
    has_changes = svn.diff.contains_diffs(diff)
    print has_changes
    svn.diff.file_output_unified3(sys.stdout, diff, original, modified, 
                                  None, None, "", None, True)
]]]

[2] Code for calling svn_wc_diff4()
[[[

#!/usr/bin/env python

import svn.wc
import svn.core
import svn.client
import svn.ra

def props_changed(path, propchanges):
    """ Here you'll have to find out what properties are regular props
        and write them out in a manner similar to what
        libsvn_client/diff.c::display_prop_diff() does. """

    for (name, value) in propchanges.items():
        (kind, unused_prefix_len) = svn.core.svn_property_kind(name)
        if kind != svn.core.svn_prop_regular_kind:
            continue
        ### Append to some prop dict

class Callback(svn.wc.DiffCallbacks2):
    def file_changed(self, adm_access, path,
                     tmpfile1, tmpfile2, rev1, rev2,
                     mimetype1, mimetype2, propchanges,
                     originalprops):
        print "In file_changed"
        props_changed(path, propchanges)
        return (svn.wc.notify_state_unknown, svn.wc.notify_state_unknown)

    def file_added(self, adm_access, path, tmpfile1, tmpfile2,
                   rev1, rev2, mimetype1, mimetype2, propchanges,
                   originalprops):
        print "In file_added"
        return (svn.wc.notify_state_unknown, svn.wc.notify_state_unknown)

    def file_deleted(self, adm_access, path, tmpfile1,
                     tmpfile2, mimetype1, mimetype2, originalprops):
            print "In file_deleted"
            return svn.wc.notify_state_unknown

    def dir_added(self, adm_access, path, rev):
        print "In dir_added"
        return svn.wc.notify_state_unknown

    def dir_deleted(self, adm_access, path):
        print "In dir_deleted"
        return svn.wc.notify_state_unknown

    def dir_props_changed(self, adm_access, path,
                          propchanges, original_props):
        print "In dir_props_changed"
        props_changed(path, propchanges)
        return svn.wc.notify_state_unknown

target = 'iota'
diff_callbacks = Callback()
depth = svn.core.svn_depth_infinity
pool = svn.core.Pool()

path_to_wc = "wc"
wc = svn.wc.adm_open3(None, path_to_wc, True, -1, None)

svn.wc.svn_wc_diff4(wc, target, diff_callbacks, depth, False, 
                    None, pool)
]]]

Daniel