Py::Object pysvn_client::cmd_revpropset( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_prop_name },
    { true,  name_prop_value },
    { true,  name_url },
    { false, name_revision },
    { false, name_force },
    { false, NULL }
    };
    FunctionArguments args( "revpropset", args_desc, a_args, a_kws );
    args.check();

    std::string propname( args.getUtf8String( name_prop_name ) );
    std::string propval( args.getUtf8String( name_prop_value ) );
    std::string path( args.getUtf8String( name_url ) );
    svn_opt_revision_t revision = args.getRevision( name_revision, svn_opt_revision_head );

    bool force = args.getBoolean( name_force, false );

    SvnPool pool( m_context );

    svn_revnum_t revnum = 0;
    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        const svn_string_t *svn_propval = svn_string_ncreate( propval.c_str(), propval.size(), pool );
        svn_error_t *error = svn_client_revprop_set
            (
            propname.c_str(),
            svn_propval,
            norm_path.c_str(),
            &revision,
            &revnum,
            force,
            m_context,
            pool
            );
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    return Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, revnum ) );
}
Py::Object pysvn_client::cmd_lock( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { true,  name_comment },
    { false, name_force },
    { false, NULL }
    };
    FunctionArguments args( "lock", args_desc, a_args, a_kws );
    args.check();

    SvnPool pool( m_context );

    apr_array_header_t *targets = targetsFromStringOrList( args.getArg( name_url_or_path ), pool );

    std::string type_error_message;
    try
    {
        type_error_message = "expecting string for comment (arg 2)";
        std::string comment( args.getUtf8String( name_comment ) );

        type_error_message = "expecting boolean for force keyword arg";
        bool force = args.getBoolean( name_force, false );

        try
        {
            checkThreadPermission();

            PythonAllowThreads permission( m_context );

            svn_error_t *error = svn_client_lock
                (
                targets,
                comment.c_str(),
                force,        // non recursive
                m_context,
                pool
                );
            permission.allowThisThread();
            if( error != NULL )
                throw SvnException( error );
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }
    }
    catch( Py::TypeError & )
    {
        throw Py::TypeError( type_error_message );
    }

    return Py::None();
}
Py::Object pysvn_client::cmd_info( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_path },
    { false, NULL }
    };
    FunctionArguments args( "info", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_path ) );

    SvnPool pool( m_context );

    const svn_wc_entry_t *entry = NULL;

    try
    {
        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        svn_wc_adm_access_t *adm_access = NULL;

#if defined( PYSVN_HAS_WC_ADM_PROBE_OPEN3 )
        const char *c_norm_path = svn_dirent_internal_style( path.c_str(), pool );
        std::string norm_path( c_norm_path );
        svn_error_t *error = svn_wc_adm_probe_open3( &adm_access, NULL, norm_path.c_str(), false, 0, NULL, NULL, pool );
#else
        std::string norm_path( svnNormalisedPath( path, pool ) );
        svn_error_t *error = svn_wc_adm_probe_open( &adm_access, NULL, norm_path.c_str(), false, false, pool );
#endif

        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );

        permission.allowOtherThreads();
        error = svn_wc_entry( &entry, norm_path.c_str(), adm_access, false, pool );
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
        
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
        return Py::None();       // needed to remove warning about return value missing
    }

    if( entry == NULL )
        return Py::None();

    return toObject( *entry, pool, m_wrapper_entry );
}
Py::Object pysvn_client::cmd_revproplist( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url },
    { false, name_revision },
    { false, NULL }
    };
    FunctionArguments args( "revproplist", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_url ) );
    svn_opt_revision_t revision = args.getRevision( name_revision, svn_opt_revision_head );

    SvnPool pool( m_context );

    apr_hash_t *props = NULL;
    svn_revnum_t revnum = 0;

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        svn_error_t *error = svn_client_revprop_list
            (
            &props,
            norm_path.c_str(),
            &revision,
            &revnum,
            m_context,
            pool
            );
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }


    Py::Tuple result(2);
    result[0] = Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, revnum ) );
    result[1] = propsToObject( props, pool );

    return result;
}
Py::Object pysvn_client::cmd_propget( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
        { true,  name_prop_name },
        { true,  name_url_or_path },
        { false, name_revision },
        { false, name_recurse },
#if defined( PYSVN_HAS_CLIENT_PROPGET2 )
        { false, name_peg_revision },
#endif
#if defined( PYSVN_HAS_CLIENT_PROPGET3 )
        { false, name_depth },
        { false, name_changelists },
#endif
        { false, NULL }
    };
    FunctionArguments args( "propget", args_desc, a_args, a_kws );
    args.check();

    std::string propname( args.getUtf8String( name_prop_name ) );
    std::string path( args.getUtf8String( name_url_or_path ) );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_PROPGET3 )
    apr_array_header_t *changelists = NULL;

    if( args.hasArg( name_changelists ) )
    {
        changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
    }

    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_files, svn_depth_empty );
#else
    bool recurse = args.getBoolean( name_recurse, false );
#endif
    svn_opt_revision_t revision;
    if( is_svn_url( path ) )
        revision = args.getRevision( name_revision, svn_opt_revision_head );
    else
        revision = args.getRevision( name_revision, svn_opt_revision_working );
#if defined( PYSVN_HAS_CLIENT_PROPGET2 )
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, revision );
#endif

    bool is_url = is_svn_url( path );
#if defined( PYSVN_HAS_CLIENT_PROPGET2 )
    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
#endif
    revisionKindCompatibleCheck( is_url, revision, name_revision, name_url_or_path );

    apr_hash_t *props = NULL;

#if defined( PYSVN_HAS_CLIENT_PROPGET3 )
    svn_revnum_t actual_revnum = 0;
#endif

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_PROPGET3 )
        svn_error_t *error = svn_client_propget3
                             (
                                 &props,
                                 propname.c_str(),
                                 norm_path.c_str(),
                                 &peg_revision,
                                 &revision,
                                 &actual_revnum,
                                 depth,
                                 changelists,
                                 m_context,
                                 pool
                             );
#elif defined( PYSVN_HAS_CLIENT_PROPGET2 )
        svn_error_t *error = svn_client_propget2
                             (
                                 &props,
                                 propname.c_str(),
                                 norm_path.c_str(),
                                 &peg_revision,
                                 &revision,
                                 recurse,
                                 m_context,
                                 pool
                             );
#else
        svn_error_t *error = svn_client_propget
                             (
                                 &props,
                                 propname.c_str(),
                                 norm_path.c_str(),
                                 &revision,
                                 recurse,
                                 m_context,
                                 pool
                             );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    return propsToObject( props, pool );
}
Py::Object pysvn_client::cmd_proplist( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
        { true,  name_url_or_path },
        { false, name_revision },
        { false, name_recurse },
#if defined( PYSVN_HAS_CLIENT_PROPLIST2 )
        { false, name_peg_revision },
#endif
#if defined( PYSVN_HAS_CLIENT_PROPLIST3 )
        { false, name_depth },
        { false, name_changelists },
#endif
        { false, NULL }
    };
    FunctionArguments args( "proplist", args_desc, a_args, a_kws );
    args.check();

    Py::List path_list( toListOfStrings( args.getArg( name_url_or_path ) ) );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_PROPLIST3 )
    apr_array_header_t *changelists = NULL;

    if( args.hasArg( name_changelists ) )
    {
        changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
    }

    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_files, svn_depth_empty );
#else
    bool recurse = args.getBoolean( name_recurse, false );
#endif
    bool is_revision_setup = false;
    bool is_url = false;

    svn_opt_revision_t revision_url;
    svn_opt_revision_t revision_file;
    if( args.hasArg( name_revision ) )
    {
        revision_url = args.getRevision( name_revision );
        revision_file = revision_url;
    }
    else
    {
        revision_url.kind = svn_opt_revision_head;
        revision_file.kind = svn_opt_revision_working;
    }

#if defined( PYSVN_HAS_CLIENT_PROPLIST2 )
    svn_opt_revision_t peg_revision_url;
    svn_opt_revision_t peg_revision_file;
    if( args.hasArg( name_peg_revision ) )
    {
        peg_revision_url = args.getRevision( name_peg_revision );
        peg_revision_file = peg_revision_url;
    }
    else
    {
        peg_revision_url = revision_url;
        peg_revision_file = revision_file;
    }
