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

Modules

ADT
Database
GTK2
GUI
IP
PiJAX
Public
Sql
Stdio
Subversion
System
Tools
Xosd
lua
v4l2
wx

Recent Changes

Public.Parser.XML2 1.50
Public.ZeroMQ 1.1
Public.Template.Mustache 1.0
Public.Protocols.XMPP 1.4
Sql.Provider.jdbc 1.0

Popular Downloads

Public.Parser.JSON2 1.0
Public.Parser.JSON 0.2
GTK2 2.23
Public.Web.FCGI 1.8
Public.Parser.XML2 1.48


Module Information
Subversion
Viewing contents of Subversion-0.110/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 
#include 
#include 


/*! @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; inelts; 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; inelts; 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; isize; 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 - 2019 | Pike is a trademark of Department of Computer and Information Science, Linköping University