/* * blame.c : entry point for blame RA functions for ra_serf * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include #include #include "svn_hash.h" #include "svn_pools.h" #include "svn_ra.h" #include "svn_dav.h" #include "svn_xml.h" #include "svn_config.h" #include "svn_delta.h" #include "svn_path.h" #include "svn_base64.h" #include "svn_props.h" #include "svn_private_config.h" #include "private/svn_string_private.h" #include "ra_serf.h" #include "../libsvn_ra/ra_loader.h" /* * This enum represents the current state of our XML parsing for a REPORT. */ typedef enum blame_state_e { INITIAL = XML_STATE_INITIAL, FILE_REVS_REPORT, FILE_REV, REV_PROP, SET_PROP, REMOVE_PROP, MERGED_REVISION, TXDELTA } blame_state_e; typedef struct blame_context_t { /* pool passed to get_file_revs */ apr_pool_t *pool; /* parameters set by our caller */ const char *path; svn_revnum_t start; svn_revnum_t end; svn_boolean_t include_merged_revisions; /* blame handler and baton */ svn_file_rev_handler_t file_rev; void *file_rev_baton; /* As we parse each FILE_REV, we collect data in these variables: property changes and new content. STREAM is valid when we're in the TXDELTA state, processing the incoming cdata. */ apr_hash_t *rev_props; apr_array_header_t *prop_diffs; apr_pool_t *state_pool; /* put property stuff in here */ svn_stream_t *stream; } blame_context_t; #define D_ "DAV:" #define S_ SVN_XML_NAMESPACE static const svn_ra_serf__xml_transition_t blame_ttable[] = { { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT, FALSE, { NULL }, FALSE }, { FILE_REVS_REPORT, S_, "file-rev", FILE_REV, FALSE, { "path", "rev", NULL }, TRUE }, { FILE_REV, S_, "rev-prop", REV_PROP, TRUE, { "name", "?encoding", NULL }, TRUE }, { FILE_REV, S_, "set-prop", SET_PROP, TRUE, { "name", "?encoding", NULL }, TRUE }, { FILE_REV, S_, "remove-prop", REMOVE_PROP, FALSE, { "name", NULL }, TRUE }, { FILE_REV, S_, "merged-revision", MERGED_REVISION, FALSE, { NULL }, TRUE }, { FILE_REV, S_, "txdelta", TXDELTA, FALSE, { NULL }, TRUE }, { 0 } }; /* Conforms to svn_ra_serf__xml_opened_t */ static svn_error_t * blame_opened(svn_ra_serf__xml_estate_t *xes, void *baton, int entered_state, const svn_ra_serf__dav_props_t *tag, apr_pool_t *scratch_pool) { blame_context_t *blame_ctx = baton; if (entered_state == FILE_REV) { apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes); /* Child elements will store properties in these structures. */ blame_ctx->rev_props = apr_hash_make(state_pool); blame_ctx->prop_diffs = apr_array_make(state_pool, 5, sizeof(svn_prop_t)); blame_ctx->state_pool = state_pool; /* Clear this, so we can detect the absence of a TXDELTA. */ blame_ctx->stream = NULL; } else if (entered_state == TXDELTA) { apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes); apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV); const char *path; const char *rev_str; const char *merged_revision; svn_txdelta_window_handler_t txdelta; void *txdelta_baton; apr_int64_t rev; path = svn_hash_gets(gathered, "path"); rev_str = svn_hash_gets(gathered, "rev"); SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); merged_revision = svn_hash_gets(gathered, "merged-revision"); SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton, path, (svn_revnum_t)rev, blame_ctx->rev_props, merged_revision != NULL, &txdelta, &txdelta_baton, blame_ctx->prop_diffs, state_pool)); blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff( txdelta, txdelta_baton, TRUE /* error_on_early_close */, state_pool), state_pool); } return SVN_NO_ERROR; } /* Conforms to svn_ra_serf__xml_closed_t */ static svn_error_t * blame_closed(svn_ra_serf__xml_estate_t *xes, void *baton, int leaving_state, const svn_string_t *cdata, apr_hash_t *attrs, apr_pool_t *scratch_pool) { blame_context_t *blame_ctx = baton; if (leaving_state == FILE_REV) { /* Note that we test STREAM, but any pointer is currently invalid. It was closed when left the TXDELTA state. */ if (blame_ctx->stream == NULL) { const char *path; const char *rev; path = svn_hash_gets(attrs, "path"); rev = svn_hash_gets(attrs, "rev"); /* Send a "no content" notification. */ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton, path, SVN_STR_TO_REV(rev), blame_ctx->rev_props, FALSE /* result_of_merge */, NULL, NULL, /* txdelta / baton */ blame_ctx->prop_diffs, scratch_pool)); } } else if (leaving_state == MERGED_REVISION) { svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*"); } else if (leaving_state == TXDELTA) { SVN_ERR(svn_stream_close(blame_ctx->stream)); } else { const char *name; const svn_string_t *value; SVN_ERR_ASSERT(leaving_state == REV_PROP || leaving_state == SET_PROP || leaving_state == REMOVE_PROP); name = apr_pstrdup(blame_ctx->state_pool, svn_hash_gets(attrs, "name")); if (leaving_state == REMOVE_PROP) { value = NULL; } else { const char *encoding = svn_hash_gets(attrs, "encoding"); if (encoding && strcmp(encoding, "base64") == 0) value = svn_base64_decode_string(cdata, blame_ctx->state_pool); else value = svn_string_dup(cdata, blame_ctx->state_pool); } if (leaving_state == REV_PROP) { svn_hash_sets(blame_ctx->rev_props, name, value); } else { svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs); prop->name = name; prop->value = value; } } return SVN_NO_ERROR; } /* Conforms to svn_ra_serf__xml_cdata_t */ static svn_error_t * blame_cdata(svn_ra_serf__xml_estate_t *xes, void *baton, int current_state, const char *data, apr_size_t len, apr_pool_t *scratch_pool) { blame_context_t *blame_ctx = baton; if (current_state == TXDELTA) { SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len)); /* Ignore the returned LEN value. */ } return SVN_NO_ERROR; } /* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_file_revs_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, apr_pool_t *pool /* request pool */, apr_pool_t *scratch_pool) { serf_bucket_t *buckets; blame_context_t *blame_ctx = baton; buckets = serf_bucket_aggregate_create(alloc); svn_ra_serf__add_open_tag_buckets(buckets, alloc, "S:file-revs-report", "xmlns:S", SVN_XML_NAMESPACE, SVN_VA_NULL); svn_ra_serf__add_tag_buckets(buckets, "S:start-revision", apr_ltoa(pool, blame_ctx->start), alloc); svn_ra_serf__add_tag_buckets(buckets, "S:end-revision", apr_ltoa(pool, blame_ctx->end), alloc); if (blame_ctx->include_merged_revisions) { svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "S:include-merged-revisions", SVN_VA_NULL); } svn_ra_serf__add_tag_buckets(buckets, "S:path", blame_ctx->path, alloc); svn_ra_serf__add_close_tag_buckets(buckets, alloc, "S:file-revs-report"); *body_bkt = buckets; return SVN_NO_ERROR; } svn_error_t * svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, const char *path, svn_revnum_t start, svn_revnum_t end, svn_boolean_t include_merged_revisions, svn_file_rev_handler_t rev_handler, void *rev_handler_baton, apr_pool_t *pool) { blame_context_t *blame_ctx; svn_ra_serf__session_t *session = ra_session->priv; svn_ra_serf__handler_t *handler; svn_ra_serf__xml_context_t *xmlctx; const char *req_url; svn_revnum_t peg_rev; blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx)); blame_ctx->pool = pool; blame_ctx->path = path; blame_ctx->file_rev = rev_handler; blame_ctx->file_rev_baton = rev_handler_baton; blame_ctx->start = start; blame_ctx->end = end; blame_ctx->include_merged_revisions = include_merged_revisions; /* Since Subversion 1.8 we allow retrieving blames backwards. So we can't just unconditionally use end_rev as the peg revision as before */ if (end > start) peg_rev = end; else peg_rev = start; SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, session, NULL /* url */, peg_rev, pool, pool)); xmlctx = svn_ra_serf__xml_context_create(blame_ttable, blame_opened, blame_closed, blame_cdata, blame_ctx, pool); handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = req_url; handler->body_type = "text/xml"; handler->body_delegate = create_file_revs_body; handler->body_delegate_baton = blame_ctx; SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); if (handler->sline.code != 200) return svn_error_trace(svn_ra_serf__unexpected_status(handler)); return SVN_NO_ERROR; }