#endif

    Py::List list_of_proplists;

    for( Py::List::size_type i=0; i<path_list.length(); i++ )
    {
        Py::String path_str( asUtf8String( path_list[i] ) );
        std::string path( path_str.as_std_string() );
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        svn_opt_revision_t revision;
        svn_opt_revision_t peg_revision;
        if( !is_revision_setup )
            if( is_svn_url( path ) )
            {
                revision = revision_url;
#if defined( PYSVN_HAS_CLIENT_PROPLIST2 )
                peg_revision = peg_revision_url;
#endif
                is_url = true;
            }
            else
            {
                revision = revision_file;
#if defined( PYSVN_HAS_CLIENT_PROPLIST2 )
                peg_revision = peg_revision_file;
#endif
            }
        else if( is_svn_url( path ) && !is_url )
        {
            throw Py::AttributeError( "cannot mix URL and PATH in name_path" );
        }

        try
        {
            const char *norm_path_c_str= norm_path.c_str();
            checkThreadPermission();

            PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_PROPLIST3 )
            ProplistReceiveBaton proplist_baton( &permission, pool, list_of_proplists );
            svn_error_t *error = svn_client_proplist3
                                 (
                                     norm_path_c_str,
                                     &peg_revision,
                                     &revision,
                                     depth,
                                     changelists,
                                     proplist_receiver_c,
                                     reinterpret_cast<void *>( &proplist_baton ),
                                     m_context,
                                     pool
                                 );
#elif defined( PYSVN_HAS_CLIENT_PROPLIST2 )
            apr_array_header_t *props = NULL;
            svn_error_t *error = svn_client_proplist2
                                 (
                                     &props,
                                     norm_path_c_str,
                                     &peg_revision,
                                     &revision,
                                     recurse,
                                     m_context,
                                     pool
                                 );
#else
            apr_array_header_t *props = NULL;
            svn_error_t *error = svn_client_proplist
                                 (
                                     &props,
                                     norm_path_c_str,
                                     &revision,
                                     recurse,
                                     m_context,
                                     pool
                                 );
#endif
            permission.allowThisThread();
            if( error != NULL )
                throw SvnException( error );

#if !defined( PYSVN_HAS_CLIENT_PROPLIST3 )
            proplistToObject( list_of_proplists, props, pool );
#endif
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }
    }

    return list_of_proplists;
}
Py::Object pysvn_client::cmd_add_to_changelist( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_path },
    { true,  name_changelist },
    { false, name_depth },
    { false, name_changelists },
    { false, NULL }
    };
    FunctionArguments args( "add_to_changelist", args_desc, a_args, a_kws );
    args.check();

    std::string type_error_message;

    SvnPool pool( m_context );

    try
    {
        apr_array_header_t *targets = targetsFromStringOrList( args.getArg( name_path ), pool );
        std::string changelist( args.getUtf8String( name_changelist ) );

        apr_array_header_t *changelists = NULL;
        if( args.hasArg( name_changelists ) )
        {
            changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
        }

        svn_depth_t depth = args.getDepth( name_depth, svn_depth_files );

        try
        {
            checkThreadPermission();

            PythonAllowThreads permission( m_context );

            svn_error_t *error = svn_client_add_to_changelist
                (
                targets,
                changelist.c_str(),
                depth,
                changelists,
                m_context,
                pool
                );
            permission.allowThisThread();
            if( error != NULL )
                throw SvnException( error );
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }
    }
    catch( Py::TypeError & )
    {
        throw Py::TypeError( type_error_message );
    }

    return Py::None();
}
Py::Object pysvn_client::cmd_move2( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true, name_sources },
    { true, name_dest_url_or_path },
    { false, name_force },
    { false, name_move_as_child },
    { false, name_make_parents },
    { false, name_revprops },
    { false, NULL }
    };
    FunctionArguments args( "move2", args_desc, a_args, a_kws );
    args.check();

    SvnPool pool( m_context );
    pysvn_commit_info_t *commit_info = NULL;

    std::string type_error_message;
    try
    {
        type_error_message = "expecting list for sources (arg 1)";
        Py::List list_all_sources = args.getArg( name_sources );

        apr_array_header_t *all_sources =
            apr_array_make( pool, list_all_sources.length(), sizeof(const char *) );

        for( unsigned int index=0; index<list_all_sources.length(); index++ )
        {
            type_error_message = "expecting string in sources list";
            Py::String py_src_url_or_path( list_all_sources[ index ] );

            std::string src_url_or_path;
            if( py_src_url_or_path.isUnicode() )
            {
                Py::String utf8( py_src_url_or_path.encode( name_utf8 ) );
                src_url_or_path = py_src_url_or_path.as_std_string();
            }
            else
            {
                src_url_or_path = py_src_url_or_path.as_std_string();
            }

            std::string norm_src_url_or_path( svnNormalisedIfPath( src_url_or_path, pool ) );

            const char *src_path_copy = apr_pstrdup( pool, norm_src_url_or_path.c_str() );

            APR_ARRAY_PUSH( all_sources, const char *) = src_path_copy;
        }

        type_error_message = "expecting string for dest_url_or_path";
        Py::String dest_path( args.getUtf8String( name_dest_url_or_path ) );

        type_error_message = "expecting boolean for keyword force";
        bool force = args.getBoolean( name_force, false );

        type_error_message = "expecting boolean for keyword move_as_child";
        bool move_as_child = args.getBoolean( name_move_as_child, false );

        type_error_message = "expecting boolean for keyword make_parents";
        bool make_parents = args.getBoolean( name_make_parents, false );

        apr_hash_t *revprops = NULL;
        if( args.hasArg( name_revprops ) )
        {
            Py::Object py_revprop = args.getArg( name_revprops );
            if( !py_revprop.isNone() )
            {
                revprops = hashOfStringsFromDistOfStrings( py_revprop, pool );
            }
        }

        try
        {
            std::string norm_dest_path( svnNormalisedIfPath( dest_path, pool ) );

            checkThreadPermission();
            PythonAllowThreads permission( m_context );

            // behavior changed
            svn_error_t *error = svn_client_move5
                (
                &commit_info,
                all_sources,
                norm_dest_path.c_str(),
                force,
                move_as_child,
                make_parents,
                revprops,
                m_context,
                pool
                );
            permission.allowThisThread();
            if( error != NULL )
            {
                throw SvnException( error );
            }
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }
    }
    catch( Py::TypeError & )
    {
        throw Py::TypeError( type_error_message );
    }

    return toObject( commit_info );
}
Py::Object pysvn_client::cmd_move( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_src_url_or_path },
    { true,  name_dest_url_or_path },
    { false, name_force },
    { false, NULL }
    };
    FunctionArguments args( "move", args_desc, a_args, a_kws );
    args.check();

    SvnPool pool( m_context );
    pysvn_commit_info_t *commit_info = NULL;

    std::string type_error_message;
    try
    {
        type_error_message = "expecting string for src_url_or_path (arg 1)";
        Py::String src_path( args.getUtf8String( name_src_url_or_path ) );

        type_error_message = "expecting string for dest_url_or_path (arg 2)";
        Py::String dest_path( args.getUtf8String( name_dest_url_or_path ) );

#ifndef PYSVN_HAS_CLIENT_MOVE2
        svn_opt_revision_t revision;
        revision.kind = svn_opt_revision_head;
#endif

        type_error_message = "expecting boolean for keyword force";
        bool force = args.getBoolean( name_force, false );

        try
        {
            std::string norm_src_path( svnNormalisedIfPath( src_path, pool ) );
            std::string norm_dest_path( svnNormalisedIfPath( dest_path, pool ) );

            checkThreadPermission();

            PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_MOVE4 )
            // behavior changed
            svn_error_t *error = svn_client_move4
                (
                &commit_info,
                norm_src_path.c_str(),
                norm_dest_path.c_str(),
                force,
                m_context,
                pool
                );
#elif defined( PYSVN_HAS_CLIENT_MOVE3 )
            svn_error_t *error = svn_client_move3
                (
                &commit_info,               // changed type
                norm_src_path.c_str(),
                norm_dest_path.c_str(),
                force,
                m_context,
                pool
                );
#elif defined( PYSVN_HAS_CLIENT_MOVE2 )
            svn_error_t *error = svn_client_move2
                (
                &commit_info,
                norm_src_path.c_str(),
                norm_dest_path.c_str(),
                force,
                m_context,
                pool
                );
#else
            svn_error_t *error = svn_client_move
                (
                &commit_info,
                norm_src_path.c_str(),
                &revision,
                norm_dest_path.c_str(),
                force,
                m_context,
                pool
                );
#endif
            permission.allowThisThread();
            if( error != NULL )
                throw SvnException( error );
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }
    }
    catch( Py::TypeError & )
    {
        throw Py::TypeError( type_error_message );
    }

    return toObject( commit_info );
}
Py::Object pysvn_client::cmd_copy2( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true, name_sources },
    { true, name_dest_url_or_path },
    { false, name_copy_as_child },
    { false, name_make_parents },
    { false, name_revprops },
    { false, NULL }
    };
    FunctionArguments args( "copy2", args_desc, a_args, a_kws );
    args.check();

    SvnPool pool( m_context );
    pysvn_commit_info_t *commit_info = NULL;

    std::string type_error_message;
    try
    {
        type_error_message = "expecting list for sources (arg 1)";
        Py::List list_all_sources = args.getArg( name_sources );

        apr_array_header_t *all_sources =
            apr_array_make( pool, list_all_sources.length(), sizeof(svn_client_copy_source_t *) );

        for( unsigned int index=0; index<list_all_sources.length(); index++ )
        {
            Py::Tuple tuple_src_rev_pegrev( list_all_sources[ index ] );

            std::string src_url_or_path;
            svn_opt_revision_t *revision = reinterpret_cast<svn_opt_revision_t *>( apr_palloc( pool, sizeof( svn_opt_revision_t ) ) );
            svn_opt_revision_t *peg_revision = reinterpret_cast<svn_opt_revision_t *>( apr_palloc( pool, sizeof( svn_opt_revision_t ) ) );

            if( tuple_src_rev_pegrev.length() > 3 )
            {
                std::string msg = "copy2() expecting tuple with 2 or 3 values in sources list";
                throw Py::AttributeError( msg );
            }

            type_error_message = "expecting string for 1st tuple value in sources list";
            Py::String py_src_url_or_path( tuple_src_rev_pegrev[0] );
            if( py_src_url_or_path.isUnicode() )
            {
                Py::String utf8( py_src_url_or_path.encode( name_utf8 ) );
                src_url_or_path = py_src_url_or_path.as_std_string();
            }
            else
            {
                src_url_or_path = py_src_url_or_path.as_std_string();
            }
            std::string norm_src_url_or_path( svnNormalisedIfPath( src_url_or_path, pool ) );
            bool is_url = is_svn_url( norm_src_url_or_path );

            if( tuple_src_rev_pegrev.length() >= 2 )
            {
                Py::Object obj( tuple_src_rev_pegrev[1] );

                if( pysvn_revision::check( obj ) )
                {
                    pysvn_revision *rev = static_cast<pysvn_revision *>( obj.ptr() );
                    *revision = rev->getSvnRevision();

                    revisionKindCompatibleCheck( is_url, *revision,
                        "sources list 2nd tuple value", "sources list 1st tuple value" );
                }
                else
                {
                    std::string msg = "copy2() expecting revision for 2nd tuple value in sources list";
                    throw Py::AttributeError( msg );
                }
            }
            else
            {
                if( is_url )
                {
                    revision->kind = svn_opt_revision_head;
                }
                else
                {
                    revision->kind = svn_opt_revision_working;
                }
            }

            if( tuple_src_rev_pegrev.length() >= 3 )
            {
                Py::Object obj( tuple_src_rev_pegrev[2] );

                if( pysvn_revision::check( obj ) )
                {
                    pysvn_revision *rev = static_cast<pysvn_revision *>( obj.ptr() );
                    *peg_revision = rev->getSvnRevision();

                    revisionKindCompatibleCheck( is_url, *peg_revision,
                        "sources list 2nd tuple value", "sources list 1st tuple value" );
                }
                else
                {
                    std::string msg = "copy2() expecting revision for 3rd tuple value in sources list";
                    throw Py::AttributeError( msg );
                }
            }
            else
            {
                *peg_revision = *revision;
            }

            svn_client_copy_source_t *source = reinterpret_cast<svn_client_copy_source_t *>( apr_palloc( pool, sizeof(*source) ) );
            source->path = apr_pstrdup( pool, norm_src_url_or_path.c_str() );
            source->revision = revision;
            source->peg_revision = peg_revision;

            APR_ARRAY_PUSH( all_sources, svn_client_copy_source_t *) = source;
        }

        type_error_message = "expecting string for dest_url_or_path";
        Py::String dest_path( args.getUtf8String( name_dest_url_or_path ) );

        type_error_message = "expecting boolean for keyword copy_as_child";
        bool copy_as_child = args.getBoolean( name_copy_as_child, false );

        type_error_message = "expecting boolean for keyword make_parents";
        bool make_parents = args.getBoolean( name_make_parents, false );

        apr_hash_t *revprops = NULL;
        if( args.hasArg( name_revprops ) )
        {
            Py::Object py_revprop = args.getArg( name_revprops );
            if( !py_revprop.isNone() )
            {
                revprops = hashOfStringsFromDistOfStrings( py_revprop, pool );
            }
        }

        try
        {
            std::string norm_dest_path( svnNormalisedIfPath( dest_path, pool ) );

            checkThreadPermission();

            PythonAllowThreads permission( m_context );

            // behavior changed
            svn_error_t *error = svn_client_copy4
                (
                &commit_info,
                all_sources,
                norm_dest_path.c_str(),
                copy_as_child,
                make_parents,
                revprops,
                m_context,
                pool
                );
            permission.allowThisThread();
            if( error != NULL )
            {
                throw SvnException( error );
            }
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }
    }
    catch( Py::TypeError & )
    {
        throw Py::TypeError( type_error_message );
    }

    return toObject( commit_info );
}
Py::Object pysvn_client::cmd_export( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_src_url_or_path },
    { true,  name_dest_path },
    { false, name_force },
    { false, name_revision },
