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 */
84 svn_ra_serf__session_t *session;
90 #define S_ SVN_XML_NAMESPACE
91 static const svn_ra_serf__xml_transition_t blame_ttable[] = {
92 { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
93 FALSE, { NULL }, FALSE },
95 { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
96 FALSE, { "path", "rev", NULL }, TRUE },
98 { FILE_REV, S_, "rev-prop", REV_PROP,
99 TRUE, { "name", "?encoding", NULL }, TRUE },
101 { FILE_REV, S_, "set-prop", SET_PROP,
102 TRUE, { "name", "?encoding", NULL }, TRUE },
104 { FILE_REV, S_, "remove-prop", REMOVE_PROP,
105 FALSE, { "name", NULL }, TRUE },
107 { FILE_REV, S_, "merged-revision", MERGED_REVISION,
108 FALSE, { NULL }, TRUE },
110 { FILE_REV, S_, "txdelta", TXDELTA,
111 FALSE, { NULL }, TRUE },
116 /* Conforms to svn_ra_serf__xml_opened_t */
118 blame_opened(svn_ra_serf__xml_estate_t *xes,
121 const svn_ra_serf__dav_props_t *tag,
122 apr_pool_t *scratch_pool)
124 blame_context_t *blame_ctx = baton;
126 if (entered_state == FILE_REV)
128 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
130 /* Child elements will store properties in these structures. */
131 blame_ctx->rev_props = apr_hash_make(state_pool);
132 blame_ctx->prop_diffs = apr_array_make(state_pool,
133 5, sizeof(svn_prop_t));
134 blame_ctx->state_pool = state_pool;
136 /* Clear this, so we can detect the absence of a TXDELTA. */
137 blame_ctx->stream = NULL;
139 else if (entered_state == TXDELTA)
141 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
142 apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
145 const char *merged_revision;
146 svn_txdelta_window_handler_t txdelta;
150 path = svn_hash_gets(gathered, "path");
151 rev_str = svn_hash_gets(gathered, "rev");
153 SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
154 merged_revision = svn_hash_gets(gathered, "merged-revision");
156 SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
157 path, (svn_revnum_t)rev,
158 blame_ctx->rev_props,
159 merged_revision != NULL,
160 &txdelta, &txdelta_baton,
161 blame_ctx->prop_diffs,
164 blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
165 txdelta, txdelta_baton,
166 TRUE /* error_on_early_close */,
175 /* Conforms to svn_ra_serf__xml_closed_t */
177 blame_closed(svn_ra_serf__xml_estate_t *xes,
180 const svn_string_t *cdata,
182 apr_pool_t *scratch_pool)
184 blame_context_t *blame_ctx = baton;
186 if (leaving_state == FILE_REV)
188 /* Note that we test STREAM, but any pointer is currently invalid.
189 It was closed when left the TXDELTA state. */
190 if (blame_ctx->stream == NULL)
195 path = svn_hash_gets(attrs, "path");
196 rev = svn_hash_gets(attrs, "rev");
198 /* Send a "no content" notification. */
199 SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
200 path, SVN_STR_TO_REV(rev),
201 blame_ctx->rev_props,
202 FALSE /* result_of_merge */,
203 NULL, NULL, /* txdelta / baton */
204 blame_ctx->prop_diffs,
208 else if (leaving_state == MERGED_REVISION)
210 svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
212 else if (leaving_state == TXDELTA)
214 SVN_ERR(svn_stream_close(blame_ctx->stream));
219 const svn_string_t *value;
221 SVN_ERR_ASSERT(leaving_state == REV_PROP
222 || leaving_state == SET_PROP
223 || leaving_state == REMOVE_PROP);
225 name = apr_pstrdup(blame_ctx->state_pool,
226 svn_hash_gets(attrs, "name"));
228 if (leaving_state == REMOVE_PROP)
234 const char *encoding = svn_hash_gets(attrs, "encoding");
236 if (encoding && strcmp(encoding, "base64") == 0)
237 value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
239 value = svn_string_dup(cdata, blame_ctx->state_pool);
242 if (leaving_state == REV_PROP)
244 svn_hash_sets(blame_ctx->rev_props, name, value);
248 svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
259 /* Conforms to svn_ra_serf__xml_cdata_t */
261 blame_cdata(svn_ra_serf__xml_estate_t *xes,
266 apr_pool_t *scratch_pool)
268 blame_context_t *blame_ctx = baton;
270 if (current_state == TXDELTA)
272 SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
273 /* Ignore the returned LEN value. */
280 /* Implements svn_ra_serf__request_body_delegate_t */
282 create_file_revs_body(serf_bucket_t **body_bkt,
284 serf_bucket_alloc_t *alloc,
285 apr_pool_t *pool /* request pool */,
286 apr_pool_t *scratch_pool)
288 serf_bucket_t *buckets;
289 blame_context_t *blame_ctx = baton;
291 buckets = serf_bucket_aggregate_create(alloc);
293 svn_ra_serf__add_open_tag_buckets(buckets, alloc,
294 "S:file-revs-report",
295 "xmlns:S", SVN_XML_NAMESPACE,
298 svn_ra_serf__add_tag_buckets(buckets,
299 "S:start-revision", apr_ltoa(pool, blame_ctx->start),
302 svn_ra_serf__add_tag_buckets(buckets,
303 "S:end-revision", apr_ltoa(pool, blame_ctx->end),
306 if (blame_ctx->include_merged_revisions)
308 svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
309 "S:include-merged-revisions", SVN_VA_NULL);
312 svn_ra_serf__add_tag_buckets(buckets,
313 "S:path", blame_ctx->path,
316 svn_ra_serf__add_close_tag_buckets(buckets, alloc,
317 "S:file-revs-report");
323 /* Implements svn_ra_serf__request_header_delegate_t */
325 setup_headers(serf_bucket_t *headers,
327 apr_pool_t *request_pool,
328 apr_pool_t *scratch_pool)
330 blame_context_t *blame_ctx = baton;
332 svn_ra_serf__setup_svndiff_accept_encoding(headers, blame_ctx->session);
338 svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
342 svn_boolean_t include_merged_revisions,
343 svn_file_rev_handler_t rev_handler,
344 void *rev_handler_baton,
347 blame_context_t *blame_ctx;
348 svn_ra_serf__session_t *session = ra_session->priv;
349 svn_ra_serf__handler_t *handler;
350 svn_ra_serf__xml_context_t *xmlctx;
352 svn_revnum_t peg_rev;
354 blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
355 blame_ctx->pool = pool;
356 blame_ctx->path = path;
357 blame_ctx->file_rev = rev_handler;
358 blame_ctx->file_rev_baton = rev_handler_baton;
359 blame_ctx->start = start;
360 blame_ctx->end = end;
361 blame_ctx->include_merged_revisions = include_merged_revisions;
362 blame_ctx->session = session;
364 /* Since Subversion 1.8 we allow retrieving blames backwards. So we can't
365 just unconditionally use end_rev as the peg revision as before */
371 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
373 NULL /* url */, peg_rev,
376 xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
382 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
384 handler->method = "REPORT";
385 handler->path = req_url;
386 handler->body_type = "text/xml";
387 handler->body_delegate = create_file_revs_body;
388 handler->body_delegate_baton = blame_ctx;
389 handler->custom_accept_encoding = TRUE;
390 handler->header_delegate = setup_headers;
391 handler->header_delegate_baton = blame_ctx;
393 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
395 if (handler->sline.code != 200)
396 return svn_error_trace(svn_ra_serf__unexpected_status(handler));