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