#if defined( PYSVN_HAS_CLIENT_EXPORT2 )
    { false, name_native_eol },
#endif
#if defined( PYSVN_HAS_CLIENT_EXPORT3 )
    { false, name_ignore_externals },
    { false, name_recurse },
    { false, name_peg_revision },
#endif
#if defined( PYSVN_HAS_CLIENT_EXPORT4 )
    { false, name_depth },
#endif
    { false, NULL }
    };
    FunctionArguments args( "export", args_desc, a_args, a_kws );
    args.check();

    std::string src_path( args.getUtf8String( name_src_url_or_path ) );
    std::string dest_path( args.getUtf8String( name_dest_path ) );
    bool is_url = is_svn_url( src_path );

    bool force = args.getBoolean( name_force, false );
    svn_opt_revision_t revision;
    if( is_url )
         revision = args.getRevision( name_revision, svn_opt_revision_head );
    else
         revision = args.getRevision( name_revision, svn_opt_revision_working );

#if defined( PYSVN_HAS_CLIENT_EXPORT2 )
    const char *native_eol = NULL;
    if( args.hasArg( name_native_eol ) )
    {
        Py::Object native_eol_obj = args.getArg( name_native_eol );
        if( native_eol_obj != Py::None() )
        {
            Py::String eol_py_str( native_eol_obj );
            std::string eol_str = eol_py_str.as_std_string( g_utf_8 );
            if( eol_str == "CR" )
                native_eol = "CR";
            else if( eol_str == "CRLF" )
                native_eol = "CRLF";
            else if( eol_str == "LF" )
                native_eol = "LF";
            else
                throw Py::ValueError( "native_eol must be one of None, \"LF\", \"CRLF\" or \"CR\"" );
        }
    }
#endif
#if defined( PYSVN_HAS_CLIENT_EXPORT3 )
#if defined( PYSVN_HAS_CLIENT_EXPORT4 )
    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_infinity, svn_depth_infinity, svn_depth_files );
#else
    bool recurse = args.getBoolean( name_recurse, true );
#endif
    bool ignore_externals = args.getBoolean( name_ignore_externals, false );
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, revision );

    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
#endif

    revisionKindCompatibleCheck( is_url, revision, name_revision, name_url_or_path );

    svn_revnum_t revnum = 0;

    SvnPool pool( m_context );

    try
    {
        std::string norm_src_path( svnNormalisedIfPath( src_path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_EXPORT4 )
        svn_error_t * error = svn_client_export4
            (
            &revnum,
            norm_src_path.c_str(),
            dest_path.c_str(),
            &peg_revision,
            &revision,
            force,
            ignore_externals,
            depth,
            native_eol,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_EXPORT3 )
        svn_error_t * error = svn_client_export3
            (
            &revnum,
            norm_src_path.c_str(),
            dest_path.c_str(),
            &peg_revision,
            &revision,
            force,
            ignore_externals,
            recurse,
            native_eol,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_EXPORT2 )
        svn_error_t * error = svn_client_export2
            (
            &revnum,
            norm_src_path.c_str(),
            dest_path.c_str(),
            &revision,
            force,
            native_eol,
            m_context,
            pool
            );
#else
        svn_error_t * error = svn_client_export
            (
            &revnum,
            norm_src_path.c_str(),
            dest_path.c_str(),
            &revision,
            force,
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    return Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, revnum ) );
}
Py::Object pysvn_client::cmd_ls( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { false, name_revision },
    { false, name_recurse },
#if defined( PYSVN_HAS_CLIENT_LS2 )
    { false, name_peg_revision },
#endif
    { false, NULL }
    };
    FunctionArguments args( "ls", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_url_or_path ) );
    bool recurse = args.getBoolean( name_recurse, false );
    svn_opt_revision_t revision = args.getRevision( name_revision, svn_opt_revision_head );

    SvnPool pool( m_context );
    apr_hash_t *hash = NULL;
    std::string norm_path( svnNormalisedIfPath( path, pool ) );
