In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_neon / replay.c
blob744b0dccb1adfa2586facb8060ff8a33670e4ae8
1 /*
2 * replay.c : routines for replaying revisions
4 * ====================================================================
5 * Copyright (c) 2005-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include "svn_base64.h"
20 #include "svn_pools.h"
21 #include "svn_xml.h"
23 #include "../libsvn_ra/ra_loader.h"
25 #include "ra_neon.h"
27 typedef struct {
28 /* The underlying editor and baton we're replaying into. */
29 const svn_delta_editor_t *editor;
30 void *edit_baton;
32 /* Parent pool for the whole reply. */
33 apr_pool_t *pool;
35 /* Stack of in progress directories, holds dir_item_t objects. */
36 apr_array_header_t *dirs;
38 /* Cached file baton so we can pass it between the add/open file and
39 * apply textdelta portions of the editor drive. */
40 void *file_baton;
42 /* Variables required to decode and apply our svndiff data off the wire. */
43 svn_txdelta_window_handler_t whandler;
44 void *whandler_baton;
45 svn_stream_t *svndiff_decoder;
46 svn_stream_t *base64_decoder;
48 /* A scratch pool used to allocate property data. */
49 apr_pool_t *prop_pool;
51 /* The name of a property that's being modified. */
52 const char *prop_name;
54 /* A stringbuf that holds the contents of a property being changed, if this
55 * is NULL it means that the property is being deleted. */
56 svn_stringbuf_t *prop_accum;
57 } replay_baton_t;
59 #define TOP_DIR(rb) (APR_ARRAY_IDX((rb)->dirs, (rb)->dirs->nelts - 1, \
60 dir_item_t))
62 /* Info about a given directory we've seen. */
63 typedef struct {
64 void *baton;
65 const char *path;
66 apr_pool_t *pool;
67 apr_pool_t *file_pool;
68 } dir_item_t;
70 static void
71 push_dir(replay_baton_t *rb, void *baton, const char *path, apr_pool_t *pool)
73 dir_item_t *di = apr_array_push(rb->dirs);
75 di->baton = baton;
76 di->path = apr_pstrdup(pool, path);
77 di->pool = pool;
78 di->file_pool = svn_pool_create(pool);
81 static const svn_ra_neon__xml_elm_t editor_report_elements[] =
83 { SVN_XML_NAMESPACE, "editor-report", ELEM_editor_report, 0 },
84 { SVN_XML_NAMESPACE, "target-revision", ELEM_target_revision, 0 },
85 { SVN_XML_NAMESPACE, "open-root", ELEM_open_root, 0 },
86 { SVN_XML_NAMESPACE, "delete-entry", ELEM_delete_entry, 0 },
87 { SVN_XML_NAMESPACE, "open-directory", ELEM_open_directory, 0 },
88 { SVN_XML_NAMESPACE, "add-directory", ELEM_add_directory, 0 },
89 { SVN_XML_NAMESPACE, "open-file", ELEM_open_file, 0 },
90 { SVN_XML_NAMESPACE, "add-file", ELEM_add_file, 0 },
91 { SVN_XML_NAMESPACE, "close-file", ELEM_close_file, 0 },
92 { SVN_XML_NAMESPACE, "close-directory", ELEM_close_directory, 0 },
93 { SVN_XML_NAMESPACE, "apply-textdelta", ELEM_apply_textdelta, 0 },
94 { SVN_XML_NAMESPACE, "change-file-prop", ELEM_change_file_prop, 0 },
95 { SVN_XML_NAMESPACE, "change-dir-prop", ELEM_change_dir_prop, 0 },
96 { NULL }
99 static svn_error_t *
100 start_element(int *elem, void *baton, int parent_state, const char *nspace,
101 const char *elt_name, const char **atts)
103 replay_baton_t *rb = baton;
105 const svn_ra_neon__xml_elm_t *elm
106 = svn_ra_neon__lookup_xml_elem(editor_report_elements, nspace, elt_name);
108 if (! elm)
110 *elem = NE_XML_DECLINE;
111 return SVN_NO_ERROR;
114 if (parent_state == ELEM_root)
116 /* If we're at the root of the tree, the element has to be the editor
117 * report itself. */
118 if (elm->id != ELEM_editor_report)
119 return UNEXPECTED_ELEMENT(nspace, elt_name);
121 else if (parent_state != ELEM_editor_report)
123 /* If we're not at the root, our parent has to be the editor report,
124 * since we don't actually nest any elements. */
125 return UNEXPECTED_ELEMENT(nspace, elt_name);
128 switch (elm->id)
130 case ELEM_target_revision:
132 const char *crev = svn_xml_get_attr_value("rev", atts);
133 if (! crev)
134 return MISSING_ATTR(nspace, elt_name, "rev");
135 else
136 return rb->editor->set_target_revision(rb->edit_baton,
137 SVN_STR_TO_REV(crev),
138 rb->pool);
140 break;
142 case ELEM_open_root:
144 const char *crev = svn_xml_get_attr_value("rev", atts);
146 if (! crev)
147 return MISSING_ATTR(nspace, elt_name, "rev");
148 else
150 apr_pool_t *subpool = svn_pool_create(rb->pool);
151 void *dir_baton;
152 SVN_ERR(rb->editor->open_root(rb->edit_baton,
153 SVN_STR_TO_REV(crev), subpool,
154 &dir_baton));
155 push_dir(rb, dir_baton, "", subpool);
158 break;
160 case ELEM_delete_entry:
162 const char *path = svn_xml_get_attr_value("name", atts);
163 const char *crev = svn_xml_get_attr_value("rev", atts);
165 if (! path)
166 return MISSING_ATTR(nspace, elt_name, "name");
167 else if (! crev)
168 return MISSING_ATTR(nspace, elt_name, "rev");
169 else
171 dir_item_t *di = &TOP_DIR(rb);
173 SVN_ERR(rb->editor->delete_entry(path, SVN_STR_TO_REV(crev),
174 di->baton, di->pool));
177 break;
179 case ELEM_open_directory:
180 case ELEM_add_directory:
182 const char *crev = svn_xml_get_attr_value("rev", atts);
183 const char *name = svn_xml_get_attr_value("name", atts);
185 if (! name)
186 return MISSING_ATTR(nspace, elt_name, "name");
187 else
189 dir_item_t *parent = &TOP_DIR(rb);
190 apr_pool_t *subpool = svn_pool_create(parent->pool);
191 svn_revnum_t rev;
192 void *dir_baton;
194 if (crev)
195 rev = SVN_STR_TO_REV(crev);
196 else
197 rev = SVN_INVALID_REVNUM;
199 if (elm->id == ELEM_open_directory)
200 SVN_ERR(rb->editor->open_directory(name, parent->baton,
201 rev, subpool, &dir_baton));
202 else if (elm->id == ELEM_add_directory)
204 const char *cpath = svn_xml_get_attr_value("copyfrom-path",
205 atts);
207 crev = svn_xml_get_attr_value("copyfrom-rev", atts);
209 if (crev)
210 rev = SVN_STR_TO_REV(crev);
211 else
212 rev = SVN_INVALID_REVNUM;
214 SVN_ERR(rb->editor->add_directory(name, parent->baton,
215 cpath, rev, subpool,
216 &dir_baton));
218 else
219 abort();
221 push_dir(rb, dir_baton, name, subpool);
224 break;
226 case ELEM_open_file:
227 case ELEM_add_file:
229 const char *path = svn_xml_get_attr_value("name", atts);
230 svn_revnum_t rev;
232 dir_item_t *parent = &TOP_DIR(rb);
234 if (! path)
235 return MISSING_ATTR(nspace, elt_name, "name");
237 svn_pool_clear(parent->file_pool);
239 if (elm->id == ELEM_add_file)
241 const char *cpath = svn_xml_get_attr_value("copyfrom-path", atts);
242 const char *crev = svn_xml_get_attr_value("copyfrom-rev", atts);
244 if (crev)
245 rev = SVN_STR_TO_REV(crev);
246 else
247 rev = SVN_INVALID_REVNUM;
249 SVN_ERR(rb->editor->add_file(path, parent->baton, cpath, rev,
250 parent->file_pool, &rb->file_baton));
252 else
254 const char *crev = svn_xml_get_attr_value("rev", atts);
256 if (crev)
257 rev = SVN_STR_TO_REV(crev);
258 else
259 rev = SVN_INVALID_REVNUM;
261 SVN_ERR(rb->editor->open_file(path, parent->baton, rev,
262 parent->file_pool,
263 &rb->file_baton));
266 break;
268 case ELEM_apply_textdelta:
269 if (! rb->file_baton)
270 return svn_error_create
271 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
272 _("Got apply-textdelta element without preceding "
273 "add-file or open-file"));
274 else
276 const char *checksum = svn_xml_get_attr_value("checksum", atts);
278 SVN_ERR(rb->editor->apply_textdelta(rb->file_baton,
279 checksum,
280 TOP_DIR(rb).file_pool,
281 &rb->whandler,
282 &rb->whandler_baton));
284 rb->svndiff_decoder = svn_txdelta_parse_svndiff
285 (rb->whandler, rb->whandler_baton,
286 TRUE, TOP_DIR(rb).file_pool);
287 rb->base64_decoder = svn_base64_decode(rb->svndiff_decoder,
288 TOP_DIR(rb).file_pool);
290 break;
292 case ELEM_close_file:
293 if (! rb->file_baton)
294 return svn_error_create
295 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
296 _("Got close-file element without preceding "
297 "add-file or open-file"));
298 else
300 const char *checksum = svn_xml_get_attr_value("checksum", atts);
302 SVN_ERR(rb->editor->close_file(rb->file_baton,
303 checksum,
304 TOP_DIR(rb).file_pool));
305 rb->file_baton = NULL;
307 break;
309 case ELEM_close_directory:
310 if (rb->dirs->nelts == 0)
311 return svn_error_create
312 (SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
313 _("Got close-directory element without ever opening "
314 "a directory"));
315 else
317 dir_item_t *di = &TOP_DIR(rb);
319 SVN_ERR(rb->editor->close_directory(di->baton, di->pool));
321 svn_pool_destroy(di->pool);
323 apr_array_pop(rb->dirs);
325 break;
327 case ELEM_change_file_prop:
328 case ELEM_change_dir_prop:
330 const char *name = svn_xml_get_attr_value("name", atts);
332 if (! name)
333 return MISSING_ATTR(nspace, elt_name, "name");
334 else
336 svn_pool_clear(rb->prop_pool);
338 if (svn_xml_get_attr_value("del", atts))
339 rb->prop_accum = NULL;
340 else
341 rb->prop_accum = svn_stringbuf_create("", rb->prop_pool);
343 rb->prop_name = apr_pstrdup(rb->prop_pool, name);
346 break;
349 *elem = elm->id;
351 return SVN_NO_ERROR;
354 static svn_error_t *
355 end_element(void *baton, int state, const char *nspace, const char *elt_name)
357 replay_baton_t *rb = baton;
359 const svn_ra_neon__xml_elm_t *elm
360 = svn_ra_neon__lookup_xml_elem(editor_report_elements, nspace, elt_name);
362 if (! elm)
363 return SVN_NO_ERROR;
365 switch (elm->id)
367 case ELEM_editor_report:
368 if (rb->dirs->nelts)
369 svn_pool_destroy(APR_ARRAY_IDX(rb->dirs, 0, dir_item_t).pool);
371 return SVN_NO_ERROR;
372 break;
374 case ELEM_apply_textdelta:
375 SVN_ERR(svn_stream_close(rb->base64_decoder));
377 rb->whandler = NULL;
378 rb->whandler_baton = NULL;
379 rb->svndiff_decoder = NULL;
380 rb->base64_decoder = NULL;
381 break;
383 case ELEM_change_file_prop:
384 case ELEM_change_dir_prop:
386 const svn_string_t *decoded_value;
387 svn_string_t prop;
389 if (rb->prop_accum)
391 prop.data = rb->prop_accum->data;
392 prop.len = rb->prop_accum->len;
394 decoded_value = svn_base64_decode_string(&prop, rb->prop_pool);
396 else
397 decoded_value = NULL; /* It's a delete */
399 if (elm->id == ELEM_change_dir_prop)
400 SVN_ERR(rb->editor->change_dir_prop(TOP_DIR(rb).baton,
401 rb->prop_name,
402 decoded_value,
403 TOP_DIR(rb).pool));
404 else
405 SVN_ERR(rb->editor->change_file_prop(rb->file_baton,
406 rb->prop_name,
407 decoded_value,
408 TOP_DIR(rb).file_pool));
410 break;
412 default:
413 break;
416 return SVN_NO_ERROR;
419 static svn_error_t *
420 cdata_handler(void *baton, int state, const char *cdata, size_t len)
422 replay_baton_t *rb = baton;
423 apr_size_t nlen = len;
425 switch (state)
427 case ELEM_apply_textdelta:
428 SVN_ERR(svn_stream_write(rb->base64_decoder, cdata, &nlen));
429 if (nlen != len)
430 return svn_error_createf
431 (SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
432 _("Error writing stream: unexpected EOF"));
433 break;
435 case ELEM_change_dir_prop:
436 case ELEM_change_file_prop:
437 if (! rb->prop_accum)
438 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
439 _("Got cdata content for a prop delete"));
440 else
441 svn_stringbuf_appendbytes(rb->prop_accum, cdata, len);
442 break;
445 return SVN_NO_ERROR;
448 svn_error_t *
449 svn_ra_neon__replay(svn_ra_session_t *session,
450 svn_revnum_t revision,
451 svn_revnum_t low_water_mark,
452 svn_boolean_t send_deltas,
453 const svn_delta_editor_t *editor,
454 void *edit_baton,
455 apr_pool_t *pool)
457 svn_ra_neon__session_t *ras = session->priv;
458 replay_baton_t rb;
460 const char *body
461 = apr_psprintf(pool,
462 "<S:replay-report xmlns:S=\"svn:\">\n"
463 " <S:revision>%ld</S:revision>\n"
464 " <S:low-water-mark>%ld</S:low-water-mark>\n"
465 " <S:send-deltas>%d</S:send-deltas>\n"
466 "</S:replay-report>",
467 revision, low_water_mark, send_deltas);
469 memset(&rb, 0, sizeof(rb));
471 rb.editor = editor;
472 rb.edit_baton = edit_baton;
473 rb.pool = pool;
474 rb.dirs = apr_array_make(pool, 5, sizeof(dir_item_t));
475 rb.prop_pool = svn_pool_create(pool);
476 rb.prop_accum = svn_stringbuf_create("", rb.prop_pool);
478 return svn_ra_neon__parsed_request(ras, "REPORT", ras->url->data, body,
479 NULL, NULL,
480 start_element,
481 cdata_handler,
482 end_element,
483 &rb,
484 NULL, /* extra headers */
485 NULL, /* status code */
486 FALSE, /* spool response */
487 pool);
490 svn_error_t *
491 svn_ra_neon__replay_range(svn_ra_session_t *session,
492 svn_revnum_t start_revision,
493 svn_revnum_t end_revision,
494 svn_revnum_t low_water_mark,
495 svn_boolean_t send_deltas,
496 svn_ra_replay_revstart_callback_t revstart_func,
497 svn_ra_replay_revfinish_callback_t revfinish_func,
498 void *replay_baton,
499 apr_pool_t *pool)
501 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);