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 {
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 },
115 /* Conforms to svn_ra_serf__xml_opened_t */
117 blame_opened(svn_ra_serf__xml_estate_t *xes,
120 const svn_ra_serf__dav_props_t *tag,
121 apr_pool_t *scratch_pool)
123 blame_context_t *blame_ctx = baton;
125 if (entered_state == FILE_REV)
127 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
129 /* Child elements will store properties in these structures. */
130 blame_ctx->rev_props = apr_hash_make(state_pool);
131 blame_ctx->prop_diffs = apr_array_make(state_pool,
132 5, sizeof(svn_prop_t));
133 blame_ctx->state_pool = state_pool;
135 /* Clear this, so we can detect the absence of a TXDELTA. */
136 blame_ctx->stream = NULL;
138 else if (entered_state == TXDELTA)
140 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
141 apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
144 const char *merged_revision;
145 svn_txdelta_window_handler_t txdelta;
148 path = svn_hash_gets(gathered, "path");
149 rev = svn_hash_gets(gathered, "rev");
150 merged_revision = svn_hash_gets(gathered, "merged-revision");
152 SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
153 path, SVN_STR_TO_REV(rev),
154 blame_ctx->rev_props,
155 merged_revision != NULL,
156 &txdelta, &txdelta_baton,
157 blame_ctx->prop_diffs,
160 blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
161 txdelta, txdelta_baton,
162 TRUE /* error_on_early_close */,
171 /* Conforms to svn_ra_serf__xml_closed_t */
173 blame_closed(svn_ra_serf__xml_estate_t *xes,
176 const svn_string_t *cdata,
178 apr_pool_t *scratch_pool)
180 blame_context_t *blame_ctx = baton;
182 if (leaving_state == FILE_REV)
184 /* Note that we test STREAM, but any pointer is currently invalid.
185 It was closed when left the TXDELTA state. */
186 if (blame_ctx->stream == NULL)
191 path = svn_hash_gets(attrs, "path");
192 rev = svn_hash_gets(attrs, "rev");
194 /* Send a "no content" notification. */
195 SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
196 path, SVN_STR_TO_REV(rev),
197 blame_ctx->rev_props,
198 FALSE /* result_of_merge */,
199 NULL, NULL, /* txdelta / baton */
200 blame_ctx->prop_diffs,
204 else if (leaving_state == MERGED_REVISION)
206 svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
208 else if (leaving_state == TXDELTA)
210 SVN_ERR(svn_stream_close(blame_ctx->stream));
215 const svn_string_t *value;
217 SVN_ERR_ASSERT(leaving_state == REV_PROP
218 || leaving_state == SET_PROP
219 || leaving_state == REMOVE_PROP);
221 name = apr_pstrdup(blame_ctx->state_pool,
222 svn_hash_gets(attrs, "name"));
224 if (leaving_state == REMOVE_PROP)
230 const char *encoding = svn_hash_gets(attrs, "encoding");
232 if (encoding && strcmp(encoding, "base64") == 0)
233 value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
235 value = svn_string_dup(cdata, blame_ctx->state_pool);
238 if (leaving_state == REV_PROP)
240 svn_hash_sets(blame_ctx->rev_props, name, value);
244 svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
255 /* Conforms to svn_ra_serf__xml_cdata_t */
257 blame_cdata(svn_ra_serf__xml_estate_t *xes,
262 apr_pool_t *scratch_pool)
264 blame_context_t *blame_ctx = baton;
266 if (current_state == TXDELTA)
268 SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
269 /* Ignore the returned LEN value. */
276 /* Implements svn_ra_serf__request_body_delegate_t */
278 create_file_revs_body(serf_bucket_t **body_bkt,
280 serf_bucket_alloc_t *alloc,
283 serf_bucket_t *buckets;
284 blame_context_t *blame_ctx = baton;
286 buckets = serf_bucket_aggregate_create(alloc);
288 svn_ra_serf__add_open_tag_buckets(buckets, alloc,
289 "S:file-revs-report",
290 "xmlns:S", SVN_XML_NAMESPACE,
293 svn_ra_serf__add_tag_buckets(buckets,
294 "S:start-revision", apr_ltoa(pool, blame_ctx->start),
297 svn_ra_serf__add_tag_buckets(buckets,
298 "S:end-revision", apr_ltoa(pool, blame_ctx->end),
301 if (blame_ctx->include_merged_revisions)
303 svn_ra_serf__add_tag_buckets(buckets,
304 "S:include-merged-revisions", NULL,
308 svn_ra_serf__add_tag_buckets(buckets,
309 "S:path", blame_ctx->path,
312 svn_ra_serf__add_close_tag_buckets(buckets, alloc,
313 "S:file-revs-report");
320 svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
324 svn_boolean_t include_merged_revisions,
325 svn_file_rev_handler_t rev_handler,
326 void *rev_handler_baton,
329 blame_context_t *blame_ctx;
330 svn_ra_serf__session_t *session = ra_session->priv;
331 svn_ra_serf__handler_t *handler;
332 svn_ra_serf__xml_context_t *xmlctx;
336 blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
337 blame_ctx->pool = pool;
338 blame_ctx->path = path;
339 blame_ctx->file_rev = rev_handler;
340 blame_ctx->file_rev_baton = rev_handler_baton;
341 blame_ctx->start = start;
342 blame_ctx->end = end;
343 blame_ctx->include_merged_revisions = include_merged_revisions;
345 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
346 session, NULL /* conn */,
350 xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
356 handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
358 handler->method = "REPORT";
359 handler->path = req_url;
360 handler->body_type = "text/xml";
361 handler->body_delegate = create_file_revs_body;
362 handler->body_delegate_baton = blame_ctx;
363 handler->conn = session->conns[0];
364 handler->session = session;
366 err = svn_ra_serf__context_run_one(handler, pool);
368 err = svn_error_compose_create(
369 svn_ra_serf__error_on_status(handler->sline.code,
374 return svn_error_trace(err);