#if defined( PYSVN_HAS_CLIENT_LS2 )
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, revision );
#endif

    bool is_url = is_svn_url( path );
#if defined( PYSVN_HAS_CLIENT_LS2 )
    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
#endif
    revisionKindCompatibleCheck( is_url, revision, name_revision, name_url_or_path );

    try
    {
        checkThreadPermission();

        PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_LS2 )
        svn_error_t *error = svn_client_ls2
            (
            &hash,
            norm_path.c_str(),
            &peg_revision,
            &revision,
            recurse,
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_ls
            (
            &hash,
            norm_path.c_str(),
            &revision,
            recurse,
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != 0 )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }


    apr_array_header_t *array = svn_sort__hash( hash, compare_items_as_paths, pool );

    std::string base_path;
    if( !norm_path.empty() )
    {
        base_path = norm_path;
        base_path += '/';
    }

    // convert the entries into python objects
    Py::List entries_list;

    for( int i = 0; i < array->nelts; ++i )
    {
        svn_sort__item_t *item = &APR_ARRAY_IDX( array, i, svn_sort__item_t );

        const char *utf8_entryname = static_cast<const char *>( item->key );
        svn_dirent_t *dirent = static_cast<svn_dirent_t *>( apr_hash_get( hash, utf8_entryname, item->klen ) );

        std::string full_name( base_path );
        full_name += utf8_entryname;

        Py::Dict entry_dict;
        entry_dict[ *py_name_name ] = Py::String( full_name, name_utf8 );
        entry_dict[ *py_name_kind ] = toEnumValue( dirent->kind );
        entry_dict[ *py_name_has_props ] = Py::Int( dirent->has_props );
        entry_dict[ *py_name_size ] = Py::Long( Py::Float( double( static_cast<signed_int64>( dirent->size ) ) ) );
        entry_dict[ *py_name_created_rev ] = Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, dirent->created_rev ) );
        entry_dict[ *py_name_time ] = toObject( dirent->time );
        entry_dict[ *py_name_last_author ] = utf8_string_or_none( dirent->last_author );

        entries_list.append( m_wrapper_dirent.wrapDict( entry_dict ) );
    }

    return entries_list;
}
Py::Object pysvn_client::cmd_propset( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
        { true,  name_prop_name },
        { true,  name_prop_value },
        { true,  name_url_or_path },
        { false, name_revision },
        { false, name_recurse },
#if defined( PYSVN_HAS_CLIENT_PROPSET2 )
        { false, name_skip_checks },
#endif
#if defined( PYSVN_HAS_CLIENT_PROPSET3 )
        { false, name_depth },
        { false, name_base_revision_for_url },
        { false, name_changelists },
        { false, name_revprops },
#endif
        { false, NULL }
    };
    FunctionArguments args( "propset", args_desc, a_args, a_kws );
    args.check();

    std::string propname( args.getUtf8String( name_prop_name ) );
    std::string propval( args.getUtf8String( name_prop_value ) );
    std::string path( args.getUtf8String( name_url_or_path ) );

    svn_opt_revision_t revision;
    if( is_svn_url( path ) )
        revision = args.getRevision( name_revision, svn_opt_revision_head );
    else
        revision = args.getRevision( name_revision, svn_opt_revision_working );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_PROPSET3 )
    apr_array_header_t *changelists = NULL;

    if( args.hasArg( name_changelists ) )
    {
        changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
    }

    svn_revnum_t base_revision_for_url = args.getInteger( name_base_revision_for_url, 0 );
    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_files, svn_depth_empty );

    apr_hash_t *revprops = NULL;
    if( args.hasArg( name_revprops ) )
    {
        Py::Object py_revprop = args.getArg( name_revprops );
        if( !py_revprop.isNone() )
        {
            revprops = hashOfStringsFromDistOfStrings( py_revprop, pool );
        }
    }
#else
    bool recurse = args.getBoolean( name_recurse, false );
#endif
#if defined( PYSVN_HAS_CLIENT_PROPSET2 )
    bool skip_checks = args.getBoolean( name_skip_checks, false );
#endif

#if defined( PYSVN_HAS_CLIENT_PROPSET3 )
    pysvn_commit_info_t *commit_info = NULL;
#endif

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        const svn_string_t *svn_propval = svn_string_ncreate( propval.c_str(), propval.size(), pool );

#if defined( PYSVN_HAS_CLIENT_PROPSET3 )
        svn_error_t *error = svn_client_propset3
                             (
                                 &commit_info,
                                 propname.c_str(),
                                 svn_propval,
                                 norm_path.c_str(),
                                 depth,
                                 skip_checks,
                                 base_revision_for_url,
                                 changelists,
                                 revprops,
                                 m_context.ctx(),
                                 pool
                             );
#elif defined( PYSVN_HAS_CLIENT_PROPSET2 )
        svn_error_t *error = svn_client_propset2
                             (
                                 propname.c_str(),
                                 svn_propval,
                                 norm_path.c_str(),
                                 recurse,
                                 skip_checks,
                                 m_context.ctx(),
                                 pool
                             );
#else
        svn_error_t *error = svn_client_propset
                             (
                                 propname.c_str(),
                                 svn_propval,
                                 norm_path.c_str(),
                                 recurse,
                                 pool
                             );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

#if defined( PYSVN_HAS_CLIENT_PROPSET3 )
    return toObject( commit_info );
#else
    return Py::None();
#endif
}
Py::Object pysvn_client::cmd_annotate( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { false, name_revision_start },
    { false, name_revision_end },
#if defined( PYSVN_HAS_CLIENT_ANNOTATE2 )
    { false, name_peg_revision },
#endif
#if defined( PYSVN_HAS_CLIENT_ANNOTATE3 )
    { false, name_ignore_space },
    { false, name_ignore_eol_style },
    { false, name_ignore_mime_type },
#endif
#if defined( PYSVN_HAS_CLIENT_ANNOTATE4 )
    { false, name_include_merged_revisions },
#endif
    { false, NULL }
    };
    FunctionArguments args( "annotate", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_url_or_path, empty_string ) );
    svn_opt_revision_t revision_start = args.getRevision( name_revision_start, svn_opt_revision_number );
    svn_opt_revision_t revision_end = args.getRevision( name_revision_end, svn_opt_revision_head );
#if defined( PYSVN_HAS_CLIENT_ANNOTATE2 )
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, revision_end );
#endif
#if defined( PYSVN_HAS_CLIENT_ANNOTATE3 )
    svn_diff_file_ignore_space_t ignore_space = svn_diff_file_ignore_space_none;
    if( args.hasArg( name_ignore_space ) )
    {
        Py::ExtensionObject< pysvn_enum_value<svn_diff_file_ignore_space_t> > py_ignore_space( args.getArg( name_ignore_space ) );
        ignore_space = svn_diff_file_ignore_space_t( py_ignore_space.extensionObject()->m_value );
    }

    svn_boolean_t ignore_eol_style = args.getBoolean( name_ignore_eol_style, false );
    svn_boolean_t ignore_mime_type = args.getBoolean( name_ignore_mime_type, false );
#endif
#if defined( PYSVN_HAS_CLIENT_ANNOTATE4 )
    svn_boolean_t include_merged_revisions = args.getBoolean( name_include_merged_revisions, false );
#endif
    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_ANNOTATE3 )
    svn_diff_file_options_t *diff_options = svn_diff_file_options_create( pool );
    diff_options->ignore_space = ignore_space;
    diff_options->ignore_eol_style = ignore_eol_style;
#endif

    bool is_url = is_svn_url( path );
#if defined( PYSVN_HAS_CLIENT_ANNOTATE2 )
    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
