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

File Contents

Contents of /Subversion-0.112/delta.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_delta.h>
#include <svn_path.h>

#include <apr_md5.h>


/*! @module Subversion */

/*! @module Delta
 *!
 *!  The Delta submodule contains functions and classes related
 *!  to delta-parsing.
 */


/*! @class DeltaWindow
 *!
 *!  An @[DeltaWindow] object describes how to reconstruct a
 *!  contiguous section of the target string (the "target view") using a
 *!  specified contiguous region of the source string (the "source
 *!  view").  It contains a series of instructions which assemble the
 *!  new target string text by pulling together substrings from:
 *!
 *!   - the source view,
 *!
 *!   - the previously constructed portion of the target view,
 *!
 *!   - a string of new data contained within the window structure
 *!
 *!  The source view must always slide forward from one window to the
 *!  next; that is, neither the beginning nor the end of the source view
 *!  may move to the left as we read from a window stream.  This
 *!  property allows us to apply deltas to non-seekable source streams
 *!  without making a full copy of the source stream.
 */

static struct program *txwindow_program;
struct txwindow_obj
{
  svn_txdelta_window_t win;
  struct pike_string *new_string;
  svn_string_t new_sstring;
};

#define THIS_TXWINDOW ((struct txwindow_obj *)Pike_fp->current_storage)

static void init_txwindow_obj (struct object *o)
{
  struct txwindow_obj *tw = THIS_TXWINDOW;
  memset (&tw->win, 0, sizeof(tw->win));
  tw->win.ops = NULL;
  tw->win.new_data = &tw->new_sstring;
  tw->new_sstring.data = NULL;
  tw->new_sstring.len = 0;
}

static void exit_txwindow_obj (struct object *o)
{
  struct txwindow_obj *tw = THIS_TXWINDOW;
  if (tw->win.ops) {
    free ((void *) tw->win.ops);
    tw->win.ops = NULL;
  }
}

static int get_delta_action (enum svn_delta_action *res, struct svalue *s)
{
  if (s->type != PIKE_T_INT)
    return 0;

  switch (s->u.integer) {
  case svn_txdelta_source:
  case svn_txdelta_target:
  case svn_txdelta_new:
    *res = s->u.integer;
    return 1;
  }

  return 0;
}

static int get_apr_size_t (apr_size_t *res, struct svalue *s)
{
#ifdef AUTO_BIGNUM
  LONGEST l;
#endif
  if (s->type == PIKE_T_INT)
    *res = s->u.integer;
#ifdef AUTO_BIGNUM
  else if (is_bignum_object_in_svalue(s) &&
	   int64_from_bignum(&l, s->u.object) == 1)
    *res = l;
#endif
  else
    return 0;
  return 1;
}

/*! @decl void create( int sview_offset, int sview_len, @
 *!		       array(array(string|int)) ops )
 *!
 *!  Create a text delta window.
 *!
 *! @param sview_offset
 *!   The offset of the source view for this window.
 *! @param sview_len
 *!   The length of the source view for this window.
 *! @param ops
 *!   The instructions for this window.
 */
static void f_txwindow_create (INT32 args)
{
  struct txwindow_obj *tw = THIS_TXWINDOW;
  LONGEST sview_offset, sview_len;
  struct array *ops, *op;
  apr_size_t tview_len = 0;
  int i, src_ops = 0;
  struct string_builder sb;
  svn_txdelta_op_t *tops;
  struct pike_string *s;
  ONERROR r1, r2;

  get_all_args ("create", args, "%l%l%a", &sview_offset, &sview_len, &ops);

  if (tw->new_string) {
    free_string (tw->new_string);
    tw->new_string = NULL;
  }
  if (tw->win.ops)
    free ((void *) tw->win.ops);
  memset (&tw->win, 0, sizeof (tw->win));
  tw->win.ops = NULL;
  tw->win.new_data = &tw->new_sstring;
  tw->new_sstring.data = NULL;
  tw->new_sstring.len = 0;

  tops = (ops->size? xalloc (ops->size * sizeof (svn_txdelta_op_t)) : NULL);
  SET_ONERROR (r1, free, tops);
  init_string_builder (&sb, 0);
  SET_ONERROR (r2, free_string_builder, &sb);

  for (i = 0; i < ops->size; i++)
    if (ITEM (ops)[i].type != PIKE_T_ARRAY ||
	(op = ITEM (ops)[i].u.array)->size < 1 ||
	!get_delta_action (&tops[i].action_code, &ITEM (op)[0]))
      Pike_error ("Invalid opcode in ops\n");
    else switch (tops[i].action_code) {
    case svn_txdelta_source:
      src_ops++;
      if (op->size != 3 || !get_apr_size_t (&tops[i].offset, &ITEM (op)[1]) ||
	  !get_apr_size_t (&tops[i].length, &ITEM (op)[2]))
	Pike_error ("Invalid SOURCE opcode in ops\n");
      if (tops[i].offset < 0 ||
	  tops[i].length <= 0 ||
	  tops[i].offset + tops[i].length > sview_len)
	Pike_error ("Invalid offset/length\n");
      tview_len += tops[i].length;
      break;
    case svn_txdelta_target:
      if (op->size != 3 || !get_apr_size_t (&tops[i].offset, &ITEM (op)[1]) ||
	  !get_apr_size_t (&tops[i].length, &ITEM (op)[2]))
	Pike_error ("Invalid TARGET opcode in ops\n");
      if (tops[i].offset < 0 ||
	  tops[i].length <= 0 ||
	  tops[i].offset >= tview_len)
	Pike_error ("Invalid offset/length\n");
      tview_len += tops[i].length;
      break;
    case svn_txdelta_new:
      if (op->size != 2 || ITEM (op)[1].type != PIKE_T_STRING)
	Pike_error ("Invalid NEW opcode in ops\n");
      if ((s = ITEM (op)[1].u.string)->size_shift)
	Pike_error ("Wide characters not allowed in text data\n");
      tops[i].offset = sb.s->len;
      tops[i].length = s->len;
      string_builder_shared_strcat (&sb, s);
      tview_len += s->len;
      break;
    }

  UNSET_ONERROR(r2);
  tw->new_string = finish_string_builder (&sb);

  UNSET_ONERROR(r1);
  tw->win.sview_offset = sview_offset;
  tw->win.sview_len = sview_len;
  tw->win.tview_len = tview_len;
  tw->win.num_ops = ops->size;
  tw->win.src_ops = src_ops;
  tw->win.ops = tops;
  tw->new_sstring.data = APR_STR0 (tw->new_string);
  tw->new_sstring.len = tw->new_string->len;

  pop_n_elems (args);
}

static void txwindow_magic_index (INT32 args)
{
  struct txwindow_obj *tw = THIS_TXWINDOW;
  char *name;

  get_all_args("`[]", args, "%s", &name);

  if (!strcmp (name, "sview_offset")) {
    /*! @decl int sview_offset
     *!
     *!   The offset of the source view for this window.
     */
    pop_n_elems (args);
    push_int64 (tw->win.sview_offset);
  } else if (!strcmp (name, "sview_len")) {
    /*! @decl int sview_len
     *!
     *!   The length of the source view for this window.
     */
    pop_n_elems (args);
    push_int64 (tw->win.sview_len);
  } else if (!strcmp (name, "tview_len")) {
    /*! @decl int tview_len
     *!
     *!   The length of the target view for this window.
     */
    pop_n_elems (args);
    push_int64 (tw->win.tview_len);
  } else if (!strcmp (name, "num_ops")) {
    /*! @decl int num_ops
     *!
     *!   Total number of delta instructions in this window.
     */
    pop_n_elems (args);
    push_int (tw->win.num_ops);
  } else if (!strcmp (name, "src_ops")) {
    /*! @decl int src_ops
     *!
     *!   Number of delta instructions in this window that rely on
     *!   the source window.
     */
    pop_n_elems (args);
    push_int (tw->win.src_ops);
  } else if (!strcmp (name, "ops")) {
    /*! @decl array(array(string|int)) ops
     *!
     *!   The instructions for this window.  Each instruction takes one
     *!   of the following three forms:
     *!
     *!   @array
     *!     @elem int SOURCE
     *!
     *!     @elem int offset
     *!
     *!     @elem int len
     *!   @endarray
     *!   Append the len bytes at offset in the source view to the target.
     *!   It must be the case that 0 <= offset < offset + len <= size of
     *!   source view.
     *!
     *!   @array
     *!     @elem int TARGET
     *!
     *!     @elem int offset
     *!
     *!     @elem int len
     *!   @endarray
     *!   Append the len bytes at offset in the target view, to the
     *!   target.  It must be the case that 0 <= offset < current position
     *!   in the target view.  However!  offset + len may be @b{beyond@} the end
     *!   of the existing target data.  "Where the heck does the text come
     *!   from, then?"  If you start at offset, and append len bytes one at a
     *!   time, it'll work out --- you're adding new bytes to the end at the
     *!   same rate you're reading them from the middle.  Thus, if your
     *!   current target text is "abcdefgh", and you get a "target"
     *!   instruction whose offset is 6 and whose len is 7, the resulting
     *!   string is "abcdefghghghghg".  This trick is actually useful in
     *!   encoding long runs of consecutive characters, long runs of CR/LF
     *!   pairs, etc.
     *!
     *!   @array
     *!     @elem int NEW
     *!
     *!     @elem string data
     *!
     *!   @endarray
     *!   Append a new string to the target.
     */
    const svn_txdelta_op_t *op;
    int i;

    pop_n_elems (args);
    for (i=0, op=tw->win.ops; i<tw->win.num_ops; i++, op++)
      switch (op->action_code) {
      case svn_txdelta_source:
	push_int (svn_txdelta_source);
	push_int64 (op->offset);
	push_int64 (op->length);
	f_aggregate (3);
	break;
      case svn_txdelta_target:
	push_int (svn_txdelta_target);
	push_int64 (op->offset);
	push_int64 (op->length);
	f_aggregate (3);
	break;
      case svn_txdelta_new:
	push_int (svn_txdelta_new);
	push_string (make_shared_binary_string (tw->win.new_data->data +
						op->offset, op->length));
	f_aggregate (2);
	break;
      default:
	push_int (0);
	break;
      }
    f_aggregate (i);
  } else {
    pop_n_elems (args);
    push_undefined ();
  }
}


