1/* 2 * blame.c : entry point for blame RA functions for ra_serf 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include <apr_uri.h> 25#include <serf.h> 26 27#include "svn_hash.h" 28#include "svn_pools.h" 29#include "svn_ra.h" 30#include "svn_dav.h" 31#include "svn_xml.h" 32#include "svn_config.h" 33#include "svn_delta.h" 34#include "svn_path.h" 35#include "svn_base64.h" 36#include "svn_props.h" 37 38#include "svn_private_config.h" 39 40#include "private/svn_string_private.h" 41 42#include "ra_serf.h" 43#include "../libsvn_ra/ra_loader.h" 44 45 46/* 47 * This enum represents the current state of our XML parsing for a REPORT. 48 */ 49typedef enum blame_state_e { 50 INITIAL = 0, 51 FILE_REVS_REPORT, 52 FILE_REV, 53 REV_PROP, 54 SET_PROP, 55 REMOVE_PROP, 56 MERGED_REVISION, 57 TXDELTA 58} blame_state_e; 59 60 61typedef struct blame_context_t { 62 /* pool passed to get_file_revs */ 63 apr_pool_t *pool; 64 65 /* parameters set by our caller */ 66 const char *path; 67 svn_revnum_t start; 68 svn_revnum_t end; 69 svn_boolean_t include_merged_revisions; 70 71 /* blame handler and baton */ 72 svn_file_rev_handler_t file_rev; 73 void *file_rev_baton; 74 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 */ 81 82 svn_stream_t *stream; 83 84} blame_context_t; 85 86 87#define D_ "DAV:" 88#define S_ SVN_XML_NAMESPACE 89static const svn_ra_serf__xml_transition_t blame_ttable[] = { 90 { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT, 91 FALSE, { NULL }, FALSE }, 92 93 { FILE_REVS_REPORT, S_, "file-rev", FILE_REV, 94 FALSE, { "path", "rev", NULL }, TRUE }, 95 96 { FILE_REV, S_, "rev-prop", REV_PROP, 97 TRUE, { "name", "?encoding", NULL }, TRUE }, 98 99 { FILE_REV, S_, "set-prop", SET_PROP, 100 TRUE, { "name", "?encoding", NULL }, TRUE }, 101 102 { FILE_REV, S_, "remove-prop", REMOVE_PROP, 103 FALSE, { "name", NULL }, TRUE }, 104 105 { FILE_REV, S_, "merged-revision", MERGED_REVISION, 106 FALSE, { NULL }, TRUE }, 107 108 { FILE_REV, S_, "txdelta", TXDELTA, 109 FALSE, { NULL }, TRUE }, 110 111 { 0 } 112}; 113 114 115/* Conforms to svn_ra_serf__xml_opened_t */ 116static svn_error_t * 117blame_opened(svn_ra_serf__xml_estate_t *xes, 118 void *baton, 119 int entered_state, 120 const svn_ra_serf__dav_props_t *tag, 121 apr_pool_t *scratch_pool) 122{ 123 blame_context_t *blame_ctx = baton; 124 125 if (entered_state == FILE_REV) 126 { 127 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes); 128 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; 134 135 /* Clear this, so we can detect the absence of a TXDELTA. */ 136 blame_ctx->stream = NULL; 137 } 138 else if (entered_state == TXDELTA) 139 { 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); 142 const char *path; 143 const char *rev; 144 const char *merged_revision; 145 svn_txdelta_window_handler_t txdelta; 146 void *txdelta_baton; 147 148 path = svn_hash_gets(gathered, "path"); 149 rev = svn_hash_gets(gathered, "rev"); 150 merged_revision = svn_hash_gets(gathered, "merged-revision"); 151 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, 158 state_pool)); 159 160 blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff( 161 txdelta, txdelta_baton, 162 TRUE /* error_on_early_close */, 163 state_pool), 164 state_pool); 165 } 166 167 return SVN_NO_ERROR; 168} 169 170 171/* Conforms to svn_ra_serf__xml_closed_t */ 172static svn_error_t * 173blame_closed(svn_ra_serf__xml_estate_t *xes, 174 void *baton, 175 int leaving_state, 176 const svn_string_t *cdata, 177 apr_hash_t *attrs, 178 apr_pool_t *scratch_pool) 179{ 180 blame_context_t *blame_ctx = baton; 181 182 if (leaving_state == FILE_REV) 183 { 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) 187 { 188 const char *path; 189 const char *rev; 190 191 path = svn_hash_gets(attrs, "path"); 192 rev = svn_hash_gets(attrs, "rev"); 193 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, 201 scratch_pool)); 202 } 203 } 204 else if (leaving_state == MERGED_REVISION) 205 { 206 svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*"); 207 } 208 else if (leaving_state == TXDELTA) 209 { 210 SVN_ERR(svn_stream_close(blame_ctx->stream)); 211 } 212 else 213 { 214 const char *name; 215 const svn_string_t *value; 216 217 SVN_ERR_ASSERT(leaving_state == REV_PROP 218 || leaving_state == SET_PROP 219 || leaving_state == REMOVE_PROP); 220 221 name = apr_pstrdup(blame_ctx->state_pool, 222 svn_hash_gets(attrs, "name")); 223 224 if (leaving_state == REMOVE_PROP) 225 { 226 value = NULL; 227 } 228 else 229 { 230 const char *encoding = svn_hash_gets(attrs, "encoding"); 231 232 if (encoding && strcmp(encoding, "base64") == 0) 233 value = svn_base64_decode_string(cdata, blame_ctx->state_pool); 234 else 235 value = svn_string_dup(cdata, blame_ctx->state_pool); 236 } 237 238 if (leaving_state == REV_PROP) 239 { 240 svn_hash_sets(blame_ctx->rev_props, name, value); 241 } 242 else 243 { 244 svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs); 245 246 prop->name = name; 247 prop->value = value; 248 } 249 } 250 251 return SVN_NO_ERROR; 252} 253 254 255/* Conforms to svn_ra_serf__xml_cdata_t */ 256static svn_error_t * 257blame_cdata(svn_ra_serf__xml_estate_t *xes, 258 void *baton, 259 int current_state, 260 const char *data, 261 apr_size_t len, 262 apr_pool_t *scratch_pool) 263{ 264 blame_context_t *blame_ctx = baton; 265 266 if (current_state == TXDELTA) 267 { 268 SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len)); 269 /* Ignore the returned LEN value. */ 270 } 271 272 return SVN_NO_ERROR; 273} 274 275 276/* Implements svn_ra_serf__request_body_delegate_t */ 277static svn_error_t * 278create_file_revs_body(serf_bucket_t **body_bkt, 279 void *baton, 280 serf_bucket_alloc_t *alloc, 281 apr_pool_t *pool) 282{ 283 serf_bucket_t *buckets; 284 blame_context_t *blame_ctx = baton; 285 286 buckets = serf_bucket_aggregate_create(alloc); 287 288 svn_ra_serf__add_open_tag_buckets(buckets, alloc, 289 "S:file-revs-report", 290 "xmlns:S", SVN_XML_NAMESPACE, 291 NULL); 292 293 svn_ra_serf__add_tag_buckets(buckets, 294 "S:start-revision", apr_ltoa(pool, blame_ctx->start), 295 alloc); 296 297 svn_ra_serf__add_tag_buckets(buckets, 298 "S:end-revision", apr_ltoa(pool, blame_ctx->end), 299 alloc); 300 301 if (blame_ctx->include_merged_revisions) 302 { 303 svn_ra_serf__add_tag_buckets(buckets, 304 "S:include-merged-revisions", NULL, 305 alloc); 306 } 307 308 svn_ra_serf__add_tag_buckets(buckets, 309 "S:path", blame_ctx->path, 310 alloc); 311 312 svn_ra_serf__add_close_tag_buckets(buckets, alloc, 313 "S:file-revs-report"); 314 315 *body_bkt = buckets; 316 return SVN_NO_ERROR; 317} 318 319svn_error_t * 320svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, 321 const char *path, 322 svn_revnum_t start, 323 svn_revnum_t end, 324 svn_boolean_t include_merged_revisions, 325 svn_file_rev_handler_t rev_handler, 326 void *rev_handler_baton, 327 apr_pool_t *pool) 328{ 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; 333 const char *req_url; 334 svn_error_t *err; 335 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; 344 345 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, 346 session, NULL /* conn */, 347 NULL /* url */, end, 348 pool, pool)); 349 350 xmlctx = svn_ra_serf__xml_context_create(blame_ttable, 351 blame_opened, 352 blame_closed, 353 blame_cdata, 354 blame_ctx, 355 pool); 356 handler = svn_ra_serf__create_expat_handler(xmlctx, pool); 357 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; 365 366 err = svn_ra_serf__context_run_one(handler, pool); 367 368 err = svn_error_compose_create( 369 svn_ra_serf__error_on_status(handler->sline, 370 handler->path, 371 handler->location), 372 err); 373 374 return svn_error_trace(err); 375} 376