2 * file_revs.c : routines for requesting and parsing file-revs reports
4 * ====================================================================
5 * Copyright (c) 2000-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 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h> /* for strcmp() */
24 #include <apr_tables.h>
25 #include <apr_strings.h>
28 #include "svn_error.h"
29 #include "svn_pools.h"
30 #include "svn_delta.h"
34 #include "svn_base64.h"
35 #include "svn_props.h"
36 #include "../libsvn_ra/ra_loader.h"
37 #include "svn_private_config.h"
46 /* From the caller. */
47 svn_file_rev_handler_t handler
;
50 /* Arguments for the callback. */
53 apr_hash_t
*rev_props
;
54 apr_array_header_t
*prop_diffs
;
56 /* The current property name. */
57 const char *prop_name
;
59 /* Is the current property encoded in base64? */
60 svn_boolean_t base64_prop
;
62 /* Buffer for accumulating CDATA for prop values. */
63 svn_stringbuf_t
*cdata_accum
;
65 /* Stream for writing text delta to. */
68 /* Merged revision flag. */
69 svn_boolean_t merged_rev
;
71 svn_boolean_t had_txdelta
; /* Did we have a txdelta in this file-rev elem? */
76 /* Prepare RB for a new revision. */
78 reset_file_rev(struct report_baton
*rb
)
80 svn_pool_clear(rb
->subpool
);
82 rb
->revnum
= SVN_INVALID_REVNUM
;
83 rb
->rev_props
= apr_hash_make(rb
->subpool
);
84 rb
->prop_diffs
= apr_array_make(rb
->subpool
, 0, sizeof(svn_prop_t
));
85 rb
->merged_rev
= FALSE
;
86 rb
->had_txdelta
= FALSE
;
92 /* Our beloved elements. */
93 static const svn_ra_neon__xml_elm_t report_elements
[] =
95 { SVN_XML_NAMESPACE
, "file-revs-report", ELEM_file_revs_report
, 0 },
96 { SVN_XML_NAMESPACE
, "file-rev", ELEM_file_rev
, 0 },
97 { SVN_XML_NAMESPACE
, "rev-prop", ELEM_rev_prop
, 0 },
98 { SVN_XML_NAMESPACE
, "set-prop", ELEM_set_prop
, 0 },
99 { SVN_XML_NAMESPACE
, "remove-prop", ELEM_remove_prop
, 0 },
100 { SVN_XML_NAMESPACE
, "merged-revision", ELEM_merged_revision
, 0 },
101 { SVN_XML_NAMESPACE
, "txdelta", ELEM_txdelta
, 0 },
106 /* This implements the `svn_ra_neon__startelm_cb_t' prototype. */
108 start_element(int *elem
, void *userdata
, int parent_state
, const char *ns
,
109 const char *ln
, const char **atts
)
111 struct report_baton
*rb
= userdata
;
112 const svn_ra_neon__xml_elm_t
*elm
;
115 elm
= svn_ra_neon__lookup_xml_elem(report_elements
, ns
, ln
);
117 /* Skip unknown elements. */
120 *elem
= NE_XML_DECLINE
;
124 switch (parent_state
)
127 if (elm
->id
!= ELEM_file_revs_report
)
128 return UNEXPECTED_ELEMENT(ns
, ln
);
131 case ELEM_file_revs_report
:
132 if (elm
->id
== ELEM_file_rev
)
135 att
= svn_xml_get_attr_value("rev", atts
);
137 return MISSING_ATTR(ns
, ln
, "rev");
138 rb
->revnum
= SVN_STR_TO_REV(att
);
139 att
= svn_xml_get_attr_value("path", atts
);
141 return MISSING_ATTR(ns
, ln
, "path");
142 rb
->path
= apr_pstrdup(rb
->subpool
, att
);
145 return UNEXPECTED_ELEMENT(ns
, ln
);
149 /* txdelta must be the last elem in file-rev. */
151 return UNEXPECTED_ELEMENT(ns
, ln
);
156 att
= svn_xml_get_attr_value("name", atts
);
158 return MISSING_ATTR(ns
, ln
, "name");
159 rb
->prop_name
= apr_pstrdup(rb
->subpool
, att
);
160 att
= svn_xml_get_attr_value("encoding", atts
);
161 if (att
&& strcmp(att
, "base64") == 0)
162 rb
->base64_prop
= TRUE
;
164 rb
->base64_prop
= FALSE
;
166 case ELEM_remove_prop
:
168 svn_prop_t
*prop
= apr_array_push(rb
->prop_diffs
);
169 att
= svn_xml_get_attr_value("name", atts
);
170 if (!att
|| *att
== '\0')
171 return MISSING_ATTR(ns
, ln
, "name");
172 prop
->name
= apr_pstrdup(rb
->subpool
, att
);
178 svn_txdelta_window_handler_t whandler
= NULL
;
180 /* It's time to call our hanlder. */
181 SVN_ERR(rb
->handler(rb
->handler_baton
, rb
->path
, rb
->revnum
,
182 rb
->rev_props
, rb
->merged_rev
, &whandler
,
183 &wbaton
, rb
->prop_diffs
, rb
->subpool
));
185 rb
->stream
= svn_base64_decode
186 (svn_txdelta_parse_svndiff(whandler
, wbaton
, TRUE
,
187 rb
->subpool
), rb
->subpool
);
190 case ELEM_merged_revision
:
192 rb
->merged_rev
= TRUE
;
196 return UNEXPECTED_ELEMENT(ns
, ln
);
200 return UNEXPECTED_ELEMENT(ns
, ln
);
208 /* Extract the property value from RB, possibly base64-decoding it.
209 Resets RB->cdata_accum. */
210 static const svn_string_t
*
211 extract_propval(struct report_baton
*rb
)
213 const svn_string_t
*v
= svn_string_create_from_buf(rb
->cdata_accum
,
215 svn_stringbuf_setempty(rb
->cdata_accum
);
217 return svn_base64_decode_string(v
, rb
->subpool
);
222 /* This implements the `svn_ra_neon__endelm_cb_t' prototype. */
224 end_element(void *userdata
, int state
,
225 const char *nspace
, const char *elt_name
)
227 struct report_baton
*rb
= userdata
;
232 /* If we had no txdelta, we call the handler here, informing it that
233 there were no content changes. */
234 if (!rb
->had_txdelta
)
235 SVN_ERR(rb
->handler(rb
->handler_baton
, rb
->path
, rb
->revnum
,
236 rb
->rev_props
, rb
->merged_rev
, NULL
, NULL
,
237 rb
->prop_diffs
, rb
->subpool
));
241 apr_hash_set(rb
->rev_props
, rb
->prop_name
, APR_HASH_KEY_STRING
,
242 extract_propval(rb
));
247 svn_prop_t
*prop
= apr_array_push(rb
->prop_diffs
);
248 prop
->name
= rb
->prop_name
;
249 prop
->value
= extract_propval(rb
);
256 SVN_ERR(svn_stream_close(rb
->stream
));
259 rb
->had_txdelta
= TRUE
;
265 /* This implements the `svn_ra_neon__cdata_cb' prototype. */
267 cdata_handler(void *userdata
, int state
,
268 const char *cdata
, size_t len
)
270 struct report_baton
*rb
= userdata
;
276 svn_stringbuf_appendbytes(rb
->cdata_accum
, cdata
, len
);
282 SVN_ERR(svn_stream_write(rb
->stream
, cdata
, &l
));
284 return svn_error_create(SVN_ERR_INCOMPLETE_DATA
, NULL
,
285 _("Failed to write full amount to stream"));
288 /* In other cases, we just ingore the CDATA. */
295 svn_ra_neon__get_file_revs(svn_ra_session_t
*session
,
299 svn_boolean_t include_merged_revisions
,
300 svn_file_rev_handler_t handler
,
304 svn_ra_neon__session_t
*ras
= session
->priv
;
305 svn_stringbuf_t
*request_body
= svn_stringbuf_create("", pool
);
306 svn_string_t bc_url
, bc_relative
;
307 const char *final_bc_url
;
309 struct report_baton rb
;
311 apr_hash_t
*request_headers
= apr_hash_make(pool
);
312 static const char request_head
[]
313 = "<S:file-revs-report xmlns:S=\"" SVN_XML_NAMESPACE
"\">" DEBUG_CR
;
314 static const char request_tail
[]
315 = "</S:file-revs-report>";
317 apr_hash_set(request_headers
, "Accept-Encoding", APR_HASH_KEY_STRING
,
318 "svndiff1;q=0.9,svndiff;q=0.8");
320 /* Construct request body. */
321 svn_stringbuf_appendcstr(request_body
, request_head
);
322 svn_stringbuf_appendcstr(request_body
,
324 "<S:start-revision>%ld"
325 "</S:start-revision>", start
));
326 svn_stringbuf_appendcstr(request_body
,
328 "<S:end-revision>%ld"
329 "</S:end-revision>", end
));
330 if (include_merged_revisions
)
332 svn_stringbuf_appendcstr(request_body
,
334 "<S:include-merged-revisions/>"));
337 svn_stringbuf_appendcstr(request_body
, "<S:path>");
338 svn_stringbuf_appendcstr(request_body
,
339 apr_xml_quote_string(pool
, path
, 0));
340 svn_stringbuf_appendcstr(request_body
, "</S:path>");
341 svn_stringbuf_appendcstr(request_body
, request_tail
);
343 /* Initialize the baton. */
344 rb
.handler
= handler
;
345 rb
.handler_baton
= handler_baton
;
346 rb
.cdata_accum
= svn_stringbuf_create("", pool
);
347 rb
.subpool
= svn_pool_create(pool
);
350 /* ras's URL may not exist in HEAD, and thus it's not safe to send
351 it as the main argument to the REPORT request; it might cause
352 dav_get_resource() to choke on the server. So instead, we pass a
353 baseline-collection URL, which we get from END. */
354 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
, &bc_url
, &bc_relative
, NULL
,
355 ras
, ras
->url
->data
, end
,
357 final_bc_url
= svn_path_url_add_component(bc_url
.data
, bc_relative
.data
,
360 /* Dispatch the request. */
361 err
= svn_ra_neon__parsed_request(ras
, "REPORT", final_bc_url
,
362 request_body
->data
, NULL
, NULL
,
363 start_element
, cdata_handler
, end_element
,
364 &rb
, request_headers
, &http_status
, FALSE
,
367 /* Map status 501: Method Not Implemented to our not implemented error.
368 1.0.x servers and older don't support this report. */
369 if (http_status
== 501)
370 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, err
,
371 _("'get-file-revs' REPORT not implemented"));
375 /* Caller expects at least one revision. Signal error otherwise. */
376 if (!SVN_IS_VALID_REVNUM(rb
.revnum
))
377 return svn_error_create(SVN_ERR_RA_DAV_REQUEST_FAILED
, NULL
,
378 _("The file-revs report didn't contain any "
381 svn_pool_destroy(rb
.subpool
);