1251881Speter/* 2251881Speter * blame.c : entry point for blame RA functions for ra_serf 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter#include <apr_uri.h> 25251881Speter#include <serf.h> 26251881Speter 27251881Speter#include "svn_hash.h" 28251881Speter#include "svn_pools.h" 29251881Speter#include "svn_ra.h" 30251881Speter#include "svn_dav.h" 31251881Speter#include "svn_xml.h" 32251881Speter#include "svn_config.h" 33251881Speter#include "svn_delta.h" 34251881Speter#include "svn_path.h" 35251881Speter#include "svn_base64.h" 36251881Speter#include "svn_props.h" 37251881Speter 38251881Speter#include "svn_private_config.h" 39251881Speter 40251881Speter#include "private/svn_string_private.h" 41251881Speter 42251881Speter#include "ra_serf.h" 43251881Speter#include "../libsvn_ra/ra_loader.h" 44251881Speter 45251881Speter 46251881Speter/* 47251881Speter * This enum represents the current state of our XML parsing for a REPORT. 48251881Speter */ 49251881Spetertypedef enum blame_state_e { 50299742Sdim INITIAL = XML_STATE_INITIAL, 51251881Speter FILE_REVS_REPORT, 52251881Speter FILE_REV, 53251881Speter REV_PROP, 54251881Speter SET_PROP, 55251881Speter REMOVE_PROP, 56251881Speter MERGED_REVISION, 57251881Speter TXDELTA 58251881Speter} blame_state_e; 59251881Speter 60251881Speter 61251881Spetertypedef struct blame_context_t { 62251881Speter /* pool passed to get_file_revs */ 63251881Speter apr_pool_t *pool; 64251881Speter 65251881Speter /* parameters set by our caller */ 66251881Speter const char *path; 67251881Speter svn_revnum_t start; 68251881Speter svn_revnum_t end; 69251881Speter svn_boolean_t include_merged_revisions; 70251881Speter 71251881Speter /* blame handler and baton */ 72251881Speter svn_file_rev_handler_t file_rev; 73251881Speter void *file_rev_baton; 74251881Speter 75251881Speter /* As we parse each FILE_REV, we collect data in these variables: 76251881Speter property changes and new content. STREAM is valid when we're 77251881Speter in the TXDELTA state, processing the incoming cdata. */ 78251881Speter apr_hash_t *rev_props; 79251881Speter apr_array_header_t *prop_diffs; 80251881Speter apr_pool_t *state_pool; /* put property stuff in here */ 81251881Speter 82251881Speter svn_stream_t *stream; 83251881Speter 84251881Speter} blame_context_t; 85251881Speter 86251881Speter 87251881Speter#define D_ "DAV:" 88251881Speter#define S_ SVN_XML_NAMESPACE 89251881Speterstatic const svn_ra_serf__xml_transition_t blame_ttable[] = { 90251881Speter { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT, 91251881Speter FALSE, { NULL }, FALSE }, 92251881Speter 93251881Speter { FILE_REVS_REPORT, S_, "file-rev", FILE_REV, 94251881Speter FALSE, { "path", "rev", NULL }, TRUE }, 95251881Speter 96251881Speter { FILE_REV, S_, "rev-prop", REV_PROP, 97251881Speter TRUE, { "name", "?encoding", NULL }, TRUE }, 98251881Speter 99251881Speter { FILE_REV, S_, "set-prop", SET_PROP, 100251881Speter TRUE, { "name", "?encoding", NULL }, TRUE }, 101251881Speter 102251881Speter { FILE_REV, S_, "remove-prop", REMOVE_PROP, 103251881Speter FALSE, { "name", NULL }, TRUE }, 104251881Speter 105251881Speter { FILE_REV, S_, "merged-revision", MERGED_REVISION, 106251881Speter FALSE, { NULL }, TRUE }, 107251881Speter 108251881Speter { FILE_REV, S_, "txdelta", TXDELTA, 109251881Speter FALSE, { NULL }, TRUE }, 110251881Speter 111251881Speter { 0 } 112251881Speter}; 113251881Speter 114251881Speter/* Conforms to svn_ra_serf__xml_opened_t */ 115251881Speterstatic svn_error_t * 116251881Speterblame_opened(svn_ra_serf__xml_estate_t *xes, 117251881Speter void *baton, 118251881Speter int entered_state, 119251881Speter const svn_ra_serf__dav_props_t *tag, 120251881Speter apr_pool_t *scratch_pool) 121251881Speter{ 122251881Speter blame_context_t *blame_ctx = baton; 123251881Speter 124251881Speter if (entered_state == FILE_REV) 125251881Speter { 126251881Speter apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes); 127251881Speter 128251881Speter /* Child elements will store properties in these structures. */ 129251881Speter blame_ctx->rev_props = apr_hash_make(state_pool); 130251881Speter blame_ctx->prop_diffs = apr_array_make(state_pool, 131251881Speter 5, sizeof(svn_prop_t)); 132251881Speter blame_ctx->state_pool = state_pool; 133251881Speter 134251881Speter /* Clear this, so we can detect the absence of a TXDELTA. */ 135251881Speter blame_ctx->stream = NULL; 136251881Speter } 137251881Speter else if (entered_state == TXDELTA) 138251881Speter { 139251881Speter apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes); 140251881Speter apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV); 141251881Speter const char *path; 142299742Sdim const char *rev_str; 143251881Speter const char *merged_revision; 144251881Speter svn_txdelta_window_handler_t txdelta; 145251881Speter void *txdelta_baton; 146299742Sdim apr_int64_t rev; 147251881Speter 148251881Speter path = svn_hash_gets(gathered, "path"); 149299742Sdim rev_str = svn_hash_gets(gathered, "rev"); 150299742Sdim 151299742Sdim SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); 152251881Speter merged_revision = svn_hash_gets(gathered, "merged-revision"); 153251881Speter 154251881Speter SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton, 155299742Sdim path, (svn_revnum_t)rev, 156251881Speter blame_ctx->rev_props, 157251881Speter merged_revision != NULL, 158251881Speter &txdelta, &txdelta_baton, 159251881Speter blame_ctx->prop_diffs, 160251881Speter state_pool)); 161251881Speter 162251881Speter blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff( 163251881Speter txdelta, txdelta_baton, 164251881Speter TRUE /* error_on_early_close */, 165251881Speter state_pool), 166251881Speter state_pool); 167251881Speter } 168251881Speter 169251881Speter return SVN_NO_ERROR; 170251881Speter} 171251881Speter 172251881Speter 173251881Speter/* Conforms to svn_ra_serf__xml_closed_t */ 174251881Speterstatic svn_error_t * 175251881Speterblame_closed(svn_ra_serf__xml_estate_t *xes, 176251881Speter void *baton, 177251881Speter int leaving_state, 178251881Speter const svn_string_t *cdata, 179251881Speter apr_hash_t *attrs, 180251881Speter apr_pool_t *scratch_pool) 181251881Speter{ 182251881Speter blame_context_t *blame_ctx = baton; 183251881Speter 184251881Speter if (leaving_state == FILE_REV) 185251881Speter { 186251881Speter /* Note that we test STREAM, but any pointer is currently invalid. 187251881Speter It was closed when left the TXDELTA state. */ 188251881Speter if (blame_ctx->stream == NULL) 189251881Speter { 190251881Speter const char *path; 191251881Speter const char *rev; 192251881Speter 193251881Speter path = svn_hash_gets(attrs, "path"); 194251881Speter rev = svn_hash_gets(attrs, "rev"); 195251881Speter 196251881Speter /* Send a "no content" notification. */ 197251881Speter SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton, 198251881Speter path, SVN_STR_TO_REV(rev), 199251881Speter blame_ctx->rev_props, 200251881Speter FALSE /* result_of_merge */, 201251881Speter NULL, NULL, /* txdelta / baton */ 202251881Speter blame_ctx->prop_diffs, 203251881Speter scratch_pool)); 204251881Speter } 205251881Speter } 206251881Speter else if (leaving_state == MERGED_REVISION) 207251881Speter { 208251881Speter svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*"); 209251881Speter } 210251881Speter else if (leaving_state == TXDELTA) 211251881Speter { 212251881Speter SVN_ERR(svn_stream_close(blame_ctx->stream)); 213251881Speter } 214251881Speter else 215251881Speter { 216251881Speter const char *name; 217251881Speter const svn_string_t *value; 218251881Speter 219251881Speter SVN_ERR_ASSERT(leaving_state == REV_PROP 220251881Speter || leaving_state == SET_PROP 221251881Speter || leaving_state == REMOVE_PROP); 222251881Speter 223251881Speter name = apr_pstrdup(blame_ctx->state_pool, 224251881Speter svn_hash_gets(attrs, "name")); 225251881Speter 226251881Speter if (leaving_state == REMOVE_PROP) 227251881Speter { 228251881Speter value = NULL; 229251881Speter } 230251881Speter else 231251881Speter { 232251881Speter const char *encoding = svn_hash_gets(attrs, "encoding"); 233251881Speter 234251881Speter if (encoding && strcmp(encoding, "base64") == 0) 235251881Speter value = svn_base64_decode_string(cdata, blame_ctx->state_pool); 236251881Speter else 237251881Speter value = svn_string_dup(cdata, blame_ctx->state_pool); 238251881Speter } 239251881Speter 240251881Speter if (leaving_state == REV_PROP) 241251881Speter { 242251881Speter svn_hash_sets(blame_ctx->rev_props, name, value); 243251881Speter } 244251881Speter else 245251881Speter { 246251881Speter svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs); 247251881Speter 248251881Speter prop->name = name; 249251881Speter prop->value = value; 250251881Speter } 251251881Speter } 252251881Speter 253251881Speter return SVN_NO_ERROR; 254251881Speter} 255251881Speter 256251881Speter 257251881Speter/* Conforms to svn_ra_serf__xml_cdata_t */ 258251881Speterstatic svn_error_t * 259251881Speterblame_cdata(svn_ra_serf__xml_estate_t *xes, 260251881Speter void *baton, 261251881Speter int current_state, 262251881Speter const char *data, 263251881Speter apr_size_t len, 264251881Speter apr_pool_t *scratch_pool) 265251881Speter{ 266251881Speter blame_context_t *blame_ctx = baton; 267251881Speter 268251881Speter if (current_state == TXDELTA) 269251881Speter { 270251881Speter SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len)); 271251881Speter /* Ignore the returned LEN value. */ 272251881Speter } 273251881Speter 274251881Speter return SVN_NO_ERROR; 275251881Speter} 276251881Speter 277251881Speter 278251881Speter/* Implements svn_ra_serf__request_body_delegate_t */ 279251881Speterstatic svn_error_t * 280251881Spetercreate_file_revs_body(serf_bucket_t **body_bkt, 281251881Speter void *baton, 282251881Speter serf_bucket_alloc_t *alloc, 283299742Sdim apr_pool_t *pool /* request pool */, 284299742Sdim apr_pool_t *scratch_pool) 285251881Speter{ 286251881Speter serf_bucket_t *buckets; 287251881Speter blame_context_t *blame_ctx = baton; 288251881Speter 289251881Speter buckets = serf_bucket_aggregate_create(alloc); 290251881Speter 291251881Speter svn_ra_serf__add_open_tag_buckets(buckets, alloc, 292251881Speter "S:file-revs-report", 293251881Speter "xmlns:S", SVN_XML_NAMESPACE, 294299742Sdim SVN_VA_NULL); 295251881Speter 296251881Speter svn_ra_serf__add_tag_buckets(buckets, 297251881Speter "S:start-revision", apr_ltoa(pool, blame_ctx->start), 298251881Speter alloc); 299251881Speter 300251881Speter svn_ra_serf__add_tag_buckets(buckets, 301251881Speter "S:end-revision", apr_ltoa(pool, blame_ctx->end), 302251881Speter alloc); 303251881Speter 304251881Speter if (blame_ctx->include_merged_revisions) 305251881Speter { 306299742Sdim svn_ra_serf__add_empty_tag_buckets(buckets, alloc, 307299742Sdim "S:include-merged-revisions", SVN_VA_NULL); 308251881Speter } 309251881Speter 310251881Speter svn_ra_serf__add_tag_buckets(buckets, 311251881Speter "S:path", blame_ctx->path, 312251881Speter alloc); 313251881Speter 314251881Speter svn_ra_serf__add_close_tag_buckets(buckets, alloc, 315251881Speter "S:file-revs-report"); 316251881Speter 317251881Speter *body_bkt = buckets; 318251881Speter return SVN_NO_ERROR; 319251881Speter} 320251881Speter 321251881Spetersvn_error_t * 322251881Spetersvn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, 323251881Speter const char *path, 324251881Speter svn_revnum_t start, 325251881Speter svn_revnum_t end, 326251881Speter svn_boolean_t include_merged_revisions, 327251881Speter svn_file_rev_handler_t rev_handler, 328251881Speter void *rev_handler_baton, 329251881Speter apr_pool_t *pool) 330251881Speter{ 331251881Speter blame_context_t *blame_ctx; 332251881Speter svn_ra_serf__session_t *session = ra_session->priv; 333251881Speter svn_ra_serf__handler_t *handler; 334251881Speter svn_ra_serf__xml_context_t *xmlctx; 335251881Speter const char *req_url; 336299742Sdim svn_revnum_t peg_rev; 337251881Speter 338251881Speter blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx)); 339251881Speter blame_ctx->pool = pool; 340251881Speter blame_ctx->path = path; 341251881Speter blame_ctx->file_rev = rev_handler; 342251881Speter blame_ctx->file_rev_baton = rev_handler_baton; 343251881Speter blame_ctx->start = start; 344251881Speter blame_ctx->end = end; 345251881Speter blame_ctx->include_merged_revisions = include_merged_revisions; 346251881Speter 347299742Sdim /* Since Subversion 1.8 we allow retrieving blames backwards. So we can't 348299742Sdim just unconditionally use end_rev as the peg revision as before */ 349299742Sdim if (end > start) 350299742Sdim peg_rev = end; 351299742Sdim else 352299742Sdim peg_rev = start; 353299742Sdim 354251881Speter SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, 355299742Sdim session, 356299742Sdim NULL /* url */, peg_rev, 357251881Speter pool, pool)); 358251881Speter 359251881Speter xmlctx = svn_ra_serf__xml_context_create(blame_ttable, 360251881Speter blame_opened, 361251881Speter blame_closed, 362251881Speter blame_cdata, 363251881Speter blame_ctx, 364251881Speter pool); 365299742Sdim handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); 366251881Speter 367251881Speter handler->method = "REPORT"; 368251881Speter handler->path = req_url; 369251881Speter handler->body_type = "text/xml"; 370251881Speter handler->body_delegate = create_file_revs_body; 371251881Speter handler->body_delegate_baton = blame_ctx; 372251881Speter 373299742Sdim SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 374251881Speter 375299742Sdim if (handler->sline.code != 200) 376299742Sdim return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 377251881Speter 378299742Sdim return SVN_NO_ERROR; 379251881Speter} 380