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"
23 #include "../libsvn_ra/ra_loader.h"
28 /* The underlying editor and baton we're replaying into. */
29 const svn_delta_editor_t
*editor
;
32 /* Parent pool for the whole reply. */
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. */
42 /* Variables required to decode and apply our svndiff data off the wire. */
43 svn_txdelta_window_handler_t whandler
;
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
;
59 #define TOP_DIR(rb) (APR_ARRAY_IDX((rb)->dirs, (rb)->dirs->nelts - 1, \
62 /* Info about a given directory we've seen. */
67 apr_pool_t
*file_pool
;
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
);
76 di
->path
= apr_pstrdup(pool
, path
);
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 },
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
);
110 *elem
= NE_XML_DECLINE
;
114 if (parent_state
== ELEM_root
)
116 /* If we're at the root of the tree, the element has to be the editor
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
);
130 case ELEM_target_revision
:
132 const char *crev
= svn_xml_get_attr_value("rev", atts
);
134 return MISSING_ATTR(nspace
, elt_name
, "rev");
136 return rb
->editor
->set_target_revision(rb
->edit_baton
,
137 SVN_STR_TO_REV(crev
),
144 const char *crev
= svn_xml_get_attr_value("rev", atts
);
147 return MISSING_ATTR(nspace
, elt_name
, "rev");
150 apr_pool_t
*subpool
= svn_pool_create(rb
->pool
);
152 SVN_ERR(rb
->editor
->open_root(rb
->edit_baton
,
153 SVN_STR_TO_REV(crev
), subpool
,
155 push_dir(rb
, dir_baton
, "", subpool
);
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
);
166 return MISSING_ATTR(nspace
, elt_name
, "name");
168 return MISSING_ATTR(nspace
, elt_name
, "rev");
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
));
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
);
186 return MISSING_ATTR(nspace
, elt_name
, "name");
189 dir_item_t
*parent
= &TOP_DIR(rb
);
190 apr_pool_t
*subpool
= svn_pool_create(parent
->pool
);
195 rev
= SVN_STR_TO_REV(crev
);
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",
207 crev
= svn_xml_get_attr_value("copyfrom-rev", atts
);
210 rev
= SVN_STR_TO_REV(crev
);
212 rev
= SVN_INVALID_REVNUM
;
214 SVN_ERR(rb
->editor
->add_directory(name
, parent
->baton
,
221 push_dir(rb
, dir_baton
, name
, subpool
);
229 const char *path
= svn_xml_get_attr_value("name", atts
);
232 dir_item_t
*parent
= &TOP_DIR(rb
);
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
);
245 rev
= SVN_STR_TO_REV(crev
);
247 rev
= SVN_INVALID_REVNUM
;
249 SVN_ERR(rb
->editor
->add_file(path
, parent
->baton
, cpath
, rev
,
250 parent
->file_pool
, &rb
->file_baton
));
254 const char *crev
= svn_xml_get_attr_value("rev", atts
);
257 rev
= SVN_STR_TO_REV(crev
);
259 rev
= SVN_INVALID_REVNUM
;
261 SVN_ERR(rb
->editor
->open_file(path
, parent
->baton
, rev
,
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"));
276 const char *checksum
= svn_xml_get_attr_value("checksum", atts
);
278 SVN_ERR(rb
->editor
->apply_textdelta(rb
->file_baton
,
280 TOP_DIR(rb
).file_pool
,
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
);
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"));
300 const char *checksum
= svn_xml_get_attr_value("checksum", atts
);
302 SVN_ERR(rb
->editor
->close_file(rb
->file_baton
,
304 TOP_DIR(rb
).file_pool
));
305 rb
->file_baton
= NULL
;
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 "
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
);
327 case ELEM_change_file_prop
:
328 case ELEM_change_dir_prop
:
330 const char *name
= svn_xml_get_attr_value("name", atts
);
333 return MISSING_ATTR(nspace
, elt_name
, "name");
336 svn_pool_clear(rb
->prop_pool
);
338 if (svn_xml_get_attr_value("del", atts
))
339 rb
->prop_accum
= NULL
;
341 rb
->prop_accum
= svn_stringbuf_create("", rb
->prop_pool
);
343 rb
->prop_name
= apr_pstrdup(rb
->prop_pool
, name
);
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
);
367 case ELEM_editor_report
:
369 svn_pool_destroy(APR_ARRAY_IDX(rb
->dirs
, 0, dir_item_t
).pool
);
374 case ELEM_apply_textdelta
:
375 SVN_ERR(svn_stream_close(rb
->base64_decoder
));
378 rb
->whandler_baton
= NULL
;
379 rb
->svndiff_decoder
= NULL
;
380 rb
->base64_decoder
= NULL
;
383 case ELEM_change_file_prop
:
384 case ELEM_change_dir_prop
:
386 const svn_string_t
*decoded_value
;
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
);
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
,
405 SVN_ERR(rb
->editor
->change_file_prop(rb
->file_baton
,
408 TOP_DIR(rb
).file_pool
));
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
;
427 case ELEM_apply_textdelta
:
428 SVN_ERR(svn_stream_write(rb
->base64_decoder
, cdata
, &nlen
));
430 return svn_error_createf
431 (SVN_ERR_STREAM_UNEXPECTED_EOF
, NULL
,
432 _("Error writing stream: unexpected EOF"));
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"));
441 svn_stringbuf_appendbytes(rb
->prop_accum
, cdata
, len
);
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
,
457 svn_ra_neon__session_t
*ras
= session
->priv
;
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
));
472 rb
.edit_baton
= edit_baton
;
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
,
484 NULL
, /* extra headers */
485 NULL
, /* status code */
486 FALSE
, /* spool response */
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
,
501 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, NULL
, NULL
);