#endif
    revisionKindCompatibleCheck( is_url, revision_start, name_revision_start, name_url_or_path );
    revisionKindCompatibleCheck( is_url, revision_end, name_revision_end, name_url_or_path );

    std::list<AnnotatedLineInfo> all_entries;

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_ANNOTATE4 )
        svn_error_t *error = svn_client_blame4
            (
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            diff_options,
            ignore_mime_type,
            include_merged_revisions,
            annotate_receiver,
            &all_entries,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_ANNOTATE3 )
        svn_error_t *error = svn_client_blame3
            (
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            diff_options,
            ignore_mime_type,
            annotate_receiver,
            &all_entries,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_ANNOTATE2 )
        svn_error_t *error = svn_client_blame2
            (
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            annotate_receiver,
            &all_entries,
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_blame
            (
            norm_path.c_str(),
            &revision_start,
            &revision_end,
            annotate_receiver,
            &all_entries,
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
        {
            throw SvnException( error );
        }
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    // convert the entries into python objects
    Py::List entries_list;
    std::list<AnnotatedLineInfo>::const_iterator entry_it = all_entries.begin();
    while( entry_it != all_entries.end() )
    {
        const AnnotatedLineInfo &entry = *entry_it;
        ++entry_it;

        Py::Dict entry_dict;
        entry_dict[name_author] = Py::String( entry.m_author, name_utf8 );
        entry_dict[name_date] = Py::String( entry.m_date );
        entry_dict[name_line] = Py::String( entry.m_line );
        entry_dict[name_number] = Py::Int( long( entry.m_line_no ) );
        entry_dict[name_revision] = Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, entry.m_revision ) );

        entries_list.append( entry_dict );
    }

    return entries_list;
}
Py::Object pysvn_client::cmd_status( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_path },
    { false, name_recurse },
    { false, name_get_all },
    { false, name_update },
    { false, name_ignore },
#if defined( PYSVN_HAS_CLIENT_STATUS2 )
    { false, name_ignore_externals },
#endif
#if defined( PYSVN_HAS_CLIENT_STATUS3 )
    { false, name_depth },
    { false, name_changelists },
#endif
    { false, NULL }
    };
    FunctionArguments args( "status", args_desc, a_args, a_kws );
    args.check();

    Py::String path( args.getUtf8String( name_path ) );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_STATUS3 )
    apr_array_header_t *changelists = NULL;

    if( args.hasArg( name_changelists ) )
    {
        changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
    }

    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_infinity, svn_depth_infinity, svn_depth_immediates );
#else
    bool recurse = args.getBoolean( name_recurse, true );
#endif
    bool get_all = args.getBoolean( name_get_all, true );
    bool update = args.getBoolean( name_update, false );
    bool ignore = args.getBoolean( name_ignore, false );
#if defined( PYSVN_HAS_CLIENT_STATUS2 )
    bool ignore_externals = args.getBoolean( name_ignore_externals, false );
#endif

    apr_hash_t *status_hash = NULL;

    Py::List entries_list;
    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        svn_revnum_t revnum;
        svn_opt_revision_t rev = { svn_opt_revision_head, {0} };

        StatusEntriesBaton baton;

        status_hash = apr_hash_make( pool );
        baton.hash = status_hash;
        baton.pool = pool;

#if defined( PYSVN_HAS_CLIENT_STATUS4 )
        svn_error_t *error = svn_client_status4
            (
            &revnum,            // revnum
            norm_path.c_str(),  // path
            &rev,
            StatusEntriesFunc,  // status func
            &baton,             // status baton
            depth,
            get_all,
            update,
            !ignore,
            ignore_externals,
            changelists,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_STATUS3 )
        svn_error_t *error = svn_client_status3
            (
            &revnum,            // revnum
            norm_path.c_str(),  // path
            &rev,
            StatusEntriesFunc,  // status func
            &baton,             // status baton
            depth,
            get_all,
            update,
            !ignore,
            ignore_externals,
            changelists,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_STATUS2 )
        svn_error_t *error = svn_client_status2
            (
            &revnum,            // revnum
            norm_path.c_str(),  // path
            &rev,
            StatusEntriesFunc,  // status func
            &baton,             // status baton
            recurse,
            get_all,
            update,
            !ignore,
            ignore_externals,
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_status
            (
            &revnum,            // revnum
            norm_path.c_str(),  // path
            &rev,
            StatusEntriesFunc,  // status func
            &baton,             // status baton
            recurse,
            get_all,
            update,
            !ignore,
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    apr_array_header_t *statusarray = svn_sort__hash( status_hash, svn_sort_compare_items_as_paths, pool );

    // Loop over array, printing each name/status-structure
    for (int i = statusarray->nelts-1; i >= 0; i--)
    {
        const svn_sort__item_t *item = &APR_ARRAY_IDX( statusarray, i, const svn_sort__item_t );
        pysvn_wc_status_t *status = (pysvn_wc_status_t *)item->value;

        entries_list.append( toObject(
                Py::String( osNormalisedPath( (const char *)item->key, pool ), "UTF-8" ),
                *status,
                pool,
                m_wrapper_status,
                m_wrapper_entry,
                m_wrapper_lock ) );
    }

    return entries_list;
}
Py::Object pysvn_client::cmd_diff_summarize_peg( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { false, name_peg_revision },
    { false, name_revision_start },
    { false, name_revision_end },
    { false, name_recurse },
    { false, name_ignore_ancestry },
#if defined( PYSVN_HAS_CLIENT_DIFF_SUMMARIZE_PEG2 )
    { false, name_depth },
    { false, name_changelists },
#endif
    { false, NULL }
    };
    FunctionArguments args( "diff_summarize_peg", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_url_or_path ) );
    svn_opt_revision_t revision_start = args.getRevision( name_revision_start, svn_opt_revision_base );
    svn_opt_revision_t revision_end = args.getRevision( name_revision_end, svn_opt_revision_working );
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, revision_end );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_DIFF_SUMMARIZE_PEG2 )
    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_infinity, svn_depth_infinity, svn_depth_files );
    apr_array_header_t *changelists = NULL;

    if( args.hasArg( name_changelists ) )
    {
        changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
    }
#else
    bool recurse = args.getBoolean( name_recurse, true );
#endif
    bool ignore_ancestry = args.getBoolean( name_ignore_ancestry, true );

    bool is_url = is_svn_url( path );
    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
    revisionKindCompatibleCheck( is_url, revision_start, name_revision_start, name_url_or_path );
    revisionKindCompatibleCheck( is_url, revision_end, name_revision_end, name_url_or_path );

    Py::List diff_list;

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        DiffSummarizeBaton diff_baton( &permission, diff_list );
        diff_baton.m_wrapper_diff_summary = &m_wrapper_diff_summary;

#if defined( PYSVN_HAS_CLIENT_DIFF_SUMMARIZE_PEG2 )
        svn_error_t *error = svn_client_diff_summarize_peg2
            (
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            depth,
            ignore_ancestry,
            changelists,
            diff_summarize_c,
            reinterpret_cast<void *>( &diff_baton ),
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_diff_summarize_peg
            (
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            recurse,
            ignore_ancestry,
            diff_summarize_c,
            reinterpret_cast<void *>( &diff_baton ),
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    // cannot convert to Unicode as we have no idea of the encoding of the bytes
    return diff_list;
}
Py::Object pysvn_client::cmd_diff_peg( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_tmp_path },
    { true,  name_url_or_path },
    { false, name_peg_revision },
    { false, name_revision_start },
    { false, name_revision_end },
    { false, name_recurse },
    { false, name_ignore_ancestry },
    { false, name_diff_deleted },
#if defined( PYSVN_HAS_CLIENT_DIFF_PEG2 )
    { false, name_ignore_content_type },
#endif
#if defined( PYSVN_HAS_CLIENT_DIFF_PEG3 )
    { false, name_header_encoding },
    { false, name_diff_options },
#endif
#if defined( PYSVN_HAS_CLIENT_DIFF_PEG4 )
    { false, name_depth },
    { false, name_relative_to_dir },
    { false, name_changelists },
#endif
    { false, NULL }
    };
    FunctionArguments args( "diff_peg", args_desc, a_args, a_kws );
    args.check();

    std::string tmp_path( args.getUtf8String( name_tmp_path ) );
    std::string path( args.getUtf8String( name_url_or_path ) );
    svn_opt_revision_t revision_start = args.getRevision( name_revision_start, svn_opt_revision_base );
    svn_opt_revision_t revision_end = args.getRevision( name_revision_end, svn_opt_revision_working );
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, revision_end );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_DIFF_PEG4 )
    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_infinity, svn_depth_infinity, svn_depth_files );
    std::string std_relative_to_dir;
    const char *relative_to_dir = NULL;
    if( args.hasArg( name_relative_to_dir ) )
    {
        std_relative_to_dir = args.getBytes( name_relative_to_dir );
        relative_to_dir = std_relative_to_dir.c_str();
    }

    apr_array_header_t *changelists = NULL;

    if( args.hasArg( name_changelists ) )
    {
        changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
    }