/*! @endclass */


/* Convert a svn_txdelta_window_t to a DeltaWindow
   object and push it on the stack */
static void push_txwindow (svn_txdelta_window_t *window)
{
  struct txwindow_obj *tw;
  push_int (0);
  push_int (0);
  ref_push_array (&empty_array);
  push_object (clone_object (txwindow_program, 3));
  tw = (struct txwindow_obj *) Pike_sp[-1].u.object->storage;
  if (tw->win.ops)
    free ((void *) tw->win.ops);
  tw->win = *window;
  if (tw->win.ops) {
    if (tw->win.num_ops) {
      svn_txdelta_op_t *tops =
	xalloc (tw->win.num_ops * sizeof (svn_txdelta_op_t));
      memcpy (tops, tw->win.ops,
	      tw->win.num_ops * sizeof (svn_txdelta_op_t));
      tw->win.ops = tops;
    } else
      tw->win.ops = NULL;
  }
  if (tw->new_string)
    free_string (tw->new_string);
  if (tw->win.new_data) {
    tw->new_string = make_shared_binary_string(tw->win.new_data->data,
					       tw->win.new_data->len);
    tw->new_sstring.data = APR_STR0 (tw->new_string);
    tw->new_sstring.len = tw->new_string->len;
    tw->win.new_data = &tw->new_sstring;
  } else {
    tw->new_string = NULL;
    tw->new_sstring.data = NULL;
    tw->new_sstring.len = 0;
  }
}


