Home modules.gotpike.org
Username: Password: [Create Account]
[Forgot Password?]
Return to module

File Contents

Contents of /Subversion-0.112/client.c:

/* ================================================================
 * Copyright (c) 2000-2004 CollabNet.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowledgment: "This product includes
 * software developed by CollabNet (http://www.Collab.Net/)."
 * Alternately, this acknowledgment may appear in the software itself, if
 * and wherever such third-party acknowledgments normally appear.
 * 
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@collab.net.
 * 
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */


#include "svnmod.h"

#include <svn_client.h>
#include <svn_sorts.h>
#include <svn_path.h>


/*! @module Subversion */


/*! @class Client
 *!
 *!  The Client class provides convenient access to most user-level
 *!  operations.
 */

/* Function indices for callbacks */
static int notify_function, log_msg_function, cancel_function;

/* Internal object structure */
struct client_obj {
  svn_client_ctx_t *ctx;
  apr_pool_t *pool;
  struct array *auth_providers;
  struct mapping *config;
  struct svalue notify_func, log_msg_func, cancel_func;
};
#define THIS ((struct client_obj *)(Pike_fp->current_storage))


/*! @decl void notify_func( string path, int action, int kind,	  @
 *!                         string mime_type, int content_state,  @
 *!                         int prop_state, int revision )
 *!
 *!  This function is called when the state of an entry in the wc
 *!  changes.  Override it if you are interrested in this information.
 *!
 *! @param path
 *!   Path to the affected file or directory.
 *! @param action
 *!   The action taken on the entry.
 *! @param kind
 *!   The kind of entry (file or directory), see @[RA.Library.Session.check_path()].
 *! @param mime_type
 *!   The media type for the entry, if available (otherwise 0).
 *! @param content_state
 *!   The new state of the entry contents.
 *! @param prop_state
 *!   The new state of the entry properties.
 *! @param revision
 *!   The new revision of the entry.
 */
static void f_notify_func (INT32 args)
{
  pop_n_elems (args);
}


/*! @decl string log_msg_func( array(array(string|int)) commit_items )
 *!
 *!  Override this function to provide log messages for commits.
 *!
 *! @param commit_items
 *!   A list of items being committed.  Each element has the following
 *!   format:
 *! @array
 *!   @elem string path
 *!     Absolute working-copy path of item.
 *!   @elem int kind
 *!     Node kind (dir, file).
 *!   @elem string URL
 *!     Commit URL for this item.
 *!   @elem int revision
 *!     Revision (copyfrom-rev if _IS_COPY).
 *!   @elem string copyfrom_url
 *!     copyfrom-url.
 *!   @elem int state_flags
 *!     State flags.
 *! @endarray
 *!
 *! @returns
 *!   The function should return a log message for the commit as a string.
 */
static void f_log_msg_func (INT32 args)
{
  pop_n_elems (args);
  push_int (0);
}


/*! @decl void cancel_func( )
 *!
 *!  Override this function to enable operations in progress to be aborted.
 *!  If the operation should continue, the function should do nothing.
 *!  If not, it should throw an error with apr_err of SVN_ERR_CANCELLED.
 *!
 */
static void f_cancel_func (INT32 args)
{
  pop_n_elems (args);
  push_int (0);
}


/* C stub for calling notify_func */

