blame.c revision 299742
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 = XML_STATE_INITIAL, 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/* Conforms to svn_ra_serf__xml_opened_t */ 115static svn_error_t * 116blame_opened(svn_ra_serf__xml_estate_t *xes, 117 void *baton, 118 int entered_state, 119 const svn_ra_serf__dav_props_t *tag, 120 apr_pool_t *scratch_pool) 121{ 122 blame_context_t *blame_ctx = baton; 123 124 if (entered_state == FILE_REV) 125 { 126 apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes); 127 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; 133 134 /* Clear this, so we can detect the absence of a TXDELTA. */ 135 blame_ctx->stream = NULL; 136 } 137 else if (entered_state == TXDELTA) 138 { 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); 141 const char *path; 142 const char *rev_str; 143 const char *merged_revision; 144 svn_txdelta_window_handler_t txdelta; 145 void *txdelta_baton; 146 apr_int64_t rev; 147 148 path = svn_hash_gets(gathered, "path"); 149 rev_str = svn_hash_gets(gathered, "rev"); 150 151 SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); 152 merged_revision = svn_hash_gets(gathered, "merged-revision"); 153 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, 160 state_pool)); 161 162 blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff( 163 txdelta, txdelta_baton, 164 TRUE /* error_on_early_close */, 165 state_pool), 166 state_pool); 167 } 168 169 return SVN_NO_ERROR; 170} 171 172 173/* Conforms to svn_ra_serf__xml_closed_t */ 174static svn_error_t * 175blame_closed(svn_ra_serf__xml_estate_t *xes, 176 void *baton, 177 int leaving_state, 178 const svn_string_t *cdata, 179 apr_hash_t *attrs, 180 apr_pool_t *scratch_pool) 181{ 182 blame_context_t *blame_ctx = baton; 183 184 if (leaving_state == FILE_REV) 185 { 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) 189 { 190 const char *path; 191 const char *rev; 192 193 path = svn_hash_gets(attrs, "path"); 194 rev = svn_hash_gets(attrs, "rev"); 195 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, 203 scratch_pool)); 204 } 205 } 206 else if (leaving_state == MERGED_REVISION) 207 { 208 svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*"); 209 } 210 else if (leaving_state == TXDELTA) 211 { 212 SVN_ERR(svn_stream_close(blame_ctx->stream)); 213 } 214 else 215 { 216 const char *name; 217 const svn_string_t *value; 218 219 SVN_ERR_ASSERT(leaving_state == REV_PROP 220 || leaving_state == SET_PROP 221 || leaving_state == REMOVE_PROP); 222 223 name = apr_pstrdup(blame_ctx->state_pool, 224 svn_hash_gets(attrs, "name")); 225 226 if (leaving_state == REMOVE_PROP) 227 { 228 value = NULL; 229 } 230 else 231 { 232 const char *encoding = svn_hash_gets(attrs, "encoding"); 233 234 if (encoding && strcmp(encoding, "base64") == 0) 235 value = svn_base64_decode_string(cdata, blame_ctx->state_pool); 236 else 237 value = svn_string_dup(cdata, blame_ctx->state_pool); 238 } 239 240 if (leaving_state == REV_PROP) 241 { 242 svn_hash_sets(blame_ctx->rev_props, name, value); 243 } 244 else 245 { 246 svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs); 247 248 prop->name = name; 249 prop->value = value; 250 } 251 } 252 253 return SVN_NO_ERROR; 254} 255 256 257/* Conforms to svn_ra_serf__xml_cdata_t */ 258static svn_error_t * 259blame_cdata(svn_ra_serf__xml_estate_t *xes, 260 void *baton, 261 int current_state, 262 const char *data, 263 apr_size_t len, 264 apr_pool_t *scratch_pool) 265{ 266 blame_context_t *blame_ctx = baton; 267 268 if (current_state == TXDELTA) 269 { 270 SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len)); 271 /* Ignore the returned LEN value. */ 272 } 273 274 return SVN_NO_ERROR; 275} 276 277 278/* Implements svn_ra_serf__request_body_delegate_t */ 279static svn_error_t * 280create_file_revs_body(serf_bucket_t **body_bkt, 281 void *baton, 282 serf_bucket_alloc_t *alloc, 283 apr_pool_t *pool /* request pool */, 284 apr_pool_t *scratch_pool) 285{ 286 serf_bucket_t *buckets; 287 blame_context_t *blame_ctx = baton; 288 289 buckets = serf_bucket_aggregate_create(alloc); 290 291 svn_ra_serf__add_open_tag_buckets(buckets, alloc, 292 "S:file-revs-report", 293 "xmlns:S", SVN_XML_NAMESPACE, 294 SVN_VA_NULL); 295 296 svn_ra_serf__add_tag_buckets(buckets, 297 "S:start-revision", apr_ltoa(pool, blame_ctx->start), 298 alloc); 299 300 svn_ra_serf__add_tag_buckets(buckets, 301 "S:end-revision", apr_ltoa(pool, blame_ctx->end), 302 alloc); 303 304 if (blame_ctx->include_merged_revisions) 305 { 306 svn_ra_serf__add_empty_tag_buckets(buckets, alloc, 307 "S:include-merged-revisions", SVN_VA_NULL); 308 } 309 310 svn_ra_serf__add_tag_buckets(buckets, 311 "S:path", blame_ctx->path, 312 alloc); 313 314 svn_ra_serf__add_close_tag_buckets(buckets, alloc, 315 "S:file-revs-report"); 316 317 *body_bkt = buckets; 318 return SVN_NO_ERROR; 319} 320 321svn_error_t * 322svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, 323 const char *path, 324 svn_revnum_t start, 325 svn_revnum_t end, 326 svn_boolean_t include_merged_revisions, 327 svn_file_rev_handler_t rev_handler, 328 void *rev_handler_baton, 329 apr_pool_t *pool) 330{ 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; 335 const char *req_url; 336 svn_revnum_t peg_rev; 337 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; 346 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 */ 349 if (end > start) 350 peg_rev = end; 351 else 352 peg_rev = start; 353 354 SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, 355 session, 356 NULL /* url */, peg_rev, 357 pool, pool)); 358 359 xmlctx = svn_ra_serf__xml_context_create(blame_ttable, 360 blame_opened, 361 blame_closed, 362 blame_cdata, 363 blame_ctx, 364 pool); 365 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); 366 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; 372 373 SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 374 375 if (handler->sline.code != 200) 376 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 377 378 return SVN_NO_ERROR; 379} 380