
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, ©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<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 */