/* Internal class for handling txdelta_window_handler_t:s */
static struct program *txdelta_handler_program;
static int txdelta_handler_func;
struct txdelta_handler_obj {
  svn_txdelta_window_handler_t handler;
  void *hbaton;
};
#define THIS_TXDELTA_HANDLER ((struct txdelta_handler_obj *)Pike_fp->current_storage)
static void f_txdelta_handler (INT32 args)
{
  struct txdelta_handler_obj *th = THIS_TXDELTA_HANDLER;
  struct object *window_obj;
  svn_txdelta_window_t *window;
  svn_error_t *err;

  get_all_args ("txdelta_handler", args, "%O", &window_obj);

  if (window_obj == NULL)
    window = NULL;
  else if (!(window =
	     (svn_txdelta_window_t *)get_storage (window_obj,
						  txwindow_program)))
    Pike_error ("Object is not DeltaWindow.\n");

  THREADS_ALLOW ();

  err = th->handler (window, th->hbaton);

  THREADS_DISALLOW ();

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

/*! @decl DeltaWindowHandler to_svndiff( Stream output )
 *!
 *!  Prepare to produce an svndiff-format diff from text delta windows.
 *!
 *! @param output
 *!   A writable generic stream to write the svndiff data to.
 *!
 *! @returns
 *!   A window handler function.  At the end of the delta window stream,
 *!   the function must be called passing zero for the @[DeltaWindow]
 *!   argument.
 */
static struct program *diff_txdelta_handler_program;
static ptrdiff_t diff_txdelta_handler_offs;
struct diff_txdelta_handler_obj {
  union anything output;
  apr_pool_t *pool;
};
#define THIS_DIFF_TXDELTA_HANDLER ((struct diff_txdelta_handler_obj *)(Pike_fp->current_storage + diff_txdelta_handler_offs))
static void init_diff_txdelta_handler_obj (struct object *o)
{
  THIS_DIFF_TXDELTA_HANDLER->pool = NULL;
}
static void exit_diff_txdelta_handler_obj (struct object *o)
{
  struct diff_txdelta_handler_obj *dtho = THIS_DIFF_TXDELTA_HANDLER;
  if (dtho->pool) {
    apr_pool_destroy (dtho->pool);
    dtho->pool = NULL;
  }
}
static void f_diff_txdelta_handler_create (INT32 args)
{
  struct txdelta_handler_obj *th = THIS_TXDELTA_HANDLER;
  struct diff_txdelta_handler_obj *dtho = THIS_DIFF_TXDELTA_HANDLER;
  svn_stream_t *output;
  apr_pool_t *pool;

  svn_pike_check_stream_arg ("to_svndiff", args, 0, &output,
			     NULL, &pool);

  dtho->pool = pool;
  assign_short_svalue (&dtho->output, &Pike_sp[-args].u, PIKE_T_OBJECT);

  THREADS_ALLOW();

  svn_txdelta_to_svndiff (output, pool, &th->handler, &th->hbaton);

  THREADS_DISALLOW();

  pop_n_elems (args);
}
static void f_to_svndiff (INT32 args)
{
  struct object *obj = clone_object (diff_txdelta_handler_program, args);
  low_object_index_no_free (Pike_sp, obj, txdelta_handler_func);
  Pike_sp ++;
  free_object (obj);
}


/*! @decl DeltaWindowHandler apply( Stream source, Stream target,	@
 *!				      string|void error_info )
 *!
 *!  Prepare to apply a text delta.
 *!
 *! @param source
 *!   A readable generic stream yielding the source data.
 *! @param target
 *!   A writable generic stream to write target data to.
 *! @param error_info
 *!   If non-zero, it is inserted parenthetically into the error string
 *!   for any error thrown by @[apply()] or the returned handler.
 *!   (It is normally used to provide path information, since there's
 *!    nothing else in the delta application's context to supply a path
 *!    for error messages.)
 *!
 *! @returns
 *!   A window handler function.  At the end of the delta window stream,
 *!   the function must be called passing zero for the @[DeltaWindow]
 *!   argument.
 */
static struct program *apply_txdelta_handler_program;
static ptrdiff_t apply_txdelta_handler_offs;
struct apply_txdelta_handler_obj {
  union anything source, target;
  apr_pool_t *pool;
};
#define THIS_APPLY_TXDELTA_HANDLER ((struct apply_txdelta_handler_obj *)(Pike_fp->current_storage + apply_txdelta_handler_offs))
static void init_apply_txdelta_handler_obj (struct object *o)
{
  THIS_APPLY_TXDELTA_HANDLER->pool = NULL;
}
static void exit_apply_txdelta_handler_obj (struct object *o)
{
  struct apply_txdelta_handler_obj *atho = THIS_APPLY_TXDELTA_HANDLER;
  if (atho->pool) {
    apr_pool_destroy (atho->pool);
    atho->pool = NULL;
  }
}
static void f_apply_txdelta_handler_create (INT32 args)
{
  struct txdelta_handler_obj *th = THIS_TXDELTA_HANDLER;
  struct apply_txdelta_handler_obj *atho = THIS_APPLY_TXDELTA_HANDLER;
  svn_stream_t *source, *target;
  struct object *source_obj, *target_obj;
  struct pike_string *error_info = NULL;
  apr_pool_t *pool;

  get_all_args ("apply", args, (args>2? "%O%O%W" : "%O%O"),
		&source_obj, &target_obj, &error_info);

  svn_pike_check_stream_arg ("apply", args, 0, &source, NULL, &pool);
  svn_pike_check_stream_arg ("apply", args, 1, &target, pool, NULL);

  error_info = svn_pike_to_utf8 (error_info);

  atho->pool = pool;
  assign_short_svalue (&atho->source, &Pike_sp[-args].u, PIKE_T_OBJECT);
  assign_short_svalue (&atho->target, &Pike_sp[1-args].u, PIKE_T_OBJECT);

  THREADS_ALLOW();

  svn_txdelta_apply (source, target, NULL, APR_STR0 (error_info), pool,
		     &th->handler, &th->hbaton);

  THREADS_DISALLOW();

  do_free_string (error_info);

  pop_n_elems (args);
}
static void f_apply_ (INT32 args)
{
  struct object *obj = clone_object (apply_txdelta_handler_program, args);
  low_object_index_no_free (Pike_sp, obj, txdelta_handler_func);
  Pike_sp ++;
  free_object (obj);
}


/*! @class DeltaStream
 *!
 *!   A delta stream --- this is the hat from which we pull a series of
 *!   @[DeltaWindow] objects, which, taken in order, describe the
 *!   entire target string.
 */

static struct program *deltastream_program;
struct stream_obj
{
  union anything source, target;
  apr_pool_t *pool;
  svn_txdelta_stream_t *stream;
};

#define THIS_STREAM ((struct stream_obj *)Pike_fp->current_storage)

static void init_stream_obj (struct object *o)
{
  struct stream_obj *tds = THIS_STREAM;
  tds->pool = NULL;
  tds->stream = NULL;
}

static void exit_stream_obj (struct object *o)
{
  struct stream_obj *tds = THIS_STREAM;
  if (tds->pool) {
    apr_pool_destroy (tds->pool);
    tds->pool = NULL;
  }
  tds->stream = NULL;
}


/*! @decl void create( Stream source, Stream target )
 *!
 *!   Create a delta stream that will turn the byte string from
 *!   @[source] into the byte stream from @[target].  @[source] and
 *!   @[target] are both readable generic streams.  When we call
 *!   @[next_window()], it will read from @[source] and @[target] to
 *!   gather as much data as it needs.
 */
static void f_deltastream_create (INT32 args)
{
  struct stream_obj *tds = THIS_STREAM;
  union anything src_obj, targ_obj;
  svn_stream_t *source, *target;
  apr_pool_t *pool;

  get_all_args ("create", args, "%o%o", &src_obj.object, &targ_obj.object);
  svn_pike_check_stream_arg ("create", args, 0, &source, tds->pool, &pool);
  svn_pike_check_stream_arg ("create", args, 1, &target, pool, NULL);

  tds->pool = pool;
  assign_short_svalue (&tds->source, &src_obj, PIKE_T_OBJECT);
  assign_short_svalue (&tds->target, &targ_obj, PIKE_T_OBJECT);  

  THREADS_ALLOW();

  svn_txdelta (&tds->stream, source, target, pool);

  THREADS_DISALLOW();

  pop_n_elems (args);
}

/*! @decl DeltaWindow next_window( )
 *!
 *!   Get the next window from this delta stream.  When we have
 *!   completely reconstructed the target string, return zero.
 */
static void f_next_window (INT32 args)
{
  struct stream_obj *tds = THIS_STREAM;
  svn_txdelta_window_t *win;
  svn_error_t *err;

  THREADS_ALLOW();

  err = svn_txdelta_next_window (&win, tds->stream, tds->pool);

  THREADS_DISALLOW();

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

/* Convert a binary digest to hex */
static void push_md5_digest (const unsigned char *digest)
{
  char buf[APR_MD5_DIGESTSIZE*2], *p = buf;
  int i;
  for (i=0; i<APR_MD5_DIGESTSIZE; i++) {
    int by = *digest++;
    *p++ = "0123456789abcdef"[(by>>4)&15];
    *p++ = "0123456789abcdef"[by&15];
  }
  push_string (make_shared_binary_string (buf, APR_MD5_DIGESTSIZE*2));
}


/*! @decl string md5_digest( )
 *!
 *!  Return the MD5 digest for the complete fulltext deltified by this
 *!  stream, or zero if the final zero window has not yet been returned.
 */
static void f_md5_digest (INT32 args)
{
  struct stream_obj *tds = THIS_STREAM;
  const unsigned char *digest;

  THREADS_ALLOW();

  digest = svn_txdelta_md5_digest (tds->stream);

  THREADS_DISALLOW();

  pop_n_elems (args);
  if (digest)
    push_md5_digest (digest);
  else
    push_int (0);
}


/*! @endclass */


/*! @class DeltaEditor
 *!
 *!  A class containing functions a tree producer can use to drive a tree
 *!  consumer.
 *!
 *!
 *!  Here's how to use these functions to express a tree delta.
 *!
 *!  The delta consumer implements the callback functions described in
 *!  this class, and the delta producer invokes them.  So the
 *!  caller (producer) is pushing tree delta data at the callee
 *!  (consumer).
 *!
 *!  At the start of traversal, the consumer provides a DeltaEditor
 *!  object global to the entire delta edit.  If there is a target
 *!  revision that needs to be set for this operation, the producer
 *!  should called the 'set_target_revision' function at this point.
 *!  Next, the producer should call the @[open_root()] function, to get a
 *!  @[DirectoryEditor] representing root of the tree being
 *!  edited.
 *!
 *!  FUNCTION CALL ORDERING
 *!
 *!  There are six restrictions on the order in which the producer
 *!  may use the batons:
 *!
 *!  1. The producer may call @[DirectoryEditor->open_directory()],
 *!     @[DirectoryEditor->add_directory()], @[DirectoryEditor->open_file()],
 *!     @[DirectoryEditor->add_file()], or @[DirectoryEditor->delete_entry()]
 *!     at most once on any given directory entry.
 *!
 *!  2. The producer may not destruct a @[DirectoryEditor] object until
 *!     it has destructed all @[DirectoryEditor] objects for its
 *!     subdirectories.
 *!
 *!  3. When a producer calls @[DirectoryEditor->open_directory()] or
 *!     @[DirectoryEditor->add_directory()], it must specify the most
 *!     recently opened of the currently open directory batons.  Put
 *!     another way, the producer cannot have two sibling directory
 *!     batons open at the same time.
 *!
 *!  4. A producer must call @[DirectoryEditor->change_dir_prop()] on
 *!     a directory either before opening any of the directory's subdirs
 *!     or after closing them, but not in the middle.
 *!
 *!  5. When the producer calls @[DirectoryEditor->open_file()] or
 *!     @[DirectoryEditor->add_file()], either:
 *!
 *!     (a) The producer must follow with the changes to the file
 *!     (@[FileEditor->change_file_prop()] and/or
 *!      @[FileEditor->apply_textdelta()], as applicable)
 *!     followed by discarding the @[FileEditor], before issuing
 *!     any other file or directory calls, or
 *!
 *!     (b) The producer must follow with a @[FileEditor->change_file_prop()]
 *!     call if it is applicable, before issuing any other file or directory
 *!     calls; later, after all @[DirectoryEditor]s including the root
 *!     have been discarded, the producer must issue `apply_textdelta'
 *!     and `close_file' calls.
 *!
 *!  6. When the producer calls @[FileEditor->apply_textdelta()], it
 *!     must make all of the window handler calls (including the 0 window
 *!     at the end) before issuing any other @[DeltaEditor] calls.
 *!
 *!  So, the producer needs to use @[DirectoryEditor]s and @[FileEditor]s
 *!  as if it is doing a single depth-first traversal of the tree, with the
 *!  exception that the producer may keep @[FileEditor]s open in order to
 *!  make @[FileEditor->apply_textdelta()] calls at the end.
 */

struct program *svn_pike_delta_editor_program = NULL;

#define THIS ((struct deltaeditor_obj *)Pike_fp->current_storage)

static void init_deltaeditor_obj (struct object *o)
{
  struct deltaeditor_obj *de = THIS;
  de->pool = NULL;
  de->editor = NULL;
}

static void exit_deltaeditor_obj (struct object *o)
{
  struct deltaeditor_obj *de = THIS;
  if(de->pool) {
    apr_pool_destroy (de->pool);
    de->pool = NULL;
  }
  de->editor = NULL;
}


/*! @class FileEditor
 *!
 *!  A class representing a file within a @[DirectoryEditor].
 *!
 *!  An object of this class can be used to change the file's contents
 *!  or properties.
 */

static struct program *fileeditor_program;
static int fileeditor_ident;

struct fileeditor_obj {
  apr_pool_t *pool;
  void *baton;
  const svn_delta_editor_t *editor;
  struct object *parentdir;
};
#define THIS_FILEEDITOR ((struct fileeditor_obj *)Pike_fp->current_storage)

static struct program *direditor_program;
static int direditor_ident;

struct direditor_obj {
  apr_pool_t *pool;
  void *baton;
  const svn_delta_editor_t *editor;
  struct object *parentdir;
};

static void init_fileeditor_obj (struct object *o)
{
  struct fileeditor_obj *fe = THIS_FILEEDITOR;
  fe->pool = NULL;
  fe->baton = NULL;
}

static void exit_fileeditor_obj (struct object *o)
{
  struct fileeditor_obj *fe = THIS_FILEEDITOR;
  if(fe->pool) {
    apr_pool_destroy (fe->pool);
    fe->pool = NULL;
  }
  fe->baton = NULL;
}

static void f_fileeditor_create (INT32 args)
{
  struct fileeditor_obj *fe = THIS_FILEEDITOR;
  struct direditor_obj *de = NULL;
  struct object *delta_obj = LOW_PARENT_INFO(Pike_fp->current_object,
					     fileeditor_program)->parent;
  struct deltaeditor_obj *dle;
  svn_error_t *err;
  INT_TYPE rev = SVN_INVALID_REVNUM;
  int mode = 0;
  struct pike_string *path, *copyfrom_path = NULL;

  if (delta_obj)
    dle = (struct deltaeditor_obj *)(get_storage (delta_obj,
						  svn_pike_delta_editor_program));
  else
    Pike_error ("Can't get parent object for Subversion.Delta.DeltaEditor.FileEditor!\n");

  if (args > 2 && Pike_sp[-2].type == PIKE_T_OBJECT &&
      Pike_sp[-1].type == PIKE_T_INT) {
    mode = Pike_sp[-1].u.integer;
    delta_obj = Pike_sp[-2].u.object;
    if (delta_obj &&
	(de = (struct direditor_obj *)(get_storage (delta_obj,
						    direditor_program)))) {
      if(fe->parentdir)
	free_object (fe->parentdir);
      fe->parentdir = delta_obj;
      Pike_sp -= 2;
      args -= 2;
    } else
      mode = 0;
  }

  switch(mode) {
  default:
    Pike_error ("?");
    break;

  case 1:
    get_all_args ("add_file", args, (args>1? "%W%W%i" : "%W"),
		  &path, &copyfrom_path, &rev);

    if (!(fe->editor = dle->editor))
      Pike_error ("DeltaEditor is not open.\n");
    path = svn_pike_to_utf8 (path);
    copyfrom_path = svn_pike_to_utf8 (copyfrom_path);
    fe->pool = svn_pool_create (de->pool);

    THREADS_ALLOW ();
    
    err = dle->editor->add_file (svn_path_canonicalize (APR_STR0 (path), fe->pool),
				 de->baton,
				 (copyfrom_path?
				  svn_path_canonicalize (APR_STR0(copyfrom_path),
							 fe->pool) : NULL),
				 rev, fe->pool, &fe->baton);
    
    THREADS_DISALLOW ();

    if (err)
      svn_pike_set_error (err);

    do_free_string (path);
    do_free_string (copyfrom_path);
    break;

  case 2:
    get_all_args ("open_file", args, "%W%i", &path, &rev);

    if (!(fe->editor = dle->editor))
      Pike_error ("DeltaEditor is not open.\n");
    path = svn_pike_to_utf8 (path);
    fe->pool = svn_pool_create (de->pool);

    THREADS_ALLOW ();
    
    err = dle->editor->open_file (svn_path_canonicalize (APR_STR0 (path),
							 fe->pool),
				  de->baton, rev, fe->pool, &fe->baton);
    
    THREADS_DISALLOW ();

    if (err)
      svn_pike_set_error (err);

    do_free_string (path);
    break;

  }

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


/* Internal subclass of DeltaHandler */

static struct program *fe_txdelta_handler_program;
static int fe_txdelta_handler_ident;

static void f_fe_txdelta_handler_create (INT32 args)
{
  struct txdelta_handler_obj *th = THIS_TXDELTA_HANDLER;
  struct object *file_obj = LOW_PARENT_INFO(Pike_fp->current_object,
					    fe_txdelta_handler_program)->parent;


  struct fileeditor_obj *fe;
  svn_error_t *err;
  struct pike_string *checksum = NULL;

  if (file_obj)
    fe = (struct fileeditor_obj *)(get_storage (file_obj,
						fileeditor_program));
  else
    Pike_error ("Can't get parent object for Subversion.Delta.DeltaEditor.FileEditor.FeDeltaHandler!\n");

  if(args > 1 && !UNSAFE_IS_ZERO (&Pike_sp[1-args]))
    get_all_args ("apply_textdelta", args, "%W", &checksum);

  if (!fe->baton)
    Pike_error ("FileEditor is not open.\n");

  checksum = svn_pike_to_utf8 (checksum);

  THREADS_ALLOW ();

  err = fe->editor->apply_textdelta (fe->baton,
				     (checksum? APR_STR0 (checksum) : NULL),
				     fe->pool,
				     &th->handler, &th->hbaton);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  do_free_string (checksum);

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


/*! @decl DeltaWindowHandler apply_textdelta( string|void base_checksum )
 *!
 *!  Apply a text delta, yielding the new revision of a file.
 *!
 *! @param base_checksum
 *!   The hex MD5 digest for the base text against which the delta
 *!   is being applied; it is ignored if zero, and may be ignored
 *!   even if non-zero.  If it is not ignored, it must match the
 *!   checksum of the base text against which svndiff data is being
 *!   applied; if it does not, apply_textdelta will throw the error
 *!   SVN_ERR_CHECKSUM_MISMATCH (if there is no base text, there may
 *!   still be an error if @[base_checksum] is neither zero nor the
 *!   hex MD5 checksum of the empty string).
 *!
 *! @returns
 *!   A functions that consume a series of delta windows.  The function
 *!   will typically apply the delta windows to produce some file, or save
 *!   the windows somewhere.  At the end of the delta window stream, the
 *!   function must be called passing zero for the @[DeltaWindow] argument.
 */
static void f_apply_textdelta (INT32 args)
{
  struct object *obj = parent_clone_object (fe_txdelta_handler_program,
					    Pike_fp->current_object,
					    fe_txdelta_handler_ident +
					    Pike_fp->context.identifier_level,
					    args);
  low_object_index_no_free (Pike_sp, obj, txdelta_handler_func);
  Pike_sp ++;
  free_object (obj);
}


/*! @decl void change_file_prop( string name, string value )
 *!
 *!  Change the value of a file's property.
 *!
 *! @param name
 *!   The name of the property to change.
 *! @param value
 *!   The new value of the property, or 0 if the property
 *!   should be removed altogether.  
 */
static void f_change_file_prop (INT32 args)
{
  struct fileeditor_obj *fe = THIS_FILEEDITOR;
  svn_error_t *err;
  struct pike_string *name, *value = NULL;
  struct svalue dummy;
  svn_string_t val_string;

  if(args > 1 && UNSAFE_IS_ZERO (&Pike_sp[1-args]))
    get_all_args ("change_file_prop", args, "%W%*", &name, &dummy);
  else
    get_all_args ("change_file_prop", args, "%W%W", &name, &value);

  if (!fe->baton)
    Pike_error ("FileEditor is not open.\n");

  name = svn_pike_to_utf8 (name);
  if (value) {
    if (svn_prop_needs_translation (APR_STR0(name)))
      value = svn_pike_to_utf8 (value);
    else
      reference_shared_string (value);

    if (value->size_shift) {
      do_free_string (name);
      do_free_string (value);
      Pike_error("Binary property can't contain wide characters.\n");
    }

    val_string.data = APR_STR0 (value);
    val_string.len = value->len;
  }

  THREADS_ALLOW ();

  err = fe->editor->change_file_prop (fe->baton, APR_STR0 (name),
				     (value? &val_string : NULL), fe->pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  do_free_string (name);
  do_free_string (value);

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

/*! @decl void close_file( string|void text_checksum )
 *!
 *!  We are done processing this file.
 *!
 *! @param text_checksum
 *!   The hex MD5 digest for the fulltext that resulted from a
 *!   delta application, see @[apply_textdelta()].  The
 *!   checksum is ignored if zero.  If non-zero, it is compared to the
 *!   checksum of the new fulltext, and the error SVN_ERR_CHECKSUM_MISMATCH
 *!   is thrown if they do not match.  If there is no new fulltext,
 *!   @[text_checksum] is ignored.
 */
static void f_close_file (INT32 args)
{
  struct fileeditor_obj *fe = THIS_FILEEDITOR;
  svn_error_t *err;
  struct pike_string *checksum = NULL;

  if(args > 0 && !UNSAFE_IS_ZERO (&Pike_sp[-args]))
    get_all_args ("close_file", args, "%W", &checksum);

  if (!fe->baton)
    Pike_error ("FileEditor is not open.\n");

  checksum = svn_pike_to_utf8 (checksum);

  THREADS_ALLOW ();

  err = fe->editor->close_file (fe->baton, (checksum? APR_STR0 (checksum) : NULL),
				fe->pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  do_free_string (checksum);

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

/*! @endclass */


/*! @class DirectoryEditor
 *!
 *!  A class representing a directory within a @[DeltaEditor].
 *!
 *!  Most of the callbacks work in the obvious way:
 *!
 *!      @[delete_entry()]
 *!      @[add_file()]
 *!      @[add_directory()]    
 *!      @[open_file()]
 *!      @[open_directory()]
 *!
 *!  Each of these takes a PATH argument, giving the path (relative to the 
 *!  root of the edit) of the file, subdirectory, or directory entry to
 *!  change. Editors will usually want to join this relative path with some
 *!  base stored in the edit baton (e.g. a URL, a location in the OS
 *!  filesystem).
 *!
 *!  Since every call requires a DirectoryEditor object, including
 *!  @[add_directory()] and @[open_directory()], where do we ever get our
 *!  initial DirectoryEditor object, to get things started?  The
 *!  @[DeltaEditor->open_root()] function returns an object for the top
 *!  directory of the change.  In general, the producer needs to invoke
 *!  the @[DeltaEditor->open_root()] function before it can get anything
 *!  of interest done.
 *!
 *!  While @[DeltaEditor->open_root()] provides a DirectoryEditor object
 *!  for the root of the tree being changed, the @[add_directory()] and
 *!  @[open_directory()] callbacks provide objects for other directories.
 *!  Like the callbacks above, they take a relative path PATH, and then
 *!  return a new DirectoryEditor object for the subdirectory being
 *!  created / modified.  The producer can then use this object to make
 *!  further changes in that subdirectory.
 *!
 *!  So, if we already have subdirectories named `foo' and `foo/bar',
 *!  then the producer can create a new file named `foo/bar/baz.c' by
 *!  calling:
 *!  @pre{
 *!     open_root () ->
 *!     open_directory ("foo") ->
 *!     open_directory ("foo/bar") ->
 *!     add_file ("foo/bar/baz.c")
 *!  @}
 *!  
 *!  The @[add_file()] and @[open_file()] callbacks each return a 
 *!  @[FileEditor] object for the file being created or changed.
 *!
 *!  The @[add_file()] and @[add_directory()] functions each take arguments
 *!  COPYFROM_PATH and COPYFROM_REVISION.  If COPYFROM_PATH is
 *!  non-zero, then COPYFROM_PATH and COPYFROM_REVISION indicate where
 *!  the file or directory should be copied from (to create the file
 *!  or directory being added).
 */

#define THIS_DIREDITOR ((struct direditor_obj *)Pike_fp->current_storage)

static void init_direditor_obj (struct object *o)
{
  struct direditor_obj *de = THIS_DIREDITOR;
  de->pool = NULL;
  de->baton = NULL;
}

static void exit_direditor_obj (struct object *o)
{
  struct direditor_obj *de = THIS_DIREDITOR;
  if(de->pool) {
    apr_pool_destroy (de->pool);
    de->pool = NULL;
  }
  de->baton = NULL;
}


static void f_direditor_create (INT32 args)
{
  struct direditor_obj *de2 = NULL, *de = THIS_DIREDITOR;
  struct object *delta_obj = LOW_PARENT_INFO(Pike_fp->current_object,
					     direditor_program)->parent;
  struct deltaeditor_obj *dle;
  svn_error_t *err;
  INT_TYPE rev = SVN_INVALID_REVNUM;
  int mode = 0;
  struct pike_string *path, *copyfrom_path = NULL;

  if (delta_obj)
    dle = (struct deltaeditor_obj *)(get_storage (delta_obj,
						  svn_pike_delta_editor_program));
  else
    Pike_error ("Can't get parent object for Subversion.Delta.DeltaEditor.DirectoryEditor!\n");

  if (args > 2 && Pike_sp[-2].type == PIKE_T_OBJECT &&
      Pike_sp[-1].type == PIKE_T_INT) {
    mode = Pike_sp[-1].u.integer;
    delta_obj = Pike_sp[-2].u.object;
    if (delta_obj &&
	(de2 = (struct direditor_obj *)(get_storage (delta_obj,
						     direditor_program)))) {
      if(de->parentdir)
	free_object (de->parentdir);
      de->parentdir = delta_obj;
      Pike_sp -= 2;
      args -= 2;
    } else
      mode = 0;
  }

  switch(mode) {
  default:
    get_all_args ("open_root", args, "%i", &rev);

    if (!(de->editor = dle->editor))
      Pike_error ("DeltaEditor is not open.\n");
    de->pool = svn_pool_create (dle->pool);
    
    THREADS_ALLOW ();
    
    err = dle->editor->open_root (dle->editor_baton, rev, de->pool, &de->baton);
    
    THREADS_DISALLOW ();

    if (err)
      svn_pike_set_error (err);

    break;

  case 1:
    get_all_args ("add_directory", args, (args>1? "%W%W%i" : "%W"),
		  &path, &copyfrom_path, &rev);

    if (!(de->editor = dle->editor))
      Pike_error ("DeltaEditor is not open.\n");
    path = svn_pike_to_utf8 (path);
    copyfrom_path = svn_pike_to_utf8 (copyfrom_path);
    de->pool = svn_pool_create (de2->pool);

    THREADS_ALLOW ();
    
    err = dle->editor->add_directory (svn_path_canonicalize (APR_STR0 (path),
							     de->pool),
				      de2->baton,
				      (copyfrom_path?
				       svn_path_canonicalize (APR_STR0(copyfrom_path),
							      de->pool) : NULL),
				      rev, de->pool, &de->baton);
    
    THREADS_DISALLOW ();

    if (err)
      svn_pike_set_error (err);

    do_free_string (path);
    do_free_string (copyfrom_path);
    break;

  case 2:
    get_all_args ("open_directory", args, "%W%i", &path, &rev);

    if (!(de->editor = dle->editor))
      Pike_error ("DeltaEditor is not open.\n");
    path = svn_pike_to_utf8 (path);
    de->pool = svn_pool_create (de2->pool);

    THREADS_ALLOW ();
    
    err = dle->editor->open_directory (svn_path_canonicalize (APR_STR0 (path),
							      de->pool),
				       de2->baton, rev, de->pool, &de->baton);
    
    THREADS_DISALLOW ();

    if (err)
      svn_pike_set_error (err);

    do_free_string (path);
    break;

  }

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

/*! @decl void delete_entry( string path, int revision )
 *!
 *!  Remove the directory entry named @[path], a child of this directory.
 *!  If @[revision] is set, it is used as asanity check to ensure that
 *!  you are removing the revision of @[path] that you really think you are.
 */
static void f_delete_entry (INT32 args)
{
  struct direditor_obj *de = THIS_DIREDITOR;
  svn_error_t *err;
  struct pike_string *path;
  INT_TYPE rev;

  get_all_args ("delete_entry", args, "%W%i", &path, &rev);

  if (!de->baton)
    Pike_error ("DirectoryEditor is not open.\n");

  path = svn_pike_to_utf8 (path);

  THREADS_ALLOW ();

  err = de->editor->delete_entry (svn_path_canonicalize (APR_STR0(path), de->pool),
				  rev, de->baton, de->pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  do_free_string (path);

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

/*! @decl DirectoryEditor add_directory( string path,			@
 *!					 string|void copyfrom_path,	@
 *!					 int|void copyfrom_revision )
 *!
 *!  We are going to add a new subdirectory named @[path].  We will use
 *!  the object returned from this function for further changes in the
 *!  new subdirectory.
 *!
 *!  If @[copyfrom_path] is non-zero, this add has history (i.e., is a
 *!  copy), and the origin of the copy may be recorded as @[copyfrom_path]
 *!  under @[copyfrom_revision].
 */
static void f_add_directory (INT32 args)
{
  struct parent_info *p = LOW_PARENT_INFO(Pike_fp->current_object,
					  direditor_program);
  ref_push_object (Pike_fp->current_object);
  push_int (1);
  push_object (parent_clone_object (direditor_program,
				    p->parent,
				    p->parent_identifier,
				    args+2));
}

/*! @decl DirectoryEditor open_directory( string path, int base_revision )
 *!
 *!  We are going to make changes in a subdirectory (of this directory).
 *!  The subdirectory is specified by @[path].  The returned object
 *!  should be used for subsequent changes in this subdirectory.
 *!  If a valid revnum, @[base_revision] is the current revision of
 *!  the subdirectory.
 */
static void f_open_directory (INT32 args)
{
  struct parent_info *p = LOW_PARENT_INFO(Pike_fp->current_object,
					  direditor_program);
  ref_push_object (Pike_fp->current_object);
  push_int (2);
  push_object (parent_clone_object (direditor_program,
				    p->parent,
				    p->parent_identifier,
				    args+2));
}

/*! @decl void change_dir_prop( string name, string value )
 *!
 *!  Change the value of a directory's property.
 *!
 *! @param name
 *!   The name of the property to change.
 *! @param value
 *!   The new value of the property, or 0 if the property
 *!   should be removed altogether.  
 */
static void f_change_dir_prop (INT32 args)
{
  struct direditor_obj *de = THIS_DIREDITOR;
  svn_error_t *err;
  struct pike_string *name, *value = NULL;
  struct svalue dummy;
  svn_string_t val_string;

  if(args > 1 && UNSAFE_IS_ZERO (&Pike_sp[1-args]))
    get_all_args ("change_dir_prop", args, "%W%*", &name, &dummy);
  else
    get_all_args ("change_dir_prop", args, "%W%W", &name, &value);

  if (!de->baton)
    Pike_error ("DirectoryEditor is not open.\n");

  name = svn_pike_to_utf8 (name);
  if (value) {
    if (svn_prop_needs_translation (APR_STR0(name)))
      value = svn_pike_to_utf8 (value);
    else
      reference_shared_string (value);

    if (value->size_shift) {
      do_free_string (name);
      do_free_string (value);
      Pike_error("Binary property can't contain wide characters.\n");
    }

    val_string.data = APR_STR0 (value);
    val_string.len = value->len;
  }

  THREADS_ALLOW ();

  err = de->editor->change_dir_prop (de->baton, APR_STR0 (name),
				     (value? &val_string : NULL), de->pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  do_free_string (name);
  do_free_string (value);

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

/*! @decl void close_directory( )
 *!
 *!  We're done processing this subdirectory.
 */
static void f_close_directory (INT32 args)
{
  struct direditor_obj *de = THIS_DIREDITOR;
  svn_error_t *err;

  if (!de->baton)
    Pike_error ("DirectoryEditor is not open.\n");

  THREADS_ALLOW ();

  err = de->editor->close_directory (de->baton, de->pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  if (err)
    pike_throw ();
  else {
    de->baton = NULL;
    pop_n_elems (args);
  }
}

/*! @decl void absent_directory( string path )
 *!
 *!  In this directory, indicate that @[path] is present as a
 *!  subdirectory in the edit source, but cannot be conveyed to the
 *!  edit consumer (perhaps because of authorization restrictions).
 */
static void f_absent_directory (INT32 args)
{
  struct direditor_obj *de = THIS_DIREDITOR;
  svn_error_t *err;
  struct pike_string *path;

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

  if (!de->baton)
    Pike_error ("DirectoryEditor is not open.\n");

  path = svn_pike_to_utf8 (path);

  THREADS_ALLOW ();

  err = de->editor->absent_directory (svn_path_canonicalize (APR_STR0 (path),
							     de->pool),
				      de->baton, de->pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  do_free_string (path);

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

/*! @decl FileEditor add_file( string path,			@
 *!			       string|void copyfrom_path,	@
 *!			       int|void copyfrom_revision )
 *!
 *!  We are going to add a new file named @[path].
 *!
 *!  If @[copyfrom_path] is non-zero, this add has history (i.e., is a
 *!  copy), and the origin of the copy may be recorded as @[copyfrom_path]
 *!  under @[copyfrom_revision].
 */
static void f_add_file (INT32 args)
{
  struct parent_info *p = LOW_PARENT_INFO(Pike_fp->current_object,
					  direditor_program);
  ref_push_object (Pike_fp->current_object);
  push_int (1);
  push_object (parent_clone_object (fileeditor_program,
				    p->parent,
				    p->parent_identifier
				    + fileeditor_ident - direditor_ident,
				    args+2));
}

/*! @decl FileEditor open_file( string path, int base_revision )
 *!
 *!  We are going to make change to a file named @[path], which
 *!  resides in this directory.  If a valid revnum, @[base_revision]
 *!  is the current revision of the file.
 */
static void f_open_file (INT32 args)
{
  struct parent_info *p = LOW_PARENT_INFO(Pike_fp->current_object,
					  direditor_program);
  ref_push_object (Pike_fp->current_object);
  push_int (2);
  push_object (parent_clone_object (fileeditor_program,
				    p->parent,
				    p->parent_identifier
				    + fileeditor_ident - direditor_ident,
				    args+2));
}

/*! @decl void absent_file( string path )
 *!
 *!  In this directory, indicate that @[path] is present as a
 *!  file in the edit source, but cannot be conveyed to the
 *!  edit consumer (perhaps because of authorization restrictions).
 */
static void f_absent_file (INT32 args)
{
  struct direditor_obj *de = THIS_DIREDITOR;
  svn_error_t *err;
  struct pike_string *path;

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

  if (!de->baton)
    Pike_error ("DirectoryEditor is not open.\n");

  path = svn_pike_to_utf8 (path);

  THREADS_ALLOW ();

  err = de->editor->absent_file (svn_path_canonicalize (APR_STR0 (path), de->pool),
				 de->baton, de->pool);
  
  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  do_free_string (path);

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


/*! @endclass */



/*! @decl void set_target_revision( int target_revision )
 *!
 *!  Set the target revision for this edit to @[target_revision].
 *!  This call, if used, should precede all other editor calls.
 */
static void f_set_target_revision (INT32 args)
{
  struct deltaeditor_obj *de = THIS;
  svn_error_t *err;
  INT_TYPE rev;

  get_all_args ("set_target_revision", args, "%i", &rev);

  if (!de->editor)
    Pike_error ("DeltaEditor is not open.\n");

  THREADS_ALLOW ();

  err = de->editor->set_target_revision (de->editor_baton, rev, de->pool);
  
  THREADS_DISALLOW ();

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

/*! @decl DirectoryEditor open_root( int base_revision )
 *!
 *!  Create a @[DirectoryEditor] for the top directory of the change.
 *!  (This is the top of the subtree being changed, not necessarily
 *!  the root of the filesystem.)  Like any other @[DirectoryEditor], the
 *!  producer should call @[DirectoryEditor.close_directory()] when they're
 *!  done.  And like other open_* calls, the @[base_revision] here is
 *!  the current revision of the directory (before getting bumped up
 *!  to the new target revision set with @[set_target_revision()]).
 */
static void f_open_root (INT32 args)
{
  push_object (parent_clone_object (direditor_program,
				    Pike_fp->current_object,
				    direditor_ident +
				    Pike_fp->context.identifier_level,
				    args));
}

/*! @decl void close_edit( )
 *!
 *!  All delta processing is done.
 */
static void f_close_edit (INT32 args)
{
  struct deltaeditor_obj *de = THIS;
  svn_error_t *err;

  if (!de->editor)
    Pike_error ("DeltaEditor is not open.\n");

  THREADS_ALLOW ();

  err = de->editor->close_edit (de->editor_baton, de->pool);
  
  THREADS_DISALLOW ();

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

/*! @decl void abort_edit( )
 *!
 *!  The editor-driver has decided to bail out.  Allow the editor to
 *!  gracefully clean up things if it needs to.
 */
static void f_abort_edit (INT32 args)
{
  struct deltaeditor_obj *de = THIS;
  svn_error_t *err;

  if (!de->editor)
    Pike_error ("DeltaEditor is not open.\n");

  THREADS_ALLOW ();

  err = de->editor->abort_edit (de->editor_baton, de->pool);
  
  THREADS_DISALLOW ();

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


/*! @endclass */


/* Stubs for pike delta editors */

static apr_status_t svn_pike_cleanup_obj (void *arg)
{
  do_free_object (arg);
  return APR_SUCCESS;
}

static svn_error_t *set_target_revision_stub (void *edit_baton,
					      svn_revnum_t target_revision,
					      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_int (target_revision);
      apply (edit_baton, "set_target_revision", 1);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *open_root_stub (void *edit_baton,
				    svn_revnum_t base_revision,
				    apr_pool_t *dir_pool,
				    void **root_baton)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;
  struct object *obj;

  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_int (base_revision);
      apply (edit_baton, "open_root", 1);

      if (Pike_sp[-1].type != PIKE_T_OBJECT)
	Pike_error ("open_root() returned a non-object!\n");

      obj = Pike_sp[-1].u.object;
      --Pike_sp;
      apr_pool_cleanup_register (dir_pool, obj, svn_pike_cleanup_obj, NULL);
      *root_baton = obj;
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *delete_entry_stub (const char *path,
				       svn_revnum_t revision,
				       void *parent_baton,
				       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 {
      svn_pike_push_utf8 (path);
      push_int (revision);
      apply (parent_baton, "delete_entry", 2);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *add_directory_stub (const char *path,
					void *parent_baton,
					const char *copyfrom_path,
					svn_revnum_t copyfrom_revision,
					apr_pool_t *dir_pool,
					void **child_baton)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;
  struct object *obj;

  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 {
      svn_pike_push_utf8 (path);
      if (copyfrom_path)
	svn_pike_push_utf8 (copyfrom_path);
      else
	push_int (0);
      push_int (copyfrom_revision);
      apply (parent_baton, "add_directory", 3);

      if (Pike_sp[-1].type != PIKE_T_OBJECT)
	Pike_error ("add_directory() returned a non-object!\n");

      obj = Pike_sp[-1].u.object;
      --Pike_sp;
      apr_pool_cleanup_register (dir_pool, obj, svn_pike_cleanup_obj, NULL);
      *child_baton = obj;
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *open_directory_stub (const char *path,
					 void *parent_baton,
					 svn_revnum_t base_revision,
					 apr_pool_t *dir_pool,
					 void **child_baton)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;
  struct object *obj;

  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 {
      svn_pike_push_utf8 (path);
      push_int (base_revision);
      apply (parent_baton, "open_directory", 2);

      if (Pike_sp[-1].type != PIKE_T_OBJECT)
	Pike_error ("open_directory() returned a non-object!\n");

      obj = Pike_sp[-1].u.object;
      --Pike_sp;
      apr_pool_cleanup_register (dir_pool, obj, svn_pike_cleanup_obj, NULL);
      *child_baton = obj;
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *change_dir_prop_stub (void *dir_baton,
					  const char *name,
					  const svn_string_t *value,
					  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 {
	svn_pike_push_utf8 (name);
	if (value) {
	  push_string (make_shared_binary_string (value->data, value->len));
	  if (svn_prop_needs_translation (name))
	    f_utf8_to_string (1);
	} else
	  push_int (0);
      apply (dir_baton, "change_dir_prop", 2);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *close_directory_stub (void *dir_baton,
					  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 {
      apply (dir_baton, "close_directory", 0);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *absent_directory_stub (const char *path,
					   void *parent_baton,
					   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 {
      svn_pike_push_utf8 (path);
      apply (parent_baton, "absent_directory", 1);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *add_file_stub (const char *path,
				   void *parent_baton,
				   const char *copy_path,
				   svn_revnum_t copy_revision,
				   apr_pool_t *file_pool,
				   void **file_baton)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;
  struct object *obj;

  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 {
      svn_pike_push_utf8 (path);
      if (copy_path)
	svn_pike_push_utf8 (copy_path);
      else
	push_int (0);
      push_int (copy_revision);
      apply (parent_baton, "add_file", 3);

      if (Pike_sp[-1].type != PIKE_T_OBJECT)
	Pike_error ("add_file() returned a non-object!\n");

      obj = Pike_sp[-1].u.object;
      --Pike_sp;
      apr_pool_cleanup_register (file_pool, obj, svn_pike_cleanup_obj, NULL);
      *file_baton = obj;
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *open_file_stub (const char *path,
				    void *parent_baton,
				    svn_revnum_t base_revision,
				    apr_pool_t *file_pool,
				    void **file_baton)
{
  JMP_BUF recovery;
  svn_error_t *err = SVN_NO_ERROR;
  struct object *obj;

  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 {
      svn_pike_push_utf8 (path);
      push_int (base_revision);
      apply (parent_baton, "open_file", 2);

      if (Pike_sp[-1].type != PIKE_T_OBJECT)
	Pike_error ("open_file() returned a non-object!\n");

      obj = Pike_sp[-1].u.object;
      --Pike_sp;
      apr_pool_cleanup_register (file_pool, obj, svn_pike_cleanup_obj, NULL);
      *file_baton = obj;
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}


static apr_status_t svn_pike_cleanup_svalue (void *arg)
{
  do_free_svalue (arg);
  return APR_SUCCESS;
}

svn_error_t *svn_pike_window_handler_stub (svn_txdelta_window_t *window,
					   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 {
      if (window)
	push_txwindow (window);
      else
	push_int (0);

      apply_svalue (baton, 1);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *apply_textdelta_stub (void *file_baton,
					  const char *base_checksum,
					  apr_pool_t *pool,
					  svn_txdelta_window_handler_t *handler,
					  void **handler_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 {
      struct svalue *sv;

      svn_pike_push_utf8 (base_checksum);
      apply (file_baton, "apply_textdelta", 1);
      sv = apr_palloc (pool, sizeof (*sv));
      assign_svalue_no_free (sv, Pike_sp-1);
      apr_pool_cleanup_register (pool, sv, svn_pike_cleanup_svalue, NULL);
      pop_stack ();
      *handler = svn_pike_window_handler_stub;
      *handler_baton = sv;
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}


static svn_error_t *change_file_prop_stub (void *file_baton,
					   const char *name,
					   const svn_string_t *value,
					   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 {
	svn_pike_push_utf8 (name);
	if (value) {
	  push_string (make_shared_binary_string (value->data, value->len));
	  if (svn_prop_needs_translation (name))
	    f_utf8_to_string (1);
	} else
	  push_int (0);
      apply (file_baton, "change_file_prop", 2);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *close_file_stub (void *file_baton,
				     const char *text_checksum,
				     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 {
      svn_pike_push_utf8 (text_checksum);
      apply (file_baton, "close_file", 0);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *absent_file_stub (const char *path,
				      void *parent_baton,
				      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 {
      svn_pike_push_utf8 (path);
      apply (parent_baton, "absent_file", 1);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *close_edit_stub (void *edit_baton, 
				     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 {
      apply (edit_baton, "close_edit", 0);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static svn_error_t *abort_edit_stub (void *edit_baton,
				     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 {
      apply (edit_baton, "abort_edit", 0);
      pop_stack ();
    }
    UNSETJMP (recovery);

    THREADS_ALLOW ();
  } while (0);

  return err;
}

static const svn_delta_editor_t pike_delta_editor = {
  set_target_revision_stub,
  open_root_stub,
  delete_entry_stub,
  add_directory_stub,
  open_directory_stub,
  change_dir_prop_stub,
  close_directory_stub,
  absent_directory_stub,
  add_file_stub,
  open_file_stub,
  apply_textdelta_stub,
  change_file_prop_stub,
  close_file_stub,
  absent_file_stub,
  close_edit_stub,
  abort_edit_stub
};

void svn_pike_delta_editor_from_obj (struct object *obj,
				     const svn_delta_editor_t **editor,
				     void **baton, apr_pool_t *pool)
{
  struct deltaeditor_obj *de;

  add_ref (obj);
  apr_pool_cleanup_register (pool, obj, svn_pike_cleanup_obj, NULL);
  if ((de = (struct deltaeditor_obj *)
       get_storage (obj, svn_pike_delta_editor_program))) {
    *editor = de->editor;
    *baton = de->editor_baton;
  } else {
    *editor = &pike_delta_editor;
    *baton = obj;
  }
}


/*! @decl Stream parse_svndiff (DeltaWindowHandler handler,	@
 *!				int|void error_on_early_close)
 *!
 *!  Return a writable generic stream which will parse svndiff-format
 *!  data into a text delta, invoking @[handler] whenever a new window is
 *!  ready.
 *!
 *! @param handler
 *!   A window handler function.  At the end of the svndiff stream,
 *!   the function will be called with a zero argument.
 *! @param error_on_early_close
 *!   If non-zero, attempting to close this stream before it has handled
 *!   the entire svndiff data set will result in SVN_ERR_SVNDIFF_UNEXPECTED_END,
 *!   else this error condition will be ignored.
 */
static void f_parse_svndiff (INT32 args)
{
  struct svalue *handler, *sv;
  svn_boolean_t error_on_early_close;
  svn_stream_t *stream;
  apr_pool_t *pool;

  get_all_args ("parse_svndiff", args, "%*", &handler);
  error_on_early_close = svn_pike_check_force_arg ("parse_svndiff", args, 1);

  pool = svn_pool_create (NULL);
  sv = apr_palloc (pool, sizeof (*sv));
  assign_svalue_no_free (sv, handler); 
  apr_pool_cleanup_register (pool, sv, svn_pike_cleanup_svalue, NULL);

  THREADS_ALLOW ();

  stream = svn_txdelta_parse_svndiff (svn_pike_window_handler_stub, sv,
				      error_on_early_close, pool);

  THREADS_DISALLOW ();

  if (stream) {
    pop_n_elems (args);
    svn_pike_push_svnstream (stream, pool);
  } else {
    apr_pool_destroy (pool);
    pop_n_elems (args);
    push_int (0);
  }
				      
}


/*! @decl Stream target_push (DeltaWindowHandler handler, Stream source)
 *!
 *!  Return a writable stream which, when fed target data, will send
 *!  delta windows to @[handler] which transform the data in @[source]
 *!  to the target data.
 *!
 *! @param handler
 *!   A window handler function.  At the end of the window stream,
 *!   the function will be called with a zero argument.
 *! @param source
 *!   The stream handler functions will read data from this stream as
 *!   necessary.
 */
static void f_target_push (INT32 args)
{
  struct svalue *handler, *sv;
  svn_stream_t *stream, *source;
  apr_pool_t *pool;

  get_all_args ("target_push", args, "%*", &handler);
  svn_pike_check_stream_arg ("target_push", args, 1, &source,
			     NULL, &pool);

  sv = apr_palloc (pool, sizeof (*sv));
  assign_svalue_no_free (sv, handler); 
  apr_pool_cleanup_register (pool, sv, svn_pike_cleanup_svalue, NULL);

  THREADS_ALLOW ();

  stream = svn_txdelta_target_push (svn_pike_window_handler_stub, sv,
				    source, pool);

  THREADS_DISALLOW ();

  if (stream) {
    pop_n_elems (args);
    svn_pike_push_svnstream (stream, pool);
  } else {
    apr_pool_destroy (pool);
    pop_n_elems (args);
    push_int (0);
  }
}


/*! @decl void send_string (string data, DeltaWindowHandler handler)
 *!
 *!  Send the contents of @[data] to window-handler @[handler]. 
 *!  This is effectively a 'copy' operation, resulting in delta windows that 
 *!  make the target equivalent to the value of @[data].
 */
static void f_send_string (INT32 args)
{
  struct svalue *handler;
  struct pike_string *data;
  svn_string_t sstr;
  apr_pool_t *pool;
  svn_error_t *err;

  get_all_args ("send_string", args, "%S%*", &data, &handler);

  pool = svn_pool_create (NULL);
  sstr.len = data->len;
  sstr.data = APR_STR0 (data);

  THREADS_ALLOW ();

  err = svn_txdelta_send_string (&sstr, svn_pike_window_handler_stub, handler,
				 pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

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


/*! @decl string send_stream (Stream stream, DeltaWindowHandler handler)
 *!
 *!  Send the contents of @[stream] to window-handler @[handler]. 
 *!  This is effectively a 'copy' operation, resulting in delta windows that 
 *!  make the target equivalent to the @[stream].
 *!
 *! @returns
 *!  The MD5 checksum for the fulltext that was deltififed.
 */
static void f_send_stream (INT32 args)
{
  struct svalue *handler;
  struct object *stream_obj;
  svn_stream_t *stream;
  apr_pool_t *pool;
  svn_error_t *err;
  unsigned char digest[APR_MD5_DIGESTSIZE];

  get_all_args ("send_stream", args, "%O%*", &stream_obj, &handler);
  svn_pike_check_stream_arg ("send_stream", args, 0, &stream,
			     NULL, &pool);

  THREADS_ALLOW ();

  err = svn_txdelta_send_stream (stream, svn_pike_window_handler_stub, handler,
				 digest, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

  if (err)
    pike_throw ();
  else {
    pop_n_elems (args);
    push_md5_digest (digest);
  }
}


/*! @decl void send_txstream (DeltaStream txstream,		    @
 *!			      DeltaWindowHandler handler)
 *!
 *!  Send the contents of @[txstream] to window-handler @[handler]. 
 *!  Windows will be extracted from the stream and delivered to the handler.
 */
static void f_send_txstream (INT32 args)
{
  struct svalue *handler;
  struct object *stream_obj;
  struct stream_obj *txstream;
  apr_pool_t *pool;
  svn_error_t *err;

  get_all_args ("send_txstream", args, "%o%*", &stream_obj, &handler);
  
  if (!(txstream =
	(struct stream_obj *)get_storage (stream_obj,
					  deltastream_program)))
    Pike_error ("Object is not DeltaStream.\n");

  pool = svn_pool_create (NULL);

  THREADS_ALLOW ();

  err = svn_txdelta_send_txstream (txstream->stream,
				   svn_pike_window_handler_stub,
				   handler, pool);

  THREADS_DISALLOW ();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

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


/*! @decl DeltaWindow read_svndiff_window( Stream stream,	@
 *!					     int svndiff_version )
 *!
 *!  Read and parse one delta window in svndiff format from the
 *!  readable stream @[stream].  The caller must take responsibility for
 *!  stripping off the four-byte 'SVN<ver>' header at the beginning of
 *!  the svndiff document before reading the first window, and must
 *!  provide the version number (the value of the fourth byte) to each
 *!  invocation of this routine with the @[svndiff_version] argument.
 */
static void f_read_svndiff_window (INT32 args)
{
  svn_txdelta_window_t *win;
  struct object *stream_obj;
  svn_stream_t *stream;
  apr_pool_t *pool;
  svn_error_t *err;
  INT_TYPE ver;

  get_all_args ("read_svndiff_window", args, "%O%i", &stream_obj, &ver);
  svn_pike_check_stream_arg ("read_svndiff_window", args, 0, &stream,
			     NULL, &pool);

  THREADS_ALLOW();

  err = svn_txdelta_read_svndiff_window (&win, stream, ver, pool);

  THREADS_DISALLOW();

  if (err) {
    svn_pike_set_error (err);
    apr_pool_destroy (pool);
    pike_throw ();
  } else {
    pop_n_elems (args);  
    if (win)
      push_txwindow (win);
    else
      push_int (0);
    apr_pool_destroy (pool);
  }
}


/*! @decl void skip_svndiff_window( Stdio.File file,		@
 *!				    int svndiff_version )
 *!
 *!  Skip one delta window in svndiff format in the file @[file].  The
 *!  caller must take responsibility for stripping off the four-byte
 *!  'SVN<ver>' header at the beginning of the svndiff document before
 *!  reading or skipping the first window, and must provide the version
 *!  number (the value of the fourth byte) to each invocation of this
 *!  routine with the @[svndiff_version] argument.
 */
static void f_skip_svndiff_window (INT32 args)
{
  struct object *file_obj;
  apr_file_t *file;
  apr_os_file_t osfile;
  apr_pool_t *pool;
  svn_error_t *err;
  INT_TYPE ver;

  get_all_args ("skip_svndiff_window", args, "%O%i", &file_obj, &ver);
  osfile = svn_pike_check_file_arg ("skip_svndiff_window", args, 0);

  pool = svn_pool_create (NULL);

  if((osfile == NO_FILE_SPECIFIED ||
      apr_os_file_put (&file, &osfile, 0, pool))) {
    apr_pool_destroy (pool);
    Pike_error ("Invalid file argument\n");
  }

  THREADS_ALLOW();

  err = svn_txdelta_skip_svndiff_window (file, ver, pool);

  THREADS_DISALLOW();

  if (err)
    svn_pike_set_error (err);

  apr_pool_destroy (pool);

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


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



/* Initialize Delta submodule */

void svn_pike_init_delta (void)
{
  struct program *delta_program;
  struct svalue prog;
  prog.type = PIKE_T_PROGRAM;
  prog.subtype = 0;

  start_new_program ();

  start_new_program ();
  ADD_STORAGE (struct txwindow_obj);
  set_init_callback(init_txwindow_obj);
  set_exit_callback(exit_txwindow_obj);

  map_variable ("new_string", "string", ID_STATIC,
		OFFSETOF (txwindow_obj, new_string), PIKE_T_STRING);

  ADD_FUNCTION ("create", f_txwindow_create,
		tFunc (tInt tInt tArr (tArr (tOr (tStr, tInt))), tVoid),
		ID_STATIC);
  ADD_FUNCTION2 ("`[]", txwindow_magic_index,
		 tFunc (tStr, tOr (tInt, tArr (tArr (tOr (tStr, tInt))))),
		 ID_STATIC, 0);
  ADD_FUNCTION2 ("`->", txwindow_magic_index,
		 tFunc (tStr, tOr (tInt, tArr (tArr (tOr (tStr, tInt))))),
		 ID_STATIC, 0);

  txwindow_program = end_program ();
  add_program_constant ("DeltaWindow", txwindow_program, 0);

  start_new_program ();
  ADD_STORAGE (struct stream_obj);
  set_init_callback (init_stream_obj);
  set_exit_callback (exit_stream_obj);

  map_variable ("source", "object", ID_STATIC,
		OFFSETOF (stream_obj, source.object), PIKE_T_OBJECT);
  map_variable ("target", "object", ID_STATIC,
		OFFSETOF (stream_obj, target.object), PIKE_T_OBJECT);

  ADD_FUNCTION ("create", f_deltastream_create, tFunc (tObj tObj, tVoid),
		ID_STATIC);
  ADD_FUNCTION ("next_window", f_next_window, tFunc (tNone, tObj), 0);
  ADD_FUNCTION ("md5_digest", f_md5_digest, tFunc (tNone, tStr), 0);

  deltastream_program = end_program();
  add_program_constant ("DeltaStream", deltastream_program, 0);

  start_new_program ();
  ADD_STORAGE (struct txdelta_handler_obj);

  txdelta_handler_func = ADD_FUNCTION ("txdelta_handler", f_txdelta_handler,
				       tFunc (tObj, tVoid), ID_STATIC);

  txdelta_handler_program = end_program();
  add_program_constant ("DeltaHandler", txdelta_handler_program, ID_STATIC);

  start_new_program ();
  prog.u.program = txdelta_handler_program;
  do_inherit(&prog, 0, NULL);
  apply_txdelta_handler_offs = ADD_STORAGE (struct apply_txdelta_handler_obj);
  set_init_callback (init_apply_txdelta_handler_obj);
  set_exit_callback (exit_apply_txdelta_handler_obj);

  ADD_FUNCTION ("create", f_apply_txdelta_handler_create,
		tFunc (tObj tObj tOr (tStr, tVoid), tVoid), ID_STATIC);

  apply_txdelta_handler_program = end_program();
  add_program_constant ("ApplyDeltaHandler", apply_txdelta_handler_program,
			ID_STATIC);

  start_new_program ();
  prog.u.program = txdelta_handler_program;
  do_inherit(&prog, 0, NULL);
  diff_txdelta_handler_offs = ADD_STORAGE (struct diff_txdelta_handler_obj);
  set_init_callback (init_diff_txdelta_handler_obj);
  set_exit_callback (exit_diff_txdelta_handler_obj);

  ADD_FUNCTION ("create", f_diff_txdelta_handler_create,
		tFunc (tObj, tVoid), ID_STATIC);

  diff_txdelta_handler_program = end_program();
  add_program_constant ("DiffDeltaHandler", diff_txdelta_handler_program,
			ID_STATIC);

  start_new_program ();
  ADD_STORAGE (struct deltaeditor_obj);
  set_init_callback(init_deltaeditor_obj);
  set_exit_callback(exit_deltaeditor_obj);

  start_new_program ();
  Pike_compiler->new_program->flags |= PROGRAM_USES_PARENT;
  ADD_STORAGE (struct direditor_obj);
  set_init_callback(init_direditor_obj);
  set_exit_callback(exit_direditor_obj);

  map_variable ("parentdir", "object", ID_STATIC,
		OFFSETOF (direditor_obj, parentdir), PIKE_T_OBJECT);

  ADD_FUNCTION ("create", f_direditor_create, tFunc (tInt, tVoid), ID_STATIC);
  ADD_FUNCTION ("delete_entry", f_delete_entry, tFunc (tStr tInt, tVoid), 0);
  ADD_FUNCTION ("add_directory", f_add_directory,
		tFunc (tStr tOr (tStr, tVoid) tOr (tInt, tVoid), tObj), 0);
  ADD_FUNCTION ("open_directory", f_open_directory,
		tFunc (tStr tOr (tStr, tVoid) tOr (tInt, tVoid), tObj), 0);
  ADD_FUNCTION ("change_dir_prop", f_change_dir_prop,
		tFunc (tStr tStr, tVoid), 0);
  ADD_FUNCTION ("close_directory", f_close_directory, tFunc (tNone, tVoid), 0);
  ADD_FUNCTION ("absent_directory", f_absent_directory,
		tFunc (tStr, tVoid), 0);
  ADD_FUNCTION ("add_file", f_add_file,
		tFunc (tStr tOr (tStr, tVoid) tOr (tInt, tVoid), tObj), 0);
  ADD_FUNCTION ("open_file", f_open_file, tFunc (tStr tInt, tObj), 0);
  ADD_FUNCTION ("absent_file", f_absent_file, tFunc (tStr, tVoid), 0);

  direditor_program = end_program ();
  direditor_ident = add_program_constant ("DirectoryEditor", direditor_program, 0);

  start_new_program ();
  Pike_compiler->new_program->flags |= PROGRAM_USES_PARENT;
  ADD_STORAGE (struct fileeditor_obj);
  set_init_callback(init_fileeditor_obj);
  set_exit_callback(exit_fileeditor_obj);

  map_variable ("parentdir", "object", ID_STATIC,
		OFFSETOF (fileeditor_obj, parentdir), PIKE_T_OBJECT);

  ADD_FUNCTION ("create", f_fileeditor_create, tFunc (tNone, tVoid), ID_STATIC);
  ADD_FUNCTION ("apply_textdelta", f_apply_textdelta,
		tFunc (tOr (tStr, tVoid), tFunc (tObj, tVoid)), 0);
  ADD_FUNCTION ("change_file_prop", f_change_file_prop,
		tFunc (tStr tStr, tVoid), 0);
  ADD_FUNCTION ("close_file", f_close_file, tFunc (tOr (tStr, tVoid), tVoid),
		0);

  start_new_program ();
  Pike_compiler->new_program->flags |= PROGRAM_USES_PARENT;
  prog.u.program = txdelta_handler_program;
  do_inherit(&prog, 0, NULL);

  ADD_FUNCTION ("create", f_fe_txdelta_handler_create,
		tFunc (tOr (tStr, tVoid), tVoid), ID_STATIC);

  fe_txdelta_handler_program = end_program();
  fe_txdelta_handler_ident = add_program_constant ("FeDeltaHandler",
						   fe_txdelta_handler_program,
						   ID_STATIC);

  fileeditor_program = end_program ();
  fileeditor_ident = add_program_constant ("FileEditor", fileeditor_program, 0);

  ADD_FUNCTION ("set_target_revision", f_set_target_revision,
		tFunc (tInt, tVoid), 0);
  ADD_FUNCTION ("open_root", f_open_root, tFunc (tInt, tObj), 0);
  ADD_FUNCTION ("close_edit", f_close_edit, tFunc (tNone, tVoid), 0);
  ADD_FUNCTION ("abort_edit", f_abort_edit, tFunc (tNone, tVoid), 0);

  svn_pike_delta_editor_program = end_program ();
  add_program_constant ("DeltaEditor", svn_pike_delta_editor_program, 0);

  ADD_FUNCTION ("to_svndiff", f_to_svndiff,
		tFunc (tObj, tFunc (tObj, tVoid)), 0);
  ADD_FUNCTION ("apply", f_apply_,
		tFunc (tObj tObj tOr (tStr, tVoid), tFunc (tObj, tVoid)), 0);
  ADD_FUNCTION ("parse_svndiff", f_parse_svndiff,
		tFunc (tFunc (tObj, tVoid) tOr (tInt, tVoid), tObj), 0);
  ADD_FUNCTION ("target_push", f_target_push,
		tFunc (tFunc (tObj, tVoid) tObj, tObj), 0);
  ADD_FUNCTION ("send_string", f_send_string,
		tFunc (tStr tFunc (tObj, tVoid), tVoid), 0);
  ADD_FUNCTION ("send_stream", f_send_stream,
		tFunc (tObj tFunc (tObj, tVoid), tStr), 0);
  ADD_FUNCTION ("send_txstream", f_send_txstream,
		tFunc (tObj tFunc (tObj, tVoid), tVoid), 0);
  ADD_FUNCTION ("read_svndiff_window", f_read_svndiff_window,
		tFunc (tObj tInt, tObj), 0);
  ADD_FUNCTION ("skip_svndiff_window", f_skip_svndiff_window,
		tFunc (tObj tInt, tVoid), 0);

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


  /*! @decl constant SOURCE
   *! @decl constant TARGET
   *! @decl constant NEW
   *!
   *! Opcodes for text deltas, see @[DeltaWindow->ops].
   */
  ADD_INT_CONSTANT ("SOURCE", svn_txdelta_source, 0);
  ADD_INT_CONSTANT ("TARGET", svn_txdelta_target, 0);
  ADD_INT_CONSTANT ("NEW", svn_txdelta_new, 0);

  /*! @decl typedef function(DeltaWindow:void) DeltaWindowHandler
   *!
   *!  A typedef for functions that consume a series of delta windows, for
   *!  use in caller-pushes interfaces.  Such functions will typically
   *!  apply the delta windows to produce some file, or save the windows
   *!  somewhere.  At the end of the delta window stream, the function
   *!  must be called passing zero for the @[DeltaWindow] argument.
   */
  ref_push_type_value (make_pike_type (tFunc (tObj, tVoid)));
  push_constant_text ("DeltaWindowHandler");
  add_constant (Pike_sp[-1].u.string, Pike_sp-2, ID_INLINE);
  pop_stack ();

  delta_program = end_program ();
  add_object_constant ("Delta", clone_object (delta_program, 0), 0);
  free_program (delta_program);
}

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

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