static void notify_func_stub (void *baton, const char *path,
			      svn_wc_notify_action_t action,
			      svn_node_kind_t kind,
			      const char *mime_type,
			      svn_wc_notify_state_t content_state,
			      svn_wc_notify_state_t prop_state,
			      svn_revnum_t revision)
{
  JMP_BUF recovery;

  do {
    struct thread_state *_tmp=thread_state_for_id (th_self ());
    HIDE_GLOBAL_VARIABLES ();
    THREADS_DISALLOW ();

    if (SETJMP (recovery))
      call_handle_error ();
    else {
      svn_pike_push_utf8 (path);
      push_int (action);
      push_int (kind);
      svn_pike_push_utf8 (mime_type);
      push_int (content_state);
      push_int (prop_state);
      push_int (revision);

      apply_svalue ((struct svalue *)baton, 7);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);
}


/* C stub for calling log_msg_func */

static svn_error_t *log_msg_func_stub (const char **log_msg,
				       const char **tmp_file,
				       apr_array_header_t *commit_items,
				       void *baton, apr_pool_t *pool)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;

  if(tmp_file) *tmp_file = NULL;
  do {
    struct thread_state *_tmp=thread_state_for_id (th_self ());
    HIDE_GLOBAL_VARIABLES ();
    THREADS_DISALLOW ();

    if (SETJMP (recovery)) {
      err = svn_pike_make_svn_error (&throw_value);
    } else {
      if (commit_items && commit_items->elts) {
	int i;
	struct svn_client_commit_item_t **cis =
	  (struct svn_client_commit_item_t **) commit_items->elts;
	push_array (allocate_array (commit_items->nelts));
	for (i=0; i<commit_items->nelts; i++) {
	  svn_pike_push_utf8 (cis[i]->path);
	  push_int (cis[i]->kind);
	  svn_pike_push_utf8 (cis[i]->url);
	  push_int (cis[i]->revision);
	  svn_pike_push_utf8 (cis[i]->copyfrom_url);
	  push_int (cis[i]->state_flags);
	  f_aggregate (6);
	  assign_svalue (&ITEM(Pike_sp[-2].u.array)[i], Pike_sp-1);
	  pop_stack ();
	}
      } else
	push_array (allocate_array (0));
      apply_svalue ((struct svalue *)baton, 1);
      if (Pike_sp[-1].type != PIKE_T_STRING)
	Pike_error ("log_msg_func must return a string\n");
      f_string_to_utf8 (1);
      *log_msg = apr_pstrdup (pool, APR_STR0 (Pike_sp[-1].u.string));
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}


/* C stub for calling status_func */

static void status_func_stub (void *baton,
			      const char *path,
			      svn_wc_status_t *status)
{
  JMP_BUF recovery;

  do {
    struct thread_state *_tmp=thread_state_for_id (th_self ());
    HIDE_GLOBAL_VARIABLES ();
    THREADS_DISALLOW ();

    if (SETJMP (recovery)) {
      call_handle_error();
    } else {
      svn_pike_push_utf8 (path);
      if(status) {
	push_constant_text("text_status");
	push_int(status->text_status);
	push_constant_text("prop_status");
	push_int(status->prop_status);
	push_constant_text("locked");
	push_int(status->locked);
	push_constant_text("copied");
	push_int(status->copied);
	push_constant_text("switched");
	push_int(status->switched);
	push_constant_text("repos_text_status");
	push_int(status->repos_text_status);
	push_constant_text("repos_prop_status");
	push_int(status->repos_prop_status);
	if(status->entry) {
	  svn_wc_entry_t *entry = status->entry;
	  push_constant_text("name");
	  svn_pike_push_utf8(entry->name);
	  push_constant_text("revision");
	  push_int(entry->revision);
	  push_constant_text("url");
	  svn_pike_push_utf8(entry->url);
	  push_constant_text("repos");
	  svn_pike_push_utf8(entry->repos);
	  push_constant_text("kind");
	  push_int(entry->kind);	
	  push_constant_text("schedule");
	  push_int(entry->schedule);
	  push_constant_text("copied");
	  push_int(entry->copied);
	  push_constant_text("deleted");
	  push_int(entry->deleted);
	  push_constant_text("copyfrom_url");
	  svn_pike_push_utf8(entry->copyfrom_url);
	  push_constant_text("copyfrom_rev");
	  push_int(entry->copyfrom_rev);
	  push_constant_text("conflict_old");
	  svn_pike_push_utf8(entry->conflict_old);
	  push_constant_text("conflict_new");
	  svn_pike_push_utf8(entry->conflict_new);
	  push_constant_text("conflict_wrk");
	  svn_pike_push_utf8(entry->conflict_wrk);
	  push_constant_text("prejfile");
	  svn_pike_push_utf8(entry->prejfile);
	  push_constant_text("text_time");
	  svn_pike_push_time(entry->text_time);
	  push_constant_text("prop_time");
	  svn_pike_push_time(entry->prop_time);
	  push_constant_text("checksum");
	  if(entry->checksum)
	    push_text(entry->checksum);
	  else
	    push_int(0);
	  push_constant_text("cmt_rev");
	  push_int(entry->cmt_rev);
	  push_constant_text("cmt_date");
	  svn_pike_push_time(entry->cmt_date);
	  push_constant_text("cmt_author");
	  svn_pike_push_utf8(entry->cmt_author);
	  f_aggregate_mapping(54);
	} else
	  f_aggregate_mapping(14);
      } else
	push_int(0);
      apply_svalue ((struct svalue *)baton, 2);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);
}


/* C stub for calling blame_receiver_func */

static svn_error_t *blame_receiver_func_stub (void *baton,
					      apr_int64_t line_no,
					      svn_revnum_t revision,
					      const char *author,
					      const char *date,
					      const char *line,
					      apr_pool_t *pool)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;

  do {
    struct thread_state *_tmp=thread_state_for_id (th_self ());
    HIDE_GLOBAL_VARIABLES ();
    THREADS_DISALLOW ();

    if (SETJMP (recovery)) {
      err = svn_pike_make_svn_error (&throw_value);
    } else {
      push_int64 (line_no);
      push_int (revision);
      svn_pike_push_utf8 (author);
      svn_pike_push_utf8 (date);
      svn_pike_push_utf8 (line);
      apply_svalue ((struct svalue *)baton, 5);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}


/* C stub for calling cancel_func */

static svn_error_t *cancel_func_stub (void *baton)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;

  do {
    struct thread_state *_tmp=thread_state_for_id (th_self ());
    HIDE_GLOBAL_VARIABLES ();
    THREADS_DISALLOW ();

    if (SETJMP (recovery)) {
      err = svn_pike_make_svn_error (&throw_value);
    } else {
      apply_svalue ((struct svalue *)baton, 0);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}


/* Convert a commit_info struct to a Pike svalue */

static void make_commit_info (struct svalue *ret,
			      struct svn_client_commit_info_t *commit_info)
{
  if (commit_info) {
    push_int (commit_info->revision);
    svn_pike_push_utf8 (commit_info->date);
    svn_pike_push_utf8 (commit_info->author);
    f_aggregate (3);
    *ret = *--Pike_sp;
  } else {
    ret->u.integer = 0;
    ret->type = PIKE_T_INT;
    ret->subtype = NUMBER_NUMBER;
  }
}


/* Convert an APR array of property hashes to a Pike mapping */
static void make_proplist (struct svalue *ret, apr_array_header_t *props)
{
  if(props) {
    svn_client_proplist_item_t **pis =
      (svn_client_proplist_item_t **) props->elts;
    int i;
    for (i=0; i<props->nelts; i++) {
      svn_stringbuf_t *name = pis[i]->node_name;
      push_string (make_shared_binary_string (name->data, name->len));
      f_utf8_to_string (1);
      svn_pike_make_propmap (Pike_sp, pis[i]->prop_hash, -1);
      Pike_sp ++;
    }
    f_aggregate_mapping(2*props->nelts);
    *ret = *--Pike_sp;  
  } else {
    ret->u.integer = 0;
    ret->type = PIKE_T_INT;
    ret->subtype = NUMBER_UNDEFINED;
  }
}


/*! @decl int checkout( string URL, string path, Revision|void revision,  @
 *!                     int|void non_recursive )
 *!
 *!  Checkout a working copy of a Subversion repository.
 *!
 *! @param URL
 *!   Repository URL to checkout.
 *! @param path
 *!   Root directory of the newly checked out working copy.
 *! @param revision
 *!   Revision number to checkout, or UNDEFINED for newset revision.
 *! @param non_recursive
 *!   If non-zero, don't checkout subdirectories.
 *!
 *! @returns
 *!   The actual revision number that was checked out.
 */
static void f_checkout (INT32 args)
{
  struct pike_string *url, *path;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_revnum_t ret_rev;
  svn_opt_revision_t revision;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("checkout", args, "%W%W", &url, &path);
  svn_pike_check_rev_arg ("checkout", args, 2, &revision);
  recurse = svn_pike_check_recurse_arg ("checkout", args, 3);

  url = svn_pike_to_utf8 (url);
  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_checkout (&ret_rev,
			     svn_path_canonicalize (APR_STR0 (url), pool),
			     svn_path_canonicalize (APR_STR0 (path), pool),
			     &revision, recurse, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path);
  do_free_string (url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (ret_rev);
  }
}


/*! @decl int update( string path, Revision|void revision,	@
 *!                   int|void nonrecursive )
 *!
 *!  Update a working copy to a new revision.
 *!
 *! @param path
 *!   The path of the working tree.
 *! @param revision
 *!   Revision to update to, or UNDEFINED for the latest revision.
 *! @param non_recursive
 *!   If non-zero, don't update subdirectories.
 *!
 *! @returns
 *!   The actual revision number that the working copy was updated to.
 */
static void f_update (INT32 args)
{
  struct pike_string *path;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_revnum_t ret_rev;
  svn_opt_revision_t revision;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("update", args, "%W", &path);
  svn_pike_check_rev_arg ("update", args, 1, &revision);
  recurse = svn_pike_check_recurse_arg ("update", args, 2);

  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_update (&ret_rev, svn_path_canonicalize (APR_STR0 (path), pool),
			   &revision, recurse,
			   ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (ret_rev);
  }
}


/*! @decl int switch( string path, string URL, Revision|void revision,	@
 *!                   int|void nonrecursive )
 *!
 *!  Switch a working tree to a different branch/tag.
 *!
 *! @param path
 *!   The path of the working tree.
 *! @param URL
 *!   New repository URL for this working tree.
 *! @param revision
 *!   Revision to switch to, or UNDEFINED for the latest revision.
 *! @param non_recursive
 *!   If non-zero, don't switch subdirectories.
 *!
 *! @returns
 *!   The actual revision number that the working tree was switched to.
 */
static void f_switch (INT32 args)
{
  struct pike_string *url, *path;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_revnum_t ret_rev;
  svn_opt_revision_t revision;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("switch", args, "%W%W", &path, &url);
  svn_pike_check_rev_arg ("switch", args, 2, &revision);
  recurse = svn_pike_check_recurse_arg ("switch", args, 3);

  path = svn_pike_to_utf8 (path);
  url = svn_pike_to_utf8 (url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_switch (&ret_rev, svn_path_canonicalize (APR_STR0 (path), pool),
			   svn_path_canonicalize (APR_STR0 (url), pool), &revision,
			   recurse, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (url);
  do_free_string (path);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (ret_rev);
  }
}


/*! @decl void add( string path, int|void non_recursive )
 *!
 *!  Schedule a working copy file or directory for addition to
 *!  the repository.  @[path]'s parent must be under revision control
 *!  already.
 *!
 *! @param path
 *!   The path of the file or directory to add.
 *! @param non_recursive
 *!   If non-zero, don't schedule the contents of @[path] for
 *!   addition if it's a directory.
 */
static void f_add_ (INT32 args)
{
  struct pike_string *path;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("add", args, "%W", &path);
  recurse = svn_pike_check_recurse_arg ("add", args, 1);

  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_add (svn_path_canonicalize (APR_STR0 (path), pool), recurse,
			ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl array(int|string) mkdir( array(string) paths_or_urls )
 *!
 *!  If @[paths_or_urls] contains URLs, immediately attempt to commit the
 *!  creation of those directory URL in the repository.  Else, create
 *!  the directories on disk, and attempt to schedule it for addition.
 *!
 *! @param paths_or_urls
 *!   Paths of directories to schedule for addition, or URLs of directories
 *!   to create in the repository.
 *!
 *! @returns
 *!   If @[paths_or_urls] contains at least one URL, an array containing
 *!   information about the commit is returned.  The elements are as follows:
 *! @array
 *!   @elem int revision
 *!     The revision in which the directory(/ies) was created.
 *!   @elem string date
 *!     The server-side date of the commit.
 *!   @elem string author
 *!     The author of the commit.
 *! @endarray
 *!
 *! @seealso
 *!   @[add()]
 */
static void f_mkdir (INT32 args)
{
  struct array *targets;
  apr_array_header_t *targets_aa;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svn_client_commit_info_t *commit_info = NULL;
  struct svalue ret;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("mkdir", args, "%a", &targets);

  pool = svn_pool_create (NULL);

  targets_aa = svn_pike_make_targets_array (targets, pool, FALSE);

  THREADS_ALLOW ();

  err = svn_client_mkdir (&commit_info, targets_aa, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    make_commit_info(&ret, commit_info);

  apr_pool_destroy (pool);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl array(int|string) delete( array(string) paths_or_urls, int|void force )
 *!
 *!  If @[paths_or_urls] contain URLs, immediately attempt to commit the
 *!  deletion of those URLs from the repository.  Else, schedule the
 *!  specified working copy paths for removal from the repository.
 *!
 *! @param paths_or_urls
 *!   Paths to schedule for deletion, or URLs to immediately
 *!   remove in the repository.
 *! @param force
 *!   Unless set, the operation will fail if a working copy path
 *!   that contains locally modified and/or unversioned items is
 *!   specified.
 *!
 *! @returns
 *!   If @[paths_or_urls] contains at least one URL, an array containing
 *!   information about the commit is returned.  The elements are as follows:
 *! @array
 *!   @elem int revision
 *!     The revision in which the item(s) was removed.
 *!   @elem string date
 *!     The server-side date of the commit.
 *!   @elem string author
 *!     The author of the commit.
 *! @endarray
 */
static void f_delete (INT32 args)
{
  struct array *targets;
  apr_array_header_t *targets_aa;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svn_client_commit_info_t *commit_info = NULL;
  struct svalue ret;
  svn_boolean_t force;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("delete", args, "%a", &targets);
  force = svn_pike_check_force_arg ("delete", args, 1);

  pool = svn_pool_create (NULL);

  targets_aa = svn_pike_make_targets_array (targets, pool, FALSE);

  THREADS_ALLOW ();

  err = svn_client_delete (&commit_info, targets_aa, force, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    make_commit_info (&ret, commit_info);

  apr_pool_destroy (pool);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl array(int|string) import( string path, string URL,	    @
 *!                                 int|void non_recursive )
 *!
 *!  Import a file or directory into the repository directory @[URL] at
 *!  head.
 *!
 *! @param path
 *!   The path of the file or directory to import.
 *! @param URL
 *!   The URL of the repository directory in which to place the
 *!   imported item(s).
 *! @param non_recursive
 *!   If non-zero, don't import subdirectories.
 *!
 *! @returns
 *!   An array containing information about the commit is returned.
 *!   The elements are as follows:
 *! @array
 *!   @elem int revision
 *!     The revision in which the item was imported.
 *!   @elem string date
 *!     The server-side date of the commit.
 *!   @elem string author
 *!     The author of the commit.
 *! @endarray
 */
static void f_import (INT32 args)
{
  struct pike_string *path, *url;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svn_client_commit_info_t *commit_info = NULL;
  struct svalue ret;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("import", args, "%W%W", &path, &url);
  recurse = svn_pike_check_recurse_arg ("import", args, 2);

  path = svn_pike_to_utf8 (path);
  url = svn_pike_to_utf8 (url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_import (&commit_info,
			   svn_path_canonicalize (APR_STR0 (path), pool),
			   svn_path_canonicalize (APR_STR0 (url), pool),
			   !recurse, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    make_commit_info (&ret, commit_info);

  apr_pool_destroy (pool);

  do_free_string (url);
  do_free_string (path);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl array(int|string) commit( array(string) targets,		    @
 *!                                 int|void non_recursive )
 *!
 *!  Commit a set of files and directorues to the repository at head.
 *!
 *! @param targets
 *!   The paths of the files and directories to commit.
 *! @param non_recursive
 *!   If non-zero, don't commit subdirectories.
 *!
 *! @returns
 *!   An array containing information about the commit is returned.
 *!   The elements are as follows:
 *! @array
 *!   @elem int revision
 *!     The revision in which the targets were committed.
 *!   @elem string date
 *!     The server-side date of the commit.
 *!   @elem string author
 *!     The author of the commit.
 *! @endarray
 */
static void f_commit (INT32 args)
{
  struct array *targets;
  apr_array_header_t *targets_aa;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svn_client_commit_info_t *commit_info = NULL;
  struct svalue ret;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("commit", args, "%a", &targets);
  recurse = svn_pike_check_recurse_arg ("commit", args, 1);

  pool = svn_pool_create (NULL);

  targets_aa = svn_pike_make_targets_array (targets, pool, FALSE);

  THREADS_ALLOW ();

  err = svn_client_commit (&commit_info, targets_aa,
			   !recurse, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    make_commit_info (&ret, commit_info);

  apr_pool_destroy (pool);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl int status( string path,				@
 *!		      function(string, mapping(string:mixed):void) status_func, @
 *!                   int|void non_recursive,			@
 *!		      int|void all, int|void update,		@
 *!		      Revision|void revision,			@
 *!		      int|void no_ignore )
 *!
 *!  Request status information for a file or directory.  The status
 *!  information will be passed to the function @[status_func].
 *!
 *! @param path
 *!   The file or directory to get status for.
 *! @param status_func
 *!   A function to invoke once for each file that status is queried on.
 *!   The parameters to this function are as follows:
 *!   @array
 *!     @elem string path
 *!       The path of the entry that this status information concerns
 *!     @elem mapping(string:mixed) status
 *!       A mapping containing status information about the entry.
 *!       The values in the mapping are as follows:
 *!       @mapping
 *!         @member int text_status
 *!           Status of the textual component.
 *!         @member int prop_status
 *!           Status of the property component.
 *!         @member int locked
 *!           Entry is locked.
 *!         @member int copied
 *!           Entry is copied.
 *!         @member int switched
 *!           Entry is switched.
 *!         @member int repos_text_status
 *!           Status of the textual component in the repository.
 *!         @member int repos_prop_status
 *!           Status of the property component in the repository.
 *!         @member string name
 *!           The entry's name.
 *!         @member int revision
 *!           Base revision.
 *!         @member string url
 *!           URL in repository.
 *!         @member string repos
 *!           Canonical repository URL.
 *!         @member int kind
 *!           Node kind (file, dir, ...).
 *!         @member int schedule
 *!           Scheduling (add, delete, replace ...).
 *!         @member int copied
 *!           In a copied state.
 *!         @member int deleted
 *!           In a deleted state.
 *!         @member string copyfrom_url
 *!           Copyfrom location.
 *!         @member int copyfrom_rev
 *!           Copyfrom revision.
 *!         @member string conflict_old
 *!           Old version of conflicted file.
 *!         @member string conflict_new
 *!           New version of conflicted file.
 *!         @member string conflict_wrk
 *!           Wroking version of conflicted file.
 *!         @member string prejfile
 *!           Property reject file
 *!         @member int text_time
 *!           Last up-to-date time for text contents.
 *!         @member int prop_time
 *!           Last up-to-date time for properties.
 *!         @member string checksum
 *!           Base64-encoded checksum for the untranslated text base file.
 *!         @member int cmt_rev
 *!           Last revision this was changed.
 *!         @member int cmt_date
 *!           Last date this was changed.
 *!         @member string cmt_author
 *!           Last commit author of this item.
 *!       @endmapping
 *!   @endarray
 *! @param non_recursive
 *!   If non-zero, don't query subdirectories.
 *! @param all
 *!   If set, all entries are retrieved; otherwise only "interesting"
 *!   entries (local mods and/or out-of-date) will be fetched.
 *! @param update
 *!   If set, the repository will be contacted, so that the status 
 *!   structure is augmented with information about out-of-dateness
 *!   (with respect to @[revision]).
 *! @param revision
 *!   When @[update] is specified, this argument can be used to
 *!   select the revision against which out-of-dateness is checked.
 *!   If @[update] is zero, this argument is not used.
 *! @param no_ignore
 *!   If set, the svn:ignore property is ignored.
 *!
 *! @returns
 *!   The revision number against which out-of-datedness was checked,
 *!   if @[update] is set.  If @[update] is not set, zero is returned.
 */
static void f_status (INT32 args)
{
  struct pike_string *path;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_revnum_t ret_rev = SVN_INVALID_REVNUM;
  svn_opt_revision_t revision;
  svn_boolean_t recurse, get_all, update, no_ignore;
  struct svalue *status_func;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("status", args, "%W%*", &path, &status_func);
  recurse = svn_pike_check_recurse_arg ("status", args, 2);
  get_all = svn_pike_check_force_arg ("status", args, 3);
  update = svn_pike_check_force_arg ("status", args, 4);
  no_ignore = svn_pike_check_force_arg ("status", args, 6);
  svn_pike_check_rev_arg ("status", args, 5, &revision);

  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_status (&ret_rev, svn_path_canonicalize (APR_STR0 (path), pool),
			   &revision, status_func_stub, status_func,
			   recurse, get_all, update, no_ignore,
			   ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (update? ret_rev : 0);
  }
}


/*! @decl void log( array(string) targets,				    @
 *!		    function(int, string, string, string,		    @
 *!		    mapping(string:array(int|string))|void:void) log_func,  @
 *!		    Revision start_revision, Revision end_revision,	    @
 *!		    int|void discover_changed_paths,			    @
 *!                 int|void strict_node_history )
 *!
 *!  Call @[log_func] on each log message from start_revision
 *!  to end_revision in turn, inclusive.
 *!
 *! @param targets
 *!   The paths of the files and directories for which log messages
 *!   are desired.
 *! @param log_func
 *!   This function is invoked once on each log message.  The
 *!   parameters are as follows:
 *!     @array
 *!       @elem int revision
 *!         The revision this log message concerns.
 *!       @elem string author
 *!         The author that committed this revision.
 *!       @elem string date
 *!         The date when this revision was committed.
 *!       @elem string message
 *!         The log message for this revision.
 *!       @elem mapping(string:array(int|string)) changed_paths
 *!         A mapping from entry name to change information for all
 *!         entries that were changed in this revision (if such information
 *!         was requested).  The values in the mapping are as follows:
 *!       @array
 *!         @elem int action
 *!           'A'dd, 'D'elete, 'R'eplace, 'M'odify
 *!         @elem string copyfrom_path
 *!           Source path of copy (if any).
 *!         @elem int copyfrom_rev
 *!           Source revision of copy (if any).
 *!       @endarray
 *!     @endarray
 *! @param start_revision
 *!   The oldest revision for which log messages are desired.
 *! @param end_revision
 *!   The newest revision for which log messages are desired.
 *! @param discover_changed_paths
 *!   If set, then the changed_paths argument to log_func
 *!   will be passed on each invocation.
 *! @param strict_node_history
 *!   If set, copy history will not be traversed while harvest revision
 *!   logs for each target.
 */
static void f_log (INT32 args)
{
  struct array *targets;
  apr_array_header_t *targets_aa;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_boolean_t discover_changed_paths, strict_node_history;
  svn_opt_revision_t start, end;
  struct svalue *log_receiver_func;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("log", args, "%a%*", &targets, &log_receiver_func);
  svn_pike_check_rev_arg ("log", args, 1, &start);
  svn_pike_check_rev_arg ("log", args, 2, &end);
  discover_changed_paths = svn_pike_check_force_arg ("log", args, 3);
  strict_node_history = svn_pike_check_force_arg ("log", args, 4);

  pool = svn_pool_create (NULL);

  targets_aa = svn_pike_make_targets_array (targets, pool, FALSE);

  THREADS_ALLOW ();

  err = svn_client_log (targets_aa, &start, &end,
			discover_changed_paths, strict_node_history,
			svn_pike_log_receiver_func_stub, log_receiver_func,
			ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void blame( string path_or_url,				      @
 *!		      function(int,int,string,string,string:void) receiver,   @
 *!		      Revision start_revision, Revision end_revision )
 *!
 *!  Call @[receiver] on each line-blame item associated with revision
 *!  @[end_revision] of @[path_or_url], using @[start_revision] as the
 *!  default source of all blame.
 *!
 *! @param path_or_url
 *!   Path or URL of the node to examine for blame.
 *! @param receiver
 *!   A function to call for each line-blame item.  The parameters
 *!   are as follows:
 *!    @array
 *!      @elem int line_no
 *!        Line number
 *!      @elem int revision
 *!        Revision number
 *!      @elem string author
 *!        The author of this line
 *!      @elem string date
 *!        Commit datestamp of this line
 *!      @elem string line
 *!        The text contents of the line
 *!    @endarray
 *! @param start_revision
 *!   The default source of all blame.
 *! @param end_revision
 *!   The revision of @[path_or_url] for which to determine blame.
 */
static void f_blame (INT32 args)
{
  struct pike_string *path_or_url;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_opt_revision_t start, end;
  struct svalue *blame_receiver_func;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("blame", args, "%W%*", &path_or_url, &blame_receiver_func);
  svn_pike_check_rev_arg ("log", args, 2, &start);
  svn_pike_check_rev_arg ("log", args, 3, &end);

  path_or_url = svn_pike_to_utf8 (path_or_url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_blame (svn_path_canonicalize (APR_STR0 (path_or_url), pool),
			  &start, &end, blame_receiver_func_stub,
			  blame_receiver_func, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path_or_url);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void diff( string path1, Revision revision1,			@
 *!		     string path2, Revision revision2,			@
 *!		     array(string)|void diff_options,			@
 *!		     Stdio.File|void outfile, Stdio.File|void errfile,	@
 *!		     int|void non_recursive, int|void diff_deleted,	@
 *!		     int|void ignore_ancestry );
 *!
 *!  Produce diff output which describes the delta between
 *!  @[path1]/@[revision1] and @[path2]/@[revision2].
 *!
 *! @param path1
 *!   The path of the first file to compare.
 *! @param revision1
 *!   The revision of the first file to compare.
 *! @param path2
 *!   The path of the second file to compare.
 *! @param revision2
 *!   The revision of the second file to compare.
 *! @param diff_options
 *!   Additional command line options to the diff processes invoked
 *!   to compare files.
 *! @param outfile
 *!   File to print diff output to.  If not specified, @[Stdio.stdout]
 *!   will be used.
 *! @param errfile
 *!   File to print diff errors to.  If not specified, @[Stdio.stderr]
 *!   will be used.
 *! @param non_recursive
 *!   If non-zero, don't compare subdirectories.
 *! @param diff_deleted
 *!   If non-zero, generate diff output on deleted files.
 *! @param ignore_ancestry
 *!   Controls whether or not items being diffed will be checked for
 *!   relatedness first.  Unrelated items are typically transmitted to
 *!   the editor as a deletion of one thing and the addition of another,
 *!   but if this flag is non-zero, unrelated items will be diffed as
 *!  if they were related.
 */
static void f_diff_ (INT32 args)
{
  struct array *options = NULL;
  apr_array_header_t *options_aa;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svalue dummy;
  svn_boolean_t recurse, diff_deleted, ignore_ancestry;
  struct pike_string *path1, *path2;
  svn_opt_revision_t rev1, rev2;
  apr_file_t *outfile, *errfile;
  apr_os_file_t outosfile, errosfile;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("diff", args, (args>4? "%W%*%W%*%A":"%W%*%W%*"),
		&path1, &dummy, &path2, &dummy, &options);
  svn_pike_check_rev_arg ("diff", args, 1, &rev1);
  svn_pike_check_rev_arg ("diff", args, 3, &rev2);
  outosfile = svn_pike_check_file_arg ("diff", args, 5);
  errosfile = svn_pike_check_file_arg ("diff", args, 6);
  recurse = svn_pike_check_recurse_arg ("diff", args, 7);
  diff_deleted = svn_pike_check_force_arg ("diff", args, 8);
  ignore_ancestry = svn_pike_check_force_arg ("diff", args, 9);

  path1 = svn_pike_to_utf8 (path1);
  path2 = svn_pike_to_utf8 (path2);

  pool = svn_pool_create (NULL);

  if((outosfile == NO_FILE_SPECIFIED?
      apr_file_open_stdout (&outfile, pool) :
      apr_os_file_put (&outfile, &outosfile, 0, pool)) ||
     (errosfile == NO_FILE_SPECIFIED?
      apr_file_open_stderr (&errfile, pool) :
      apr_os_file_put (&errfile, &errosfile, 0, pool))) {
    apr_pool_destroy (pool);
    do_free_string (path1);
    do_free_string (path2);
    Pike_error ("Failed to open stdout/stderr\n");
  }

  options_aa = svn_pike_make_targets_array (options, pool, TRUE);

  THREADS_ALLOW ();

  err = svn_client_diff (options_aa, svn_path_canonicalize (APR_STR0(path1), pool),
			 &rev1, svn_path_canonicalize (APR_STR0(path2), pool),
			 &rev2, recurse, ignore_ancestry, !diff_deleted,
			 outfile, errfile, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path1);
  do_free_string (path2);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void diff_peg( string path, Revision peg_revision,		    @
 *!			 Revision start_revision, Revision end_revision,    @
 *!			 array(string)|void diff_options,		    @
 *!			 Stdio.File|void outfile, Stdio.File|void errfile,  @
 *!			 int|void non_recursive, int|void diff_deleted,	    @
 *!			 int|void ignore_ancestry );
 *!
 *!  Produce diff output which describes the delta between the
 *!  filesystem object @[path] in peg revision @[peg_revision], as it
 *!  changed between @[start_revision] and @[end_revision].
 *!
 *! @param path
 *!   The path of the file system object.
 *! @param peg_revision
 *!   The peg revision of @[path].
 *! @param start_revision
 *!   The first revision of the delta.
 *! @param end_revision
 *!   The second revision of the delta.
 *! @param diff_options
 *!   Additional command line options to the diff processes invoked
 *!   to compare files.
 *! @param outfile
 *!   File to print diff output to.  If not specified, @[Stdio.stdout]
 *!   will be used.
 *! @param errfile
 *!   File to print diff errors to.  If not specified, @[Stdio.stderr]
 *!   will be used.
 *! @param non_recursive
 *!   If non-zero, don't compare subdirectories.
 *! @param diff_deleted
 *!   If non-zero, generate diff output on deleted files.
 *! @param ignore_ancestry
 *!   Controls whether or not items being diffed will be checked for
 *!   relatedness first.  Unrelated items are typically transmitted to
 *!   the editor as a deletion of one thing and the addition of another,
 *!   but if this flag is non-zero, unrelated items will be diffed as
 *!  if they were related.
 */
static void f_diff_peg (INT32 args)
{
  struct array *options = NULL;
  apr_array_header_t *options_aa;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svalue dummy;
  svn_boolean_t recurse, diff_deleted, ignore_ancestry;
  struct pike_string *path;
  svn_opt_revision_t peg_rev, start_rev, end_rev;
  apr_file_t *outfile, *errfile;
  apr_os_file_t outosfile, errosfile;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("diff_peg", args, (args>4? "%W%*%*%*%A":"%W%*%*%*"),
		&path, &dummy, &dummy, &dummy, &options);
  svn_pike_check_rev_arg ("diff_peg", args, 1, &peg_rev);
  svn_pike_check_rev_arg ("diff_peg", args, 2, &start_rev);
  svn_pike_check_rev_arg ("diff_peg", args, 3, &end_rev);
  outosfile = svn_pike_check_file_arg ("diff_peg", args, 5);
  errosfile = svn_pike_check_file_arg ("diff_peg", args, 6);
  recurse = svn_pike_check_recurse_arg ("diff_peg", args, 7);
  diff_deleted = svn_pike_check_force_arg ("diff_peg", args, 8);
  ignore_ancestry = svn_pike_check_force_arg ("diff_peg", args, 9);

  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  if((outosfile == NO_FILE_SPECIFIED?
      apr_file_open_stdout (&outfile, pool) :
      apr_os_file_put (&outfile, &outosfile, 0, pool)) ||
     (errosfile == NO_FILE_SPECIFIED?
      apr_file_open_stderr (&errfile, pool) :
      apr_os_file_put (&errfile, &errosfile, 0, pool))) {
    apr_pool_destroy (pool);
    do_free_string (path);
    Pike_error ("Failed to open stdout/stderr\n");
  }

  options_aa = svn_pike_make_targets_array (options, pool, TRUE);

  THREADS_ALLOW ();

  err = svn_client_diff_peg (options_aa,
			     svn_path_canonicalize (APR_STR0(path), pool),
			     &peg_rev, &start_rev, &end_rev, recurse,
			     ignore_ancestry, !diff_deleted, outfile, errfile,
			     ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void merge( string source1, Revision revision1,		@
 *!		      string source2, Revision revision2,		@
 *!		      string target_wcpath, int|void non_recursive,	@
 *!		      int|void force, int|void dry_run,			@
 *!		      int|void ignore_ancestry )
 *!
 *!  Merge changes from @[source1]/@[revision1] to @[source2]/@[revision2]
 *!  into the working-copy path @[target_wcpath].
 *!
 *! @param source1
 *!   The repository URL or working copy path of the first file to compare.
 *! @param revision1
 *!   The revision of the first file to compare.
 *! @param source2
 *!   The repository URL or working copy path of the second file to compare.
 *! @param revision2
 *!   The revision of the second file to compare.
 *! @param target_wcpath
 *!   The working-copy path of the file into which to merge the changes
 *!   from @[source1]/@[revision1] to @[source2]/@[revision2].
 *! @param non_recursive
 *!   If non-zero, don't merge subdirectories.
 *! @param force
 *!   If non-zero, files deleted between @[source1]/@[revision1] and
 *!   @[source2]/@[revision2] will be deleted in @[target_wcpath] even
 *!   if they have local modifications.
 *! @param dry_run
 *!   If non-zero, the merge is carried out, and full notification
 *!   feedback is provided, but the working copy is not modified.
 *! @param ignore_ancestry
 *!   Controls whether or not items being diffed will be checked for
 *!   relatedness first.  Unrelated items are typically transmitted to
 *!   the editor as a deletion of one thing and the addition of another,
 *!   but if this flag is non-zero, unrelated items will be diffed as if
 *!   they were related.
 */
static void f_merge (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct svalue dummy;
  svn_boolean_t recurse, force, dry_run, ignore_ancestry;
  struct pike_string *url1, *url2, *path;
  svn_opt_revision_t rev1, rev2;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("merge", args, "%W%*%W%*%W",
		&url1, &dummy, &url2, &dummy, &path);
  svn_pike_check_rev_arg ("merge", args, 1, &rev1);
  svn_pike_check_rev_arg ("merge", args, 3, &rev2);
  recurse = svn_pike_check_recurse_arg ("merge", args, 5);
  force = svn_pike_check_force_arg ("merge", args, 6);
  dry_run = svn_pike_check_force_arg ("merge", args, 7);
  ignore_ancestry = svn_pike_check_force_arg ("merge", args, 8);

  url1 = svn_pike_to_utf8 (url1);
  url2 = svn_pike_to_utf8 (url2);
  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_merge (svn_path_canonicalize (APR_STR0(url1), pool), &rev1,
			  svn_path_canonicalize (APR_STR0(url2), pool), &rev2,
			  svn_path_canonicalize (APR_STR0(path), pool), recurse,
			  ignore_ancestry, force, dry_run, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (url1);
  do_free_string (url2);
  do_free_string (path);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void merge_peg( string source, Revision revision1,		@
 *!			  Revision revision2, Revision peg_revision,	@
 *!			  string target_wcpath, int|void non_recursive,	@
 *!			  int|void force, int|void dry_run,		@
 *!			  int|void ignore_ancestry )
 *!
 *!  Merge the changes between the filesystem object @[source] in peg
 *!  revision @[peg_revision], as it changed between @[revision1] and
 *!  @[revision2].
 *!
 *! @param source
 *!   The repository URL or working copy path of the filesystem object.
 *! @param revision1
 *!   The start revision of the change to merge.
 *! @param revision2
 *!   The end revision of the change to merge.
 *! @param peg_revision
 *!   The peg revision of @[source].
 *! @param target_wcpath
 *!   The working-copy path of the file into which to merge the changes
 *!   from @[revision1] to @[revision2].
 *! @param non_recursive
 *!   If non-zero, don't merge subdirectories.
 *! @param force
 *!   If non-zero, files deleted between @[revision1] and
 *!   @[revision2] will be deleted in @[target_wcpath] even if they have
 *!   local modifications.
 *! @param dry_run
 *!   If non-zero, the merge is carried out, and full notification
 *!   feedback is provided, but the working copy is not modified.
 *! @param ignore_ancestry
 *!   Controls whether or not items being diffed will be checked for
 *!   relatedness first.  Unrelated items are typically transmitted to
 *!   the editor as a deletion of one thing and the addition of another,
 *!   but if this flag is non-zero, unrelated items will be diffed as if
 *!   they were related.
 */
static void f_merge_peg (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct svalue dummy;
  svn_boolean_t recurse, force, dry_run, ignore_ancestry;
  struct pike_string *source, *path;
  svn_opt_revision_t rev1, rev2, peg_rev;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("merge_peg", args, "%W%*%*%*%W",
		&source, &dummy, &dummy, &dummy, &path);
  svn_pike_check_rev_arg ("merge_peg", args, 1, &rev1);
  svn_pike_check_rev_arg ("merge_peg", args, 2, &rev2);
  svn_pike_check_rev_arg ("merge_peg", args, 3, &peg_rev);
  recurse = svn_pike_check_recurse_arg ("merge_peg", args, 5);
  force = svn_pike_check_force_arg ("merge_peg", args, 6);
  dry_run = svn_pike_check_force_arg ("merge_peg", args, 7);
  ignore_ancestry = svn_pike_check_force_arg ("merge_peg", args, 8);

  source = svn_pike_to_utf8 (source);
  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_merge_peg (svn_path_canonicalize (APR_STR0(source), pool),
			      &rev1, &rev2, &peg_rev,
			      svn_path_canonicalize (APR_STR0(path), pool),
			      recurse, ignore_ancestry, force, dry_run,
			      ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (source);
  do_free_string (path);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void cleanup( string dir )
 *!
 *!  Recursively cleanup a working copy directory @[dir], finishing any
 *!  incomplete operations, removing lockfiles, etc.
 *!
 *! @param dir
 *!   The working-copy path of the directory to cleanup
 */
static void f_cleanup (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct pike_string *path;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("cleanup", args, "%W", &path);

  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_cleanup (svn_path_canonicalize (APR_STR0(path), pool), ctx,
			    pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void relocate( string dir, string from, string to,	@
 *!			 int|void non_recursive )
 *!
 *!  Modify a working copy directory @[dir], changing any
 *!  repository URLs that begin with @[from] to begin with @[to] instead,
 *!  recursing into subdirectories unless @[non_recursive] is non-zero.
 *!
 *! @param dir
 *!   Working copy directory
 *! @param from
 *!   Original URL
 *! @param to
 *!   New URL
 *! @param non_recursive
 *!   If non-zero, don't relocate subdirectories.
 */
static void f_relocate (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  svn_boolean_t recurse;
  struct pike_string *dir, *from, *to;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("relocate", args, "%W%W%W", &dir, &from, &to);
  recurse = svn_pike_check_recurse_arg ("relocate", args, 3);

  dir = svn_pike_to_utf8 (dir);
  from = svn_pike_to_utf8 (from);
  to = svn_pike_to_utf8 (to);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_relocate (svn_path_canonicalize (APR_STR0(dir), pool),
			     svn_path_canonicalize (APR_STR0(from), pool),
			     svn_path_canonicalize (APR_STR0(to), pool),
			     recurse, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (dir);
  do_free_string (from);
  do_free_string (to);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void revert( array(string) paths, int|void non_recursive )
 *!
 *!  Restore the pristine version of a working copy @[paths], effectively
 *!  undoing any local modifications.
 *!
 *! @param paths
 *!   An array of working-copy paths of the files or directories to revert.
 *! @param non_recursive
 *!   If non-zero, don't revert directory contents.
 */
static void f_revert (INT32 args)
{
  struct array *paths;
  apr_array_header_t *paths_aa;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("revert", args, "%a", &paths);
  recurse = svn_pike_check_recurse_arg ("revert", args, 1);

  pool = svn_pool_create (NULL);

  paths_aa = svn_pike_make_targets_array (paths, pool, FALSE);

  THREADS_ALLOW ();

  err = svn_client_revert (paths_aa, recurse, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl void resolved( string path, int|void non_recursive )
 *!
 *!  Remove the 'conflicted' state on a working copy @[path].
 *!  This will not semantically resolve conflicts;  it just allows
 *!  @[path] to be committed in the future.  The implementation details
 *!  are opaque.
 *!
 *! @param path
 *!   The working-copy path of the file which is in a 'conflicted' state.
 *! @param non_recursive
 *!   If non-zero, don't resolv conflicts in subdirectories.
 */
static void f_resolved (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  svn_boolean_t recurse;
  struct pike_string *path;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("resolved", args, "%W", &path);
  recurse = svn_pike_check_recurse_arg ("resolved", args, 1);

  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_resolved (svn_path_canonicalize (APR_STR0(path), pool),
			     recurse, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl array(int|string) copy( string src_path_or_url,		@
 *!				  Revision src_revision,    		@
 *!				  string dst_path_or_url )
 *!
 *!  Copy @[src_path_or_url] to @[dst_path_or_url].
 *!
 *! @param src_path_or_url
 *!   Path to schedule for copying, or URL to immediately
 *!   copy in the repository.
 *! @param src_revision
 *!   The revision of @[src_path_or_url] to copy.
 *! @param dst_path_or_url
 *!   Path where to put the copy, or URL to immediately commit the copy
 *!   to the repository.
 *!
 *! @returns
 *!   If either @[src_path_or_url] or @[dst_path_or_url] is an URL, an
 *!   array containing information about the commit is returned.
 *!   The elements are as follows:
 *! @array
 *!   @elem int revision
 *!     The revision in which the item was copied.
 *!   @elem string date
 *!     The server-side date of the commit.
 *!   @elem string author
 *!     The author of the commit.
 *! @endarray
 */
static void f_copy (INT32 args)
{
  struct pike_string *src_path_or_url, *dst_path_or_url;
  svn_opt_revision_t rev;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svn_client_commit_info_t *commit_info = NULL;
  struct svalue dummy, ret;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("copy", args, "%W%*%W", &src_path_or_url,
		&dummy, &dst_path_or_url);
  svn_pike_check_rev_arg ("copy", args, 1, &rev);

  src_path_or_url = svn_pike_to_utf8 (src_path_or_url);
  dst_path_or_url = svn_pike_to_utf8 (dst_path_or_url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_copy (&commit_info,
			 svn_path_canonicalize (APR_STR0 (src_path_or_url), pool),
			 &rev,
			 svn_path_canonicalize (APR_STR0 (dst_path_or_url), pool),
			 ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    make_commit_info (&ret, commit_info);

  apr_pool_destroy (pool);

  do_free_string (src_path_or_url);
  do_free_string (dst_path_or_url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl array(int|string) move( string src_path_or_url,		@
 *!				  Revision src_revision,    		@
 *!				  string dst_path_or_url, int|void force )
 *!
 *!  Move @[src_path_or_url] to @[dst_path_or_url].
 *!
 *! @param src_path_or_url
 *!   Path to schedule for moving, or URL to immediately
 *!   move in the repository.
 *! @param src_revision
 *!   The revision of @[src_path_or_url] to move.
 *! @param dst_path_or_url
 *!   Path where to put the moved item, or URL to immediately commit the
 *!   move to the repository.
 *! @param force
 *!   Unless set, the operation will fail if a working copy path
 *!   that contains locally modified and/or unversioned items is
 *!   specified as the destination.
 *!
 *! @returns
 *!   If either @[src_path_or_url] or @[dst_path_or_url] is an URL, an
 *!   array containing information about the commit is returned.
 *!   The elements are as follows:
 *! @array
 *!   @elem int revision
 *!     The revision in which the item was moved.
 *!   @elem string date
 *!     The server-side date of the commit.
 *!   @elem string author
 *!     The author of the commit.
 *! @endarray
 */
static void f_move (INT32 args)
{
  struct pike_string *src_path_or_url, *dst_path_or_url;
  svn_opt_revision_t rev;
  svn_error_t *err;
  apr_pool_t *pool;
  struct svn_client_commit_info_t *commit_info = NULL;
  struct svalue dummy, ret;
  svn_boolean_t force;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("move", args, "%W%*%W", &src_path_or_url,
		&dummy, &dst_path_or_url);
  svn_pike_check_rev_arg ("move", args, 1, &rev);
  force = svn_pike_check_force_arg ("move", args, 3);

  src_path_or_url = svn_pike_to_utf8 (src_path_or_url);
  dst_path_or_url = svn_pike_to_utf8 (dst_path_or_url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_move (&commit_info,
			 svn_path_canonicalize (APR_STR0 (src_path_or_url), pool),
			 &rev,
			 svn_path_canonicalize (APR_STR0 (dst_path_or_url), pool),
			 force, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    make_commit_info (&ret, commit_info);

  apr_pool_destroy (pool);

  do_free_string (src_path_or_url);
  do_free_string (dst_path_or_url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl void propset( string propname, string propval, string target,	  @
 *!			int|void non_recursive )
 *!
 *!  Set property @[propname] to @[propval] on @[target].
 *!
 *! @param propname
 *!   The name of the property to set.
 *! @param propval
 *!   The value to set the property to, or zero to delete it.
 *! @param target
 *!   The path of the file or directory on which to set the property.
 *! @param non_recursive
 *!   If non-zero, set the property only on target, even if it is a directory.
 */
static void f_propset (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct pike_string *propname, *propval=NULL, *target;
  struct svalue dummy;
  svn_string_t propval_string;
  svn_boolean_t recurse;

  if(args > 1 && UNSAFE_IS_ZERO (&Pike_sp[1-args]))
    get_all_args ("propset", args, "%W%*%W", &propname, &dummy, &target);
  else
    get_all_args ("propset", args, "%W%W%W", &propname, &propval, &target);
  recurse = svn_pike_check_recurse_arg ("propset", args, 3);

  propname = svn_pike_to_utf8 (propname);
  if (propval) {
    if (svn_prop_needs_translation (APR_STR0(propname)))
      propval = svn_pike_to_utf8 (propval);
    else
      reference_shared_string (propval);
    if (propval->size_shift) {
      do_free_string (propname);
      do_free_string (propval);
      Pike_error("Binary property can't contain wide characters.\n");
    }
    propval_string.data = APR_STR0 (propval);
    propval_string.len = propval->len;
  }
  target = svn_pike_to_utf8 (target);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_propset (APR_STR0(propname), (propval? &propval_string : NULL),
			    svn_path_canonicalize (APR_STR0(target), pool),
			    recurse, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (propname);
  do_free_string (propval);
  do_free_string (target);

  if (err)
    pike_throw ();
  else
    pop_n_elems (args);
}


/*! @decl int revprop_set( string propname, string propval, string URL,	  @
 *!			   Revision revision, int|void force )
 *!
 *!  Set property @[propname] to @[propval] on revision @[revision] in
 *!  the repository represented by @[URL].
 *!
 *!  Note that unlike its cousin @[propset()], this function
 *!  doesn't affect the working copy at all;  it's a pure network
 *!  operation that changes an @b{unversioned@} property attached to a
 *!  revision.  This can be used to tweak log messages, dates, authors,
 *!  and the like.  Be careful:  it's a lossy operation.
 *!
 *! @param propname
 *!   The name of the property to set.
 *! @param propval
 *!   The value to set the property to, or zero to delete it.
 *! @param URL
 *!   The URL of the repository.
 *! @param revision
 *!   The revision to set the property on.
 *! @param force
 *!   If non-zero, allow newlines in the author property.
 *!
 *! @returns
 *!   The actual revision number in which the property was set.
 */
static void f_revprop_set (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct svalue dummy;
  struct pike_string *propname, *propval=NULL, *url;
  svn_string_t propval_string;
  svn_opt_revision_t rev;
  svn_revnum_t ret_rev;
  svn_boolean_t force;
  svn_client_ctx_t *ctx = THIS->ctx;

  if(args > 1 && UNSAFE_IS_ZERO (&Pike_sp[1-args]))
    get_all_args ("revprop_set", args, "%W%*%W", &propname, &dummy, &url);
  else
    get_all_args ("revprop_set", args, "%W%W%W", &propname, &propval, &url);
  svn_pike_check_rev_arg ("revprop_set", args, 3, &rev);
  force = svn_pike_check_force_arg ("revprop_set", args, 4);

  propname = svn_pike_to_utf8 (propname);
  if (propval) {
    if (svn_prop_needs_translation (APR_STR0(propname)))
      propval = svn_pike_to_utf8 (propval);
    else
      reference_shared_string (propval);
    if (propval->size_shift) {
      do_free_string (propname);
      do_free_string (propval);
      Pike_error("Binary property can't contain wide characters.\n");
    }
    propval_string.data = APR_STR0 (propval);
    propval_string.len = propval->len;
  }
  url = svn_pike_to_utf8 (url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_revprop_set (APR_STR0(propname),
				(propval? &propval_string : NULL),
				svn_path_canonicalize (APR_STR0(url), pool),
				&rev, &ret_rev, force, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (propname);
  do_free_string (propval);
  do_free_string (url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (ret_rev);
  }
}


/*! @decl mapping(string:string) propget( string propname, string target,    @
 *!					  int|void non_recursive,	     @
 *!					  Revision|void revision )
 *!
 *!  Get property @[propname] for @[target].
 *!
 *! @param propname
 *!   The name of the property to get.
 *! @param target
 *!   The path of the file or directory for which to get the property.
 *! @param non_recursive
 *!   If non-zero, get the property only for target, even if it is a directory.
 *! @param revision
 *!   If specified, the revisison of the property to get.  If no revision
 *!   is given, get the property from the working copy, or from the repository
 *!   head if the target is an URL.
 *!
 *! @returns
 *!   A mapping from pathname to property value, with one element for
 *!   each node under target which has the named property set.
 */
static void f_propget (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct pike_string *propname, *target;
  struct svalue ret;
  svn_boolean_t recurse;
  apr_hash_t *props = NULL;
  svn_opt_revision_t rev;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("propget", args, "%W%W", &propname, &target);
  recurse = svn_pike_check_recurse_arg ("propget", args, 2);
  svn_pike_check_rev_arg ("propget", args, 3, &rev);

  propname = svn_pike_to_utf8 (propname);
  target = svn_pike_to_utf8 (target);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_propget (&props, APR_STR0(propname),
			    svn_path_canonicalize (APR_STR0(target), pool),
			    &rev, recurse, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    svn_pike_make_propmap (&ret, props,
			   !!svn_prop_needs_translation (APR_STR0(propname)));

  apr_pool_destroy (pool);

  do_free_string (propname);
  do_free_string (target);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl array(int|string) revprop_get( string propname, string URL,	@
 *!					 Revision revision )
 *!
 *!  Get property @[propname] for revision @[revision] of the
 *!  repository represented by @[URL].
 *!
 *!  Note that unlike its cousin @[propget()], this routine
 *!  doesn't affect the working copy at all; it's a pure network
 *!  operation that queries an @b{unversioned@} property attached to a
 *!  revision.  This can be query log messages, dates, authors, and the
 *!  like.
 *!
 *! @param propname
 *!   The name of the property to get.
 *! @param URL
 *!   The URL of the repository.
 *! @param revision
 *!   The revision for which to get the property.
 *!
 *! @returns
 *!   An array with two elements:
 *! @array
 *!   @elem int revision
 *!     The actual revision number from which the property value was fetched.
 *!   @elem string propval
 *!     The property value itself, or zero if the property was not set.
 *! @endarray
 */
static void f_revprop_get (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct pike_string *propname, *url;
  svn_opt_revision_t rev;
  svn_revnum_t ret_rev;
  svn_string_t *propval_string = NULL;
  struct pike_string *propval = NULL;
  svn_boolean_t prop_is_utf;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("revprop_get", args, "%W%W", &propname, &url);
  svn_pike_check_rev_arg ("revprop_get", args, 2, &rev);

  propname = svn_pike_to_utf8 (propname);
  url = svn_pike_to_utf8 (url);

  pool = svn_pool_create (NULL);

  prop_is_utf = svn_prop_needs_translation (APR_STR0(propname));

  THREADS_ALLOW ();

  err = svn_client_revprop_get (APR_STR0(propname), &propval_string,
				svn_path_canonicalize (APR_STR0(url), pool),
				&rev, &ret_rev, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else if (propval_string)
    propval = make_shared_binary_string(propval_string->data,
					propval_string->len);

  apr_pool_destroy (pool);

  do_free_string (propname);
  do_free_string (url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (ret_rev);
    if (propval) {
      push_string (propval);
      if (prop_is_utf)
	f_utf8_to_string (1);
    } else push_undefined();
    f_aggregate (2);
  }
}


/*! @decl mapping(string:mapping(string:string))			@
 *!		proplist( string target, int|void non_recursive,	@
 *!			  Revision|void revision )
 *!
 *!  List all properties for @[target].
 *!
 *! @param target
 *!   The path of the file or directory for which to list all properties.
 *! @param non_recursive
 *!   If non-zero, list only the properties for target, even if it is
 *!   a directory.
 *! @param revision
 *!   If specified, the revisison of target to list the properties for.
 *!   If no revision is given, list the properties from the working copy,
 *!   or from the repository head if the target is an URL.
 *!
 *! @returns
 *!   A mapping from pathnames to property mappings, with one element for
 *!   each node under target which has properties.  Each property mapping
 *!   is a mapping from property name to property value containing all
 *!   currently set properties for that node.
 */
static void f_proplist (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct pike_string *target;
  struct svalue ret;
  svn_boolean_t recurse;
  apr_array_header_t *props = NULL;
  svn_opt_revision_t rev;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("proplist", args, "%W", &target);
  recurse = svn_pike_check_recurse_arg ("proplist", args, 1);
  svn_pike_check_rev_arg ("proplist", args, 2, &rev);

  target = svn_pike_to_utf8 (target);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_proplist (&props,
			     svn_path_canonicalize (APR_STR0(target), pool),
			     &rev, recurse, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    make_proplist (&ret, props);

  apr_pool_destroy (pool);

  do_free_string (target);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl array(int|mapping(string:string)) revprop_list( string URL,	      @
 *!							  Revision revision )
 *!
 *!  List all properties for revision @[revision] of the repository
 *!  represented by @[URL].
 *!
 *!  Note that unlike its cousin @[proplist()], this function
 *!  doesn't read a working copy at all; it's a pure network operation
 *!  that reads @b{unversioned@} properties attached to a revision.
 *!
 *! @param URL
 *!   The URL of the repository.
 *! @param revision
 *!   The revision to list the properties of.
 *!
 *! @returns
 *!   An array with two elements:
 *! @array
 *!   @elem int revision
 *!     The actual revision number for which the property values were listed.
 *!   @elem mapping(string:string) props
 *!     A mapping from property name to property value containing all
 *!     the properties of this revision.
 *! @endarray
 */
static void f_revprop_list (INT32 args)
{
  svn_error_t *err;
  apr_pool_t *pool;
  struct svalue ret;
  struct pike_string *url;
  svn_opt_revision_t rev;
  svn_revnum_t ret_rev;
  apr_hash_t *props = NULL;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("revprop_list", args, "%W", &url);
  svn_pike_check_rev_arg ("revprop_list", args, 1, &rev);

  url = svn_pike_to_utf8 (url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_revprop_list (&props,
				 svn_path_canonicalize (APR_STR0(url), pool),
				 &rev, &ret_rev, ctx, pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else
    svn_pike_make_propmap (&ret, props, -1);

  apr_pool_destroy (pool);

  do_free_string (url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (ret_rev);
    *Pike_sp++ = ret;
    f_aggregate (2);
  }
}


/*! @decl int export( string from, string to, Revision|void revision,	@
 *!		      int|void force, string|void native_eol )
 *!
 *!  Export the contents of either a subversion repository or
 *!  a subversion working copy into a 'clean' directory (meaning a
 *!  directory with no administrative directories).
 *!
 *! @param from
 *!   Either the path of a working copy on disk, or an URL to the
 *!   repository you wish to export.
 *! @param to
 *!   The path to the directory where you wish to create the exported tree.
 *! @param revision
 *!   Revision number to export (only used when exporting from repository).
 *! @param force
 *!   If non-zero, causes the export to overwrite files or directories.
 *! @param native_eol
 *!   Allows you to override the standard eol marker on the platform you
 *!   are running on.  If zero, the standard eol marker will be used.
 *!  @dl
 *!   @item '"LF"'
 *!      The eol marker is line feed.
 *!   @item '"CR"'
 *!      The eol marker is carriage return.
 *!   @item '"CRLF"'
 *!      The eol marker is carriage return followed by line feed.
 *!  @enddl
 *!
 *! @returns
 *!   The actual revision number that was exported out, if @[from] is an
 *!   URL.  For local exports, -1 is returned.
 */
static void f_export (INT32 args)
{
  struct pike_string *src_path, *dst_path, *native_eol = NULL;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_revnum_t ret_rev = SVN_INVALID_REVNUM;
  svn_opt_revision_t revision;
  svn_boolean_t force;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("export", args, "%W%W", &src_path, &dst_path);
  svn_pike_check_rev_arg ("export", args, 2, &revision);
  force = svn_pike_check_force_arg ("export", args, 3);
  if (args > 4 && Pike_sp[4-args].type == PIKE_T_STRING)
    native_eol = Pike_sp[4-args].u.string;

  src_path = svn_pike_to_utf8 (src_path);
  dst_path = svn_pike_to_utf8 (dst_path);
  native_eol = svn_pike_to_utf8 (native_eol);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_export2 (&ret_rev,
			    svn_path_canonicalize (APR_STR0 (src_path), pool),
			    svn_path_canonicalize (APR_STR0 (dst_path), pool),
			    &revision, force,
			    (native_eol? APR_STR0 (native_eol) : NULL), ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (src_path);
  do_free_string (dst_path);
  do_free_string (native_eol);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (ret_rev);
  }
}


/*! @decl mapping(string:array) ls( string URL, Revision|void revision,	@
 *!				    int|void nonrecursive )
 *!
 *!  List a repository directory or file.
 *!
 *! @param URL
 *!   The URL of the repository item to list.
 *! @param revision
 *!   Revision to list, or UNDEFINED for the latest revision.
 *! @param non_recursive
 *!   If non-zero, don't list contents of subdirectories.
 *!
 *! @returns
 *!   A mapping from entry name to entry information, or UNDEFINED
 *!   if no such entry exists in the repository (at the specified revision).
 *!   The values in the mapping are as follows:
 *! @array
 *!   @elem int kind
 *!     Node kind (dir, file), see @[RA.Library.Session.check_path()].
 *!   @elem int size
 *!     Length of file text, or 0 for directories.
 *!   @elem int has_props
 *!     Non-zero if the node has properties.
 *!   @elem int created_rev
 *!     Last revision in which this node changed.
 *!   @elem int time
 *!     Time of created_rev (modification time).
 *!   @elem string last_author
 *!     Author of created_rev
 *! @endarray
 */
static void f_ls (INT32 args)
{
  struct pike_string *url;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_opt_revision_t revision;
  apr_hash_t *dirents = NULL;
  struct svalue ret;
  svn_boolean_t recurse;
  svn_client_ctx_t *ctx = THIS->ctx;

  get_all_args ("ls", args, "%W", &url);
  svn_pike_check_rev_arg ("ls", args, 1, &revision);
  recurse = svn_pike_check_recurse_arg ("ls", args, 2);

  url = svn_pike_to_utf8 (url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_ls (&dirents, svn_path_canonicalize (APR_STR0 (url), pool),
		       &revision, recurse, ctx, pool);

  THREADS_DISALLOW ();

  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) {
    dirents = NULL;
    err = NULL;
  }

  if (err)
    svn_pike_set_error (err);
  else
    svn_pike_make_dirents (&ret, dirents);

  apr_pool_destroy (pool);

  do_free_string (url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    *Pike_sp++ = ret;
  }
}


/*! @decl void cat( Stream out, string path_or_url, Revision|void revision )
 *!
 *!  Output the content of a file to a stream.
 *!
 *! @param out
 *!   The stream on which to output the file's contents.
 *! @param path_or_url
 *!   Identifies the file to output.
 *! @param revision
 *!   Revision of the contents to output, or UNDEFINED for current contents.
 */
static void f_cat (INT32 args)
{
  struct pike_string *path_or_url;
  svn_error_t *err;
  svn_stream_t *stream_stub;
  apr_pool_t *pool;
  svn_opt_revision_t revision;
  svn_client_ctx_t *ctx = THIS->ctx;
  struct object *out_obj;

  get_all_args ("cat", args, "%o%W", &out_obj, &path_or_url);
  svn_pike_check_rev_arg ("cat", args, 2, &revision);
  svn_pike_check_stream_arg ("cat", args, 0, &stream_stub,
			     NULL, &pool);

  path_or_url = svn_pike_to_utf8 (path_or_url);

  THREADS_ALLOW ();

  err = svn_client_cat (stream_stub,
			svn_path_canonicalize (APR_STR0 (path_or_url), pool),
			&revision, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  do_free_string (path_or_url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_int (0);
  }
}


/*! @decl string url_from_path( string path_or_url )
 *!
 *!  If @[path_or_url] is already an URL, return @[path_or_url].
 *!
 *!  If @[path_or_url] is a versioned item, return the entry URL of
 *!  that item.
 *!
 *!  If @[path_or_url] is unversioned (has no entry), return 0.
 *!
 *! @param path_or_url
 *!   The item to get the entry URL of.
 */
static void f_url_from_path (INT32 args)
{
  struct pike_string *path_or_url;
  svn_error_t *err;
  apr_pool_t *pool;
  const char *url = NULL;
  struct pike_string *ret = NULL;

  get_all_args ("url_from_path", args, "%W", &path_or_url);

  path_or_url = svn_pike_to_utf8 (path_or_url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_url_from_path (&url,
				  svn_path_canonicalize (APR_STR0 (path_or_url),
							 pool), pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else if (url)
    ret = make_shared_string(url);

  apr_pool_destroy (pool);

  do_free_string (path_or_url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    if (ret) {
      push_string (ret);
      f_utf8_to_string (1);
    } else
      push_int (0);
  }
}


/*! @decl string uuid_from_url( string url )
 *!
 *!  Get repository UUID for a @[url].
 *!
 *! @param url
 *!   The URL to the repository you want the UUID for.
 */
static void f_uuid_from_url (INT32 args)
{
  struct pike_string *url;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_client_ctx_t *ctx = THIS->ctx;
  const char *uuid = NULL;
  struct pike_string *ret = NULL;

  get_all_args ("uuid_from_url", args, "%W", &url);

  url = svn_pike_to_utf8 (url);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_client_uuid_from_url (&uuid,
				  svn_path_canonicalize (APR_STR0 (url), pool),
				  ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else if (uuid)
    ret = make_shared_string(uuid);

  apr_pool_destroy (pool);

  do_free_string (url);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    if (ret) {
      push_string (ret);
      f_utf8_to_string (1);
    } else
      push_int (0);
  }
}


/*! @decl string uuid_from_path( string path )
 *!
 *!  Return the repository UUID for working-copy @[path].
 *!
 *! @param path
 *!   The path to the repository to get the UUID for.
 */
static void f_uuid_from_path (INT32 args)
{
  struct pike_string *path;
  svn_error_t *err;
  apr_pool_t *pool;
  svn_wc_adm_access_t *adm_access;
  svn_client_ctx_t *ctx = THIS->ctx;
  const char *path_can, *uuid = NULL;
  struct pike_string *ret = NULL;

  get_all_args ("uuid_from_path", args, "%W", &path);

  path = svn_pike_to_utf8 (path);

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  path_can = svn_path_canonicalize (APR_STR0 (path), pool);

  if (!(err = svn_wc_adm_open (&adm_access, NULL, path_can,
			       FALSE, FALSE, pool)))
    err = svn_client_uuid_from_path (&uuid, path_can, adm_access, ctx, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);
  else if (uuid)
    ret = make_shared_string(uuid);

  apr_pool_destroy (pool);

  do_free_string (path);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    if (ret) {
      push_string (ret);
      f_utf8_to_string (1);
    } else
      push_int (0);
  }
}


/*! @decl Version version( )
 *!
 *!  Get libsvn_client version information.
 */
static void f_version (INT32 args)
{
  pop_n_elems (args);
  svn_pike_push_version (svn_client_version ());
}


/*! @decl void create( array(Auth.AuthProvider)|void auth_providers,	@
 *!		       mapping(string:Config)|void config )
 *!
 *!  Create a Subversion client.
 *!
 *! @param auth_providers
 *!   An ordered list of objects providing authentication credentials.
 *! @param config
 *!   A mapping from names to @[Config]s.  For example, the @[Config]
 *!   for the "~/.subversion/config" file is under the index "config".
 */
static void f_client_create (INT32 args)
{
  struct client_obj *co = THIS;
  struct array *auths = NULL;
  apr_array_header_t *aa;
  INT32 i;

  do_free_mapping(co->config);
  do_free_array(co->auth_providers);
  co->config = NULL;
  co->auth_providers = NULL;

  if (args)
    get_all_args ("create", args, (args>1? "%A%m":"%A"), &auths, &co->config);

  if(co->config)
    co->config = copy_mapping(co->config);

  co->ctx->config = svn_pike_mapping_to_config_hash (co->config, co->pool);

  if (auths) {

    co->auth_providers = auths = copy_array (auths);

    aa = apr_array_make (co->pool, auths->size,
			 sizeof (svn_auth_provider_object_t *));

    if (!aa)
      Pike_error ("Out of memory.\n");

    for (i=0; i<auths->size; i++) {
      svn_auth_provider_object_t *po;
      
      if (ITEM (auths)[i].type != PIKE_T_OBJECT ||
	  !(po = (svn_auth_provider_object_t *)
	    get_storage (ITEM (auths)[i].u.object,
			 svn_pike_auth_provider_program)))
	Pike_error ("Not an AuthProvider object.\n");
      
      *(svn_auth_provider_object_t **)apr_array_push (aa) = po;
    }

    svn_auth_open (&co->ctx->auth_baton, aa, co->pool);

  }

  pop_n_elems (args);
}

static void init_client_obj (struct object *o)
{
  struct client_obj *co = THIS;
  svn_error_t *err;
  int idlevel = Pike_fp->context.identifier_level;

  co->pool = svn_pool_create (NULL);

  low_object_index_no_free (&co->notify_func, Pike_fp->current_object,
			    notify_function + idlevel);
  low_object_index_no_free (&co->log_msg_func, Pike_fp->current_object,
			    log_msg_function + idlevel);
  low_object_index_no_free (&co->cancel_func, Pike_fp->current_object,
			    cancel_function + idlevel);

  if(!co->pool)
    Pike_error("Out of memory.\n");

  err = svn_client_create_context(&co->ctx, co->pool);

  if (err) {
    svn_pike_set_error (err);
    pike_throw();
  }

  svn_auth_open (&co->ctx->auth_baton,
		 apr_array_make (co->pool, 0,
				 sizeof (svn_auth_provider_object_t *)),
		 co->pool);

  co->ctx->notify_func = notify_func_stub;
  co->ctx->notify_baton = &co->notify_func;
  co->ctx->log_msg_func = log_msg_func_stub;
  co->ctx->log_msg_baton = &co->log_msg_func;
  co->ctx->cancel_func = cancel_func_stub;
  co->ctx->cancel_baton = &co->cancel_func;
  co->ctx->config = NULL;
}

static void exit_client_obj (struct object *o)
{
  struct client_obj *co = THIS;

  free_svalue (&co->log_msg_func);
  free_svalue (&co->notify_func);
  free_svalue (&co->cancel_func);

  if (co->pool) {
    apr_pool_destroy (co->pool);
    co->pool = NULL;
  }
}



/* Initialize Client class */

void svn_pike_init_client (void)
{
  ptrdiff_t off;

  start_new_program ();

  off = ADD_STORAGE (struct client_obj);
  set_init_callback(init_client_obj);
  set_exit_callback(exit_client_obj);

  map_variable ("config", "mapping", ID_STATIC,
		off + OFFSETOF (client_obj, config),
		PIKE_T_MAPPING);
  map_variable ("auth_providers", "array", ID_STATIC,
		off + OFFSETOF (client_obj, auth_providers),
		PIKE_T_ARRAY);

  notify_function = ADD_FUNCTION ("notify_func", f_notify_func,
				  tFunc (tStr tInt tInt tStr tInt tInt tInt,
					 tVoid), 0);

  log_msg_function = ADD_FUNCTION ("log_msg_func", f_log_msg_func,
				   tFunc (tArr (tArr (tOr (tStr, tInt))),
					  tStr), 0);

  cancel_function = ADD_FUNCTION ("cancel_func", f_cancel_func,
				  tFunc (tNone, tVoid), 0);

  ADD_FUNCTION ("create", f_client_create, tFunc (tOr (tArr (tObj), tVoid),
						  tVoid), ID_STATIC);

  ADD_FUNCTION ("checkout", f_checkout, tFunc (tStr tStr tOr (tRev, tVoid)
					       tOr (tInt, tVoid), tInt), 0);
  ADD_FUNCTION ("update", f_update, tFunc (tStr tOr (tRev, tVoid)
					   tOr (tInt, tVoid), tInt), 0);
  ADD_FUNCTION ("switch", f_switch, tFunc (tStr tStr tOr (tRev, tVoid)
					   tOr (tInt, tVoid), tInt), 0);
  ADD_FUNCTION ("add", f_add_, tFunc (tStr tOr (tInt, tVoid), tVoid), 0);
  ADD_FUNCTION ("mkdir", f_mkdir, tFunc (tArr (tStr),
					 tArr (tOr (tInt, tStr))), 0);
  ADD_FUNCTION ("delete", f_delete, tFunc (tArr (tStr) tOr (tInt, tVoid),
					   tArr (tOr (tInt, tStr))), 0);
  ADD_FUNCTION ("import", f_import, tFunc (tStr tStr tOr (tInt, tVoid),
					   tArr (tOr (tInt, tStr))), 0);
  ADD_FUNCTION ("commit", f_commit, tFunc (tArr (tStr) tOr (tInt, tVoid),
					   tArr (tOr (tInt, tStr))), 0);
  ADD_FUNCTION ("status", f_status, tFunc (tStr
					   tFunc (tStr tMap (tStr, tMixed),
						  tVoid) tOr (tInt, tVoid)
					   tOr (tInt, tVoid) tOr (tInt, tVoid)
					   tOr (tRev, tVoid) tOr (tInt, tVoid),
					   tInt), 0);
  ADD_FUNCTION ("log", f_log, tFunc (tArr (tStr) tFunc (tInt tStr tStr tStr
							tOr (tMap (tStr,
								   tArr (tOr (tInt,
									      tStr))),
							     tVoid),
							tVoid)
				     tRev tRev tOr (tInt, tVoid)
				     tOr (tInt, tVoid), tVoid), 0);
  ADD_FUNCTION ("blame", f_blame, tFunc (tStr tFunc (tInt tInt tStr tStr tStr,
						     tVoid)
					 tRev tRev, tVoid), 0);
  ADD_FUNCTION ("diff", f_diff_, tFunc (tStr tRev tStr tRev tOr (tArr (tStr),
								 tVoid)
					tOr (tObjImpl_STDIO_FD, tVoid)
					tOr (tObjImpl_STDIO_FD, tVoid)
					tOr (tInt, tVoid) tOr (tInt, tVoid)
					tOr (tInt, tVoid), tVoid), 0);
  ADD_FUNCTION ("diff_peg", f_diff_peg, tFunc (tStr tRev tRev tRev
					       tOr (tArr (tStr), tVoid)
					       tOr (tObjImpl_STDIO_FD, tVoid)
					       tOr (tObjImpl_STDIO_FD, tVoid)
					       tOr (tInt, tVoid)
					       tOr (tInt, tVoid)
					       tOr (tInt, tVoid), tVoid), 0);
  ADD_FUNCTION ("merge", f_merge, tFunc (tStr tRev tStr tRev tStr
					 tOr (tInt, tVoid) tOr (tInt, tVoid)
					 tOr (tInt, tVoid) tOr (tInt, tVoid),
					 tVoid), 0);
  ADD_FUNCTION ("merge_peg", f_merge_peg, tFunc (tStr tRev tRev tRev tStr
						 tOr (tInt, tVoid)
						 tOr (tInt, tVoid)
						 tOr (tInt, tVoid)
						 tOr (tInt, tVoid), tVoid), 0);
  ADD_FUNCTION ("cleanup", f_cleanup, tFunc (tStr, tVoid), 0);
  ADD_FUNCTION ("relocate", f_relocate, tFunc (tStr tStr tStr tOr (tInt, tVoid),
					       tVoid), 0);
  ADD_FUNCTION ("revert", f_revert, tFunc (tArr (tStr) tOr (tInt, tVoid),
					   tVoid), 0);
  ADD_FUNCTION ("resolved", f_resolved, tFunc (tStr tOr (tInt, tVoid),
					       tVoid), 0);
  ADD_FUNCTION ("copy", f_copy, tFunc (tStr tRev tStr,
				       tArr (tOr (tInt, tStr))), 0);
  ADD_FUNCTION ("move", f_move, tFunc (tStr tRev tStr tOr (tInt, tVoid),
				       tArr (tOr (tInt, tStr))), 0);
  ADD_FUNCTION ("propset", f_propset, tFunc (tStr tStr tStr tOr (tInt, tVoid),
					     tVoid), 0);
  ADD_FUNCTION ("revprop_set", f_revprop_set, tFunc (tStr tStr tStr tRev
						     tOr (tInt, tVoid), tInt),
		0);
  ADD_FUNCTION ("propget", f_propget, tFunc (tStr tStr tOr (tInt, tVoid),
					     tMap (tStr, tStr)), 0);
  ADD_FUNCTION ("revprop_get", f_revprop_get,
		tFunc (tStr tStr tRev, tArr (tOr (tInt, tStr))), 0);
  ADD_FUNCTION ("proplist", f_proplist, tFunc (tStr tOr (tInt, tVoid),
					       tMap (tStr, tMap (tStr, tStr))),
		0);
  ADD_FUNCTION ("revprop_list", f_revprop_list,
		tFunc (tStr tRev, tArr (tOr (tInt, tMap (tStr, tStr)))), 0);
  ADD_FUNCTION ("export", f_export, tFunc (tStr tStr tOr (tRev, tVoid)
					   tOr (tInt, tVoid) tOr (tStr, tVoid),
					   tInt), 0);
  ADD_FUNCTION ("ls", f_ls, tFunc (tStr tOr (tRev, tVoid) tOr (tInt, tVoid),
				   tMap (tStr, tArray)), 0);
  ADD_FUNCTION ("cat", f_cat, tFunc (tObj tStr tOr (tRev, tVoid), tVoid), 0);
  ADD_FUNCTION ("url_from_path", f_url_from_path, tFunc (tStr, tStr), 0);
  ADD_FUNCTION ("uuid_from_url", f_uuid_from_url, tFunc (tStr, tStr), 0);
  ADD_FUNCTION ("uuid_from_path", f_uuid_from_path, tFunc (tStr, tStr), 0);

  ADD_FUNCTION ("version", f_version, tFunc (tNone, tObj), 0);

  end_class ("Client", 0);
}

/*! @endclass  */
/*! @endmodule */

gotpike.org | Copyright © 2004 - 2011 | Pike is a trademark of Department of Computer and Information Science, Linköping University