2 * blame.c : entry point for blame RA functions for ra_serf
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
28 #include "svn_pools.h"
32 #include "svn_config.h"
33 #include "svn_delta.h"
35 #include "svn_base64.h"
36 #include "svn_props.h"
38 #include "svn_private_config.h"
40 #include "private/svn_string_private.h"
43 #include "../libsvn_ra/ra_loader.h"
47 * This enum represents the current state of our XML parsing for a REPORT.
49 typedef enum blame_state_e {
50 INITIAL = XML_STATE_INITIAL,
61 typedef struct blame_context_t {
62 /* pool passed to get_file_revs */
65 /* parameters set by our caller */
69 svn_boolean_t include_merged_revisions;
71 /* blame handler and baton */
72 svn_file_rev_handler_t file_rev;
75 /* As we parse each FILE_REV, we collect data in these variables:
76 property changes and new content. STREAM is valid when we're
77 in the TXDELTA state, processing the incoming cdata. */
78 apr_hash_t *rev_props;
79 apr_array_header_t *prop_diffs;
80 apr_pool_t *state_pool; /* put property stuff in here */
88 #define S_ SVN_XML_NAMESPACE
89 static const svn_ra_serf__xml_transition_t blame_ttable[] = {
90 { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
91 FALSE, { NULL }, FALSE },
93 { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
94 FALSE, { "path", "rev", NULL }, TRUE },
96 { FILE_REV, S_, "rev-prop", REV_PROP,
97 TRUE, { "name", "?encoding", NULL }, TRUE },
99 { FILE_REV, S_, "set-prop", SET_PROP,
100 TRUE, { "name", "?encoding", NULL }, TRUE },
102 { FILE_REV, S_, "remove-prop", REMOVE_PROP,
103 FALSE, { "name", NULL }, TRUE },
105 { FILE_REV, S_, "merged-revision", MERGED_REVISION,
106 FALSE, { NULL }, TRUE },
108 { FILE_REV, S_, "txdelta", TXDELTA,
109 FALSE, { NULL }, TRUE },
114 /* Conforms to svn_ra_serf__xml_opened_t */
116 blame_opened(svn_ra_serf__xml_estate_t *xes,
119 const svn_ra_serf__dav_props_t *tag,
120 apr_pool_t *scratch_pool)
122 blame_context_t *blame_ctx = baton;
124 if (entered_state == FILE_REV)
126 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
128 /* Child elements will store properties in these structures. */
129 blame_ctx->rev_props = apr_hash_make(state_pool);
130 blame_ctx->prop_diffs = apr_array_make(state_pool,
131 5, sizeof(svn_prop_t));
132 blame_ctx->state_pool = state_pool;
134 /* Clear this, so we can detect the absence of a TXDELTA. */
135 blame_ctx->stream = NULL;
137 else if (entered_state == TXDELTA)
139 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
140 apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
143 const char *merged_revision;
144 svn_txdelta_window_handler_t txdelta;
148 path = svn_hash_gets(gathered, "path");
149 rev_str = svn_hash_gets(gathered, "rev");
151 SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
152 merged_revision = svn_hash_gets(gathered, "merged-revision");
154 SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
155 path, (svn_revnum_t)rev,
156 blame_ctx->rev_props,
157 merged_revision != NULL,
158 &txdelta, &txdelta_baton,
159 blame_ctx->prop_diffs,
162 blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
163 txdelta, txdelta_baton,
164 TRUE /* error_on_early_close */,
173 /* Conforms to svn_ra_serf__xml_closed_t */
175 blame_closed(svn_ra_serf__xml_estate_t *xes,
178 const svn_string_t *cdata,
180 apr_pool_t *scratch_pool)
182 blame_context_t *blame_ctx = baton;
184 if (leaving_state == FILE_REV)
186 /* Note that we test STREAM, but any pointer is currently invalid.
187 It was closed when left the TXDELTA state. */
188 if (blame_ctx->stream == NULL)
193 path = svn_hash_gets(attrs, "path");
194 rev = svn_hash_gets(attrs, "rev");
196 /* Send a "no content" notification. */
197 SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
198 path, SVN_STR_TO_REV(rev),
199 blame_ctx->rev_props,
200 FALSE /* result_of_merge */,
201 NULL, NULL, /* txdelta / baton */
202 blame_ctx->prop_diffs,
206 else if (leaving_state == MERGED_REVISION)
208 svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
210 else if (leaving_state == TXDELTA)
212 SVN_ERR(svn_stream_close(blame_ctx->stream));
217 const svn_string_t *value;
219 SVN_ERR_ASSERT(leaving_state == REV_PROP
220 || leaving_state == SET_PROP
221 || leaving_state == REMOVE_PROP);
223 name = apr_pstrdup(blame_ctx->state_pool,
224 svn_hash_gets(attrs, "name"));
226 if (leaving_state == REMOVE_PROP)
232 const char *encoding = svn_hash_gets(attrs, "encoding");
234 if (encoding && strcmp(encoding, "base64") == 0)
235 value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
237 value = svn_string_dup(cdata, blame_ctx->state_pool);
240 if (leaving_state == REV_PROP)
242 svn_hash_sets(blame_ctx->rev_props, name, value);
246 svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
257 /* Conforms to svn_ra_serf__xml_cdata_t */
259 blame_cdata(svn_ra_serf__xml_estate_t *xes,
264 apr_pool_t *scratch_pool)
266 blame_context_t *blame_ctx = baton;
268 if (current_state == TXDELTA)
270 SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
271 /* Ignore the returned LEN value. */
278 /* Implements svn_ra_serf__request_body_delegate_t */
280 create_file_revs_body(serf_bucket_t **body_bkt,
282 serf_bucket_alloc_t *alloc,
283 apr_pool_t *pool /* request pool */,
284 apr_pool_t *scratch_pool)
286 serf_bucket_t *buckets;
287 blame_context_t *blame_ctx = baton;
289 buckets = serf_bucket_aggregate_create(alloc);
291 svn_ra_serf__add_open_tag_buckets(buckets, alloc,
292 "S:file-revs-report",
293 "xmlns:S", SVN_XML_NAMESPACE,
296 svn_ra_serf__add_tag_buckets(buckets,
297 "S:start-revision", apr_ltoa(pool, blame_ctx->start),
300 svn_ra_serf__add_tag_buckets(buckets,
301 "S:end-revision", apr_ltoa(pool, blame_ctx->end),
304 if (blame_ctx->include_merged_revisions)
306 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
307 "S:include-merged-revisions", SVN_VA_NULL);
310 svn_ra_serf__add_tag_buckets(buckets,
311 "S:path", blame_ctx->path,
314 svn_ra_serf__add_close_tag_buckets(buckets, alloc,
315 "S:file-revs-report");
322 svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
326 svn_boolean_t include_merged_revisions,
327 svn_file_rev_handler_t rev_handler,
328 void *rev_handler_baton,
331 blame_context_t *blame_ctx;
332 svn_ra_serf__session_t *session = ra_session->priv;
333 svn_ra_serf__handler_t *handler;
334 svn_ra_serf__xml_context_t *xmlctx;
336 svn_revnum_t peg_rev;
338 blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
339 blame_ctx->pool = pool;
340 blame_ctx->path = path;
341 blame_ctx->file_rev = rev_handler;
342 blame_ctx->file_rev_baton = rev_handler_baton;
343 blame_ctx->start = start;
344 blame_ctx->end = end;
345 blame_ctx->include_merged_revisions = include_merged_revisions;
347 /* Since Subversion 1.8 we allow retrieving blames backwards. So we can't
348 just unconditionally use end_rev as the peg revision as before */
354 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
356 NULL /* url */, peg_rev,
359 xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
365 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
367 handler->method = "REPORT";
368 handler->path = req_url;
369 handler->body_type = "text/xml";
370 handler->body_delegate = create_file_revs_body;
371 handler->body_delegate_baton = blame_ctx;
373 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
375 if (handler->sline.code != 200)
376 return svn_error_trace(svn_ra_serf__unexpected_status(handler));