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.ZeroMQ 1.1
Public.Template.Mustache 1.0
Public.Protocols.XMPP 1.4
Sql.Provider.jdbc 1.0
Public.Storage.Manta 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.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 
#include 

#include 


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