#else
    bool recurse = args.getBoolean( name_recurse, true );
#endif
    bool ignore_ancestry = args.getBoolean( name_ignore_ancestry, true );
    bool diff_deleted = args.getBoolean( name_diff_deleted, true );
#if defined( PYSVN_HAS_CLIENT_DIFF_PEG2 )
    bool ignore_content_type = args.getBoolean( name_ignore_content_type, false );
#endif

#if defined( PYSVN_HAS_CLIENT_DIFF_PEG3 )
    std::string header_encoding( args.getUtf8String( name_header_encoding, empty_string ) );
    const char *header_encoding_ptr = APR_LOCALE_CHARSET;
    if( !header_encoding.empty() )
        header_encoding_ptr = header_encoding.c_str();

    apr_array_header_t *options = NULL;
    if( args.hasArg( name_diff_options ) )
    {
        options = arrayOfStringsFromListOfStrings( args.getArg( name_diff_options ), pool );
    }
    else
    {
        options = apr_array_make( pool, 0, sizeof( const char * ) );
    }
#else
    apr_array_header_t *options = apr_array_make( pool, 0, sizeof( const char * ) );
#endif

    bool is_url = is_svn_url( path );
    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
    revisionKindCompatibleCheck( is_url, revision_start, name_revision_start, name_url_or_path );
    revisionKindCompatibleCheck( is_url, revision_end, name_revision_end, name_url_or_path );

    svn_stringbuf_t *stringbuf = NULL;

    try
    {
        std::string norm_tmp_path( svnNormalisedIfPath( tmp_path, pool ) );
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );
        pysvn_apr_file output_file( pool );
        pysvn_apr_file error_file( pool );

        output_file.open_unique_file( norm_tmp_path );
        error_file.open_unique_file( norm_tmp_path );

        // std::cout << "peg_revision "    << peg_revision.kind    << " " << peg_revision.value.number     << std::endl;
        // std::cout << "revision_start "  << revision_start.kind  << " " << revision_start.value.number   << std::endl;
        // std::cout << "revision_end "    << revision_end.kind    << " " << revision_end.value.number     << std::endl;

#if defined( PYSVN_HAS_CLIENT_DIFF_PEG4 )
        svn_error_t *error = svn_client_diff_peg4
            (
            options,
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            relative_to_dir,
            depth,
            ignore_ancestry,
            !diff_deleted,
            ignore_content_type,
            header_encoding_ptr,
            output_file.file(),
            error_file.file(),
            changelists,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_DIFF_PEG3 )
        svn_error_t *error = svn_client_diff_peg3
            (
            options,
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            recurse,
            ignore_ancestry,
            !diff_deleted,
            ignore_content_type,
            header_encoding_ptr,
            output_file.file(),
            error_file.file(),
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_DIFF_PEG2 )
        svn_error_t *error = svn_client_diff_peg2
            (
            options,
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            recurse,
            ignore_ancestry,
            !diff_deleted,
            ignore_content_type,
            output_file.file(),
            error_file.file(),
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_diff_peg
            (
            options,
            norm_path.c_str(),
            &peg_revision,
            &revision_start,
            &revision_end,
            recurse,
            ignore_ancestry,
            !diff_deleted,
            output_file.file(),
            error_file.file(),
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );

        output_file.close();

        output_file.open_tmp_file();
        error = svn_stringbuf_from_aprfile( &stringbuf, output_file.file(), pool );
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    // cannot convert to Unicode as we have no idea of the encoding of the bytes
    return Py::String( stringbuf->data, (int)stringbuf->len );
}
Py::Object pysvn_client::cmd_import( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_path },
    { true,  name_url },
    { true,  name_log_message },
    { false, name_recurse },
#if defined( PYSVN_HAS_CLIENT_IMPORT2 )
    { false, name_ignore },
#endif
#if defined( PYSVN_HAS_CLIENT_IMPORT3 )
    { false, name_depth },
    { false, name_ignore_unknown_node_types },
    { false, name_revprops },
#endif
    { false, NULL }
    };
    FunctionArguments args( "import_", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_path ) );
    std::string url( args.getUtf8String( name_url ) );
    std::string message( args.getUtf8String( name_log_message ) );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_IMPORT3 )
    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_infinity, svn_depth_infinity, svn_depth_files );
    bool ignore_unknown_node_types = args.getBoolean( name_ignore_unknown_node_types, false );

    apr_hash_t *revprops = NULL;
    if( args.hasArg( name_revprops ) )
    {
        Py::Object py_revprop = args.getArg( name_revprops );
        if( !py_revprop.isNone() )
        {
            revprops = hashOfStringsFromDistOfStrings( py_revprop, pool );
        }
    }
#else
    bool recurse = args.getBoolean( name_recurse, true );
#endif
#if defined( PYSVN_HAS_CLIENT_IMPORT2 )
    bool ignore = args.getBoolean( name_ignore, false );
#endif

    pysvn_commit_info_t *commit_info = NULL;

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        m_context.setLogMessage( message.c_str() );

#if defined( PYSVN_HAS_CLIENT_IMPORT3 )
        svn_error_t *error = svn_client_import3
            (
            &commit_info,       // changed type
            norm_path.c_str(),
            url.c_str(),
            depth,
            !ignore,
            ignore_unknown_node_types,
            revprops,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_IMPORT2 )
        svn_error_t *error = svn_client_import2
            (
            &commit_info,       // changed type
            norm_path.c_str(),
            url.c_str(),
            !recurse,           // non_recursive
            !ignore,
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_import
            (
            &commit_info,
            norm_path.c_str(),
            url.c_str(),
            !recurse,           // non_recursive
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    return toObject( commit_info, m_commit_info_style );
}
Py::Object pysvn_client::cmd_info2( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { false, name_revision },
    { false, name_peg_revision},
    { false, name_recurse },
#if defined( PYSVN_HAS_CLIENT_INFO2 )
    { false, name_depth },
    { false, name_changelists },
#endif
    { false, NULL }
    };
    FunctionArguments args( "info2", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_url_or_path ) );

    svn_opt_revision_kind kind = svn_opt_revision_unspecified;
    if( is_svn_url( path ) )
        kind = svn_opt_revision_head;

    svn_opt_revision_t revision = args.getRevision( name_revision, kind );
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, revision );

    SvnPool pool( m_context );

#if defined( PYSVN_HAS_CLIENT_INFO2 )
    apr_array_header_t *changelists = NULL;

    if( args.hasArg( name_changelists ) )
    {
        changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );
    }

    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_infinity, svn_depth_infinity, svn_depth_empty );
#else
    bool recurse = args.getBoolean( name_recurse, true );
#endif

    bool is_url = is_svn_url( path );
    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
    revisionKindCompatibleCheck( is_url, revision, name_revision, name_url_or_path );

    Py::List info_list;

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        InfoReceiveBaton info_baton( &permission, info_list, m_wrapper_info, m_wrapper_lock, m_wrapper_wc_info );

#if defined( PYSVN_HAS_CLIENT_INFO2 )
        svn_error_t *error = 
            svn_client_info2
                (
                norm_path.c_str(),
                &peg_revision,
                &revision,
                info_receiver_c,
                reinterpret_cast<void *>( &info_baton ),
                depth,
                changelists,
                m_context,
                pool
                );
#else
        svn_error_t *error = 
            svn_client_info
                (
                norm_path.c_str(),
                &peg_revision,
                &revision,
                info_receiver_c,
                reinterpret_cast<void *>( &info_baton ),
                recurse,
                m_context,
                pool
                );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    return info_list;
}
Py::Object pysvn_client::cmd_list( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { false, name_peg_revision },
    { false, name_revision },
    { false, name_recurse },
    { false, name_dirent_fields },
    { false, name_fetch_locks },
#if defined( PYSVN_HAS_CLIENT_LIST2 )
    { false, name_depth },
#endif
    { false, NULL }
    };
    FunctionArguments args( "list", args_desc, a_args, a_kws );
    args.check();

    std::string path( args.getUtf8String( name_url_or_path ) );
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, svn_opt_revision_unspecified );
    bool is_url = is_svn_url( path );
    svn_opt_revision_t revision;
    if( is_url )
         revision = args.getRevision( name_revision, svn_opt_revision_head );
    else
         revision = args.getRevision( name_revision, svn_opt_revision_working );
#if defined( PYSVN_HAS_CLIENT_LIST2 )
    svn_depth_t depth = args.getDepth( name_depth, name_recurse, svn_depth_immediates, svn_depth_infinity, svn_depth_immediates );
#else
    bool recurse = args.getBoolean( name_recurse, false );
#endif
    apr_uint32_t dirent_fields = args.getLong( name_dirent_fields, SVN_DIRENT_ALL );
    bool fetch_locks = args.getBoolean( name_fetch_locks, false );

    revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
    revisionKindCompatibleCheck( is_url, revision, name_revision, name_url_or_path );

    SvnPool pool( m_context );
    std::string norm_path( svnNormalisedIfPath( path, pool ) );

    Py::List list_list;

    try
    {
        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        ListReceiveBaton list_baton( &permission, list_list );
        list_baton.m_dirent_fields = dirent_fields;
        list_baton.m_is_url = is_url;
        list_baton.m_fetch_locks = fetch_locks;
        list_baton.m_url_or_path = norm_path;
        list_baton.m_wrapper_lock = &m_wrapper_lock;
        list_baton.m_wrapper_list = &m_wrapper_list;

#if defined( PYSVN_HAS_CLIENT_LIST2 )
        svn_error_t *error = svn_client_list2
            (
            norm_path.c_str(),
            &peg_revision,
            &revision,
            depth,
            dirent_fields,
            fetch_locks,
            list_receiver_c,
            reinterpret_cast<void *>( &list_baton ),
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_list
            (
            norm_path.c_str(),
            &peg_revision,
            &revision,
            recurse,
            dirent_fields,
            fetch_locks,
            list_receiver_c,
            reinterpret_cast<void *>( &list_baton ),
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != 0 )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    return list_list;
}
// PYSVN_HAS_CLIENT_LOG4 version
Py::Object pysvn_client::cmd_log( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { false, name_revision_start },
    { false, name_revision_end },
    { false, name_discover_changed_paths },
    { false, name_strict_node_history },
    { false, name_limit },
    { false, name_peg_revision },
    { false, name_include_merged_revisions },
    { false, name_revprops },
    { false, NULL }
    };
    FunctionArguments args( "log", args_desc, a_args, a_kws );
    args.check();

    SvnPool pool( m_context );

    svn_opt_revision_t revision_start = args.getRevision( name_revision_start, svn_opt_revision_head );
    svn_opt_revision_t revision_end = args.getRevision( name_revision_end, svn_opt_revision_number );
    bool discover_changed_paths = args.getBoolean( name_discover_changed_paths, false );
    bool strict_node_history = args.getBoolean( name_strict_node_history, true );
    int limit = args.getInteger( name_limit, 0 );
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, svn_opt_revision_unspecified );

    svn_boolean_t include_merged_revisions = args.getBoolean( name_include_merged_revisions, false );
    apr_array_header_t *revprops = NULL;
    if( args.hasArg( name_revprops ) )
    {
        Py::Object py_revprop = args.getArg( name_revprops );
        if( !py_revprop.isNone() )
        {
            revprops = arrayOfStringsFromListOfStrings( py_revprop, pool );
        }
    }

    Py::Object url_or_path_obj = args.getArg( name_url_or_path );
    Py::List url_or_path_list;
    if( url_or_path_obj.isList() )
    {
        url_or_path_list = url_or_path_obj;
    }
    else
    {
        Py::List py_list;
        py_list.append( url_or_path_obj );
        url_or_path_list = py_list;
    }

    for( size_t i=0; i<url_or_path_list.size(); i++ )
    {
        Py::Bytes py_path( asUtf8Bytes( url_or_path_list[ i ] ) );
        std::string path( py_path.as_std_string() );
        bool is_url = is_svn_url( path );

        revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
        revisionKindCompatibleCheck( is_url, revision_start, name_revision_start, name_url_or_path );
        revisionKindCompatibleCheck( is_url, revision_end, name_revision_end, name_url_or_path );
    }

    apr_array_header_t *targets = targetsFromStringOrList( url_or_path_list, pool );

    Py::List log_list;

    try
    {
        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        Log4Baton baton( &permission, pool, log_list );
        baton.m_wrapper_log = &m_wrapper_log;
        baton.m_wrapper_log_changed_path = &m_wrapper_log_changed_path;

#if defined( PYSVN_HAS_CLIENT_LOG5 )
        apr_array_header_t *revision_ranges = apr_array_make( pool, 0, sizeof(svn_opt_revision_range_t *) );
        svn_opt_revision_range_t *range = reinterpret_cast<svn_opt_revision_range_t *>( apr_palloc( pool, sizeof(*range) ) );

        range->start = revision_start;
        range->end = revision_end;

        APR_ARRAY_PUSH( revision_ranges, svn_opt_revision_range_t * ) = range;

        svn_error_t *error = svn_client_log5
            (
            targets,
            &peg_revision,
            revision_ranges,
            limit,
            discover_changed_paths,
            strict_node_history,
            include_merged_revisions,
            revprops,
            log4Receiver,
            reinterpret_cast<void *>( &baton ),
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_log4
            (
            targets,
            &peg_revision,
            &revision_start,
            &revision_end,
            limit,
            discover_changed_paths,
            strict_node_history,
            include_merged_revisions,
            revprops,
            log4Receiver,
            reinterpret_cast<void *>( &baton ),
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    return log_list;
}
// PYSVN_HAS_CLIENT_LOG, PYSVN_HAS_CLIENT_LOG2, PYSVN_HAS_CLIENT_LOG3 version
Py::Object pysvn_client::cmd_log( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_url_or_path },
    { false, name_revision_start },
    { false, name_revision_end },
    { false, name_discover_changed_paths },
    { false, name_strict_node_history },
#if defined( PYSVN_HAS_CLIENT_LOG2 ) || defined( PYSVN_HAS_CLIENT_LOG3 )
    { false, name_limit },
#endif
#if defined( PYSVN_HAS_CLIENT_LOG3 )
    { false, name_peg_revision },
#endif
#if defined( PYSVN_HAS_CLIENT_LOG4 )
    { false, name_include_merged_revisions },
    { false, name_revprops },
#endif
    { false, NULL }
    };
    FunctionArguments args( "log", args_desc, a_args, a_kws );
    args.check();

    svn_opt_revision_t revision_start = args.getRevision( name_revision_start, svn_opt_revision_head );
    svn_opt_revision_t revision_end = args.getRevision( name_revision_end, svn_opt_revision_number );
    bool discover_changed_paths = args.getBoolean( name_discover_changed_paths, false );
    bool strict_node_history = args.getBoolean( name_strict_node_history, true );
    int limit = args.getInteger( name_limit, 0 );
#if defined( PYSVN_HAS_CLIENT_LOG3 )
    svn_opt_revision_t peg_revision = args.getRevision( name_peg_revision, svn_opt_revision_unspecified );
#endif
#if defined( PYSVN_HAS_CLIENT_LOG4 )
    svn_boolean_t include_merged_revisions = args.getBoolean( name_include_merged_revisions, false );
    apr_array_header_t *revprops = NULL;
    Py::Object py_revprop = args.getArg( name_revprops );
    if( py_revprop is not None )
    {
        revprops = arrayOfStringsFromListOfStrings( py_revprop. pool );
    }
#endif

    Py::Object url_or_path_obj = args.getArg( name_url_or_path );
    Py::List url_or_path_list;
    if( url_or_path_obj.isList() )
    {
        url_or_path_list = url_or_path_obj;
    }
    else
    {
        Py::List py_list;
        py_list.append( url_or_path_obj );
        url_or_path_list = py_list;
    }

    for( size_t i=0; i<url_or_path_list.size(); i++ )
    {
        Py::Bytes py_path( asUtf8Bytes( url_or_path_list[ i ] ) );
        std::string path( py_path.as_std_string() );
        bool is_url = is_svn_url( path );

        // std::cout << "peg_revision "    << peg_revision.kind    << " " << peg_revision.value.number     << std::endl;
        // std::cout << "revision_start "  << revision_start.kind  << " " << revision_start.value.number   << std::endl;
        // std::cout << "revision_end "    << revision_end.kind    << " " << revision_end.value.number     << std::endl;

#if defined( PYSVN_HAS_CLIENT_LOG3 )
        revisionKindCompatibleCheck( is_url, peg_revision, name_peg_revision, name_url_or_path );
#endif
        revisionKindCompatibleCheck( is_url, revision_start, name_revision_start, name_url_or_path );
        revisionKindCompatibleCheck( is_url, revision_end, name_revision_end, name_url_or_path );
    }

    SvnPool pool( m_context );

    apr_array_header_t *targets = targetsFromStringOrList( url_or_path_list, pool );

#if defined( PYSVN_HAS_CLIENT_LOG4 )
    Log4Baton baton( permission, pool );
#else
    std::list<LogEntryInfo> all_entries;
#endif

    try
    {
        checkThreadPermission();

        PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_LOG4 )
        svn_error_t *error = svn_client_log4
            (
            targets,
            &peg_revision,
            &revision_start,
            &revision_end,
            limit,
            discover_changed_paths,
            strict_node_history,
            include_merged_revisions,
            revprops,
            logReceiver,
            &all_entries,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_LOG3 )
        svn_error_t *error = svn_client_log3
            (
            targets,
            &peg_revision,
            &revision_start,
            &revision_end,
            limit,
            discover_changed_paths,
            strict_node_history,
            logReceiver,
            &all_entries,
            m_context,
            pool
            );
#elif defined( PYSVN_HAS_CLIENT_LOG2 )
        svn_error_t *error = svn_client_log2
            (
            targets,
            &revision_start,
            &revision_end,
            limit,
            discover_changed_paths,
            strict_node_history,
            logReceiver,
            &all_entries,
            m_context,
            pool
            );
#else
        svn_error_t *error = svn_client_log
            (
            targets,
            &revision_start,
            &revision_end,
            discover_changed_paths,
            strict_node_history,
            logReceiver,
            &all_entries,
            m_context,
            pool
            );
#endif
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    apr_time_t now = apr_time_now();

    // convert the entries into python objects
    Py::List entries_list;
    std::list<LogEntryInfo>::const_iterator entry_it = all_entries.begin();
    while( entry_it != all_entries.end() )
    {
        const LogEntryInfo &entry = *entry_it;
        ++entry_it;

        Py::Dict entry_dict;
        entry_dict[name_author] = Py::String( entry.m_author, name_utf8 );
        entry_dict[name_date] = toObject( convertStringToTime( entry.m_date, now, pool ) );
        entry_dict[name_message] = Py::String( entry.m_message, name_utf8 );
        entry_dict[name_revision] = Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, entry.m_revision ) );

        Py::List changed_paths_list;
        std::list<LogChangePathInfo>::const_iterator changed_paths_it = entry.m_changed_paths.begin();
        while( changed_paths_it != entry.m_changed_paths.end() )
        {
            const LogChangePathInfo &change_entry = *changed_paths_it;
            ++changed_paths_it;

            Py::Dict changed_entry_dict;
            changed_entry_dict[name_path] = Py::String( change_entry.m_path, name_utf8 );
            changed_entry_dict[name_action] = Py::String( &change_entry.m_action, 1 );
            changed_entry_dict[name_copyfrom_path] = utf8_string_or_none( change_entry.m_copy_from_path );

            if( SVN_IS_VALID_REVNUM( change_entry.m_copy_from_revision ) )
                changed_entry_dict[name_copyfrom_revision] = Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, change_entry.m_copy_from_revision ) );
            else
                changed_entry_dict[name_copyfrom_revision] = Py::None();

            changed_paths_list.append( m_wrapper_log_changed_path.wrapDict( changed_entry_dict ) );
        }

        entry_dict[name_changed_paths] = changed_paths_list;

        entries_list.append( m_wrapper_log.wrapDict( entry_dict ) );
    }

    return entries_list;
}
Py::Object pysvn_client::cmd_get_changelist( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_path },
    { false, name_depth },
    { false, name_changelists },
    { false, NULL }
    };
    FunctionArguments args( "get_changelists", args_desc, a_args, a_kws );
    args.check();

    std::string type_error_message;

    SvnPool pool( m_context );

    try
    {
        std::string path( args.getUtf8String( name_path ) );
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        apr_array_header_t *changelists = NULL;
        if( args.hasArg( name_changelists ) )
        {
            changelists = arrayOfStringsFromListOfStrings( args.getArg( name_changelists ), pool );

            //for (int j = 0; j < changelists->nelts; ++j)
            //{
            //    const char *name = ((const char **)changelists->elts)[j];
            //    std::cout << "QQQ: get changelist=" << name << std::endl;
            //}
        }

        svn_depth_t depth = args.getDepth( name_depth, svn_depth_files );

        Py::List changelist_list;

        try
        {
            checkThreadPermission();

            PythonAllowThreads permission( m_context );

            ChangelistBaton baton( &permission, pool, changelist_list );

            svn_error_t *error = svn_client_get_changelists
                (
                norm_path.c_str(),
                changelists,
                depth,
                changelistReceiver,
                reinterpret_cast<void *>( &baton ),
                m_context,
                pool
                );
            permission.allowThisThread();
            if( error != NULL )
                throw SvnException( error );
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }

        return changelist_list;
    }
    catch( Py::TypeError & )
    {
        throw Py::TypeError( type_error_message );
    }

    return Py::None();
}
Py::Object pysvn_client::cmd_revpropget( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_prop_name },
    { true,  name_url },
    { false, name_revision },
    { false, NULL }
    };
    FunctionArguments args( "revpropget", args_desc, a_args, a_kws );
    args.check();

    std::string propname( args.getUtf8String( name_prop_name ) );
    std::string path( args.getUtf8String( name_url ) );
    svn_opt_revision_t revision = args.getRevision( name_revision, svn_opt_revision_head );

    SvnPool pool( m_context );

    svn_string_t *propval = NULL;
    svn_revnum_t revnum = 0;

    try
    {
        std::string norm_path( svnNormalisedIfPath( path, pool ) );

        checkThreadPermission();

        PythonAllowThreads permission( m_context );

        svn_error_t * error = svn_client_revprop_get
            (
            propname.c_str(),
            &propval,
            norm_path.c_str(),
            &revision,
            &revnum,
            m_context,
            pool
            );
        permission.allowThisThread();
        if( error != NULL )
            throw SvnException( error );
    }
    catch( SvnException &e )
    {
        // use callback error over ClientException
        m_context.checkForError( m_module.client_error );

        throw_client_error( e );
    }

    Py::Tuple result(2);
    result[0] = Py::asObject( new pysvn_revision( svn_opt_revision_number, 0, revnum ) );
    // prop_name that is not in this rev returns a NULL value
    if( propval == NULL )
    {
        result[1] = Py::None();
    }
    else
    {
        result[1] = Py::String( propval->data, propval->len, name_utf8 );
    }

    return result;
}
Py::Object pysvn_client::cmd_copy( const Py::Tuple &a_args, const Py::Dict &a_kws )
{
    static argument_description args_desc[] =
    {
    { true,  name_src_url_or_path },
    { true,  name_dest_url_or_path },
    { false, name_src_revision },
    { false, NULL }
    };
    FunctionArguments args( "copy", args_desc, a_args, a_kws );
    args.check();

    SvnPool pool( m_context );
    pysvn_commit_info_t *commit_info = NULL;

    std::string type_error_message;
    try
    {
        type_error_message = "expecting string for src_path (arg 1)";
        Py::String src_path( args.getUtf8String( name_src_url_or_path ) );

        type_error_message = "expecting string for dest_path (arg 2)";
        Py::String dest_path( args.getUtf8String( name_dest_url_or_path ) );

        type_error_message = "expecting revision for keyword src_revision";
        svn_opt_revision_t revision;
        if( is_svn_url( src_path ) )
            revision = args.getRevision( name_src_revision, svn_opt_revision_head );
        else
            revision = args.getRevision( name_src_revision, svn_opt_revision_working );

        try
        {
            std::string norm_src_path( svnNormalisedIfPath( src_path, pool ) );
            std::string norm_dest_path( svnNormalisedIfPath( dest_path, pool ) );

            checkThreadPermission();

            PythonAllowThreads permission( m_context );

#if defined( PYSVN_HAS_CLIENT_COPY3 )
            // behavior changed
            svn_error_t *error = svn_client_copy3
                (
                &commit_info,
                norm_src_path.c_str(),
                &revision,
                norm_dest_path.c_str(),
                m_context,
                pool
                );
#elif defined( PYSVN_HAS_CLIENT_COPY2 )
            svn_error_t *error = svn_client_copy2
                (
                &commit_info,       // commit info type changed
                norm_src_path.c_str(),
                &revision,
                norm_dest_path.c_str(),
                m_context,
                pool
                );
#else
            svn_error_t *error = svn_client_copy
                (
                &commit_info,
                norm_src_path.c_str(),
                &revision,
                norm_dest_path.c_str(),
                m_context,
                pool
                );
#endif
            permission.allowThisThread();
            if( error != NULL )
                throw SvnException( error );
        }
        catch( SvnException &e )
        {
            // use callback error over ClientException
            m_context.checkForError( m_module.client_error );

            throw_client_error( e );
        }
    }
    catch( Py::TypeError & )
    {
        throw Py::TypeError( type_error_message );
    }

    return toObject( commit_info );
}