]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/blame.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / blame.c
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 \f
46 /*
47  * This enum represents the current state of our XML parsing for a REPORT.
48  */
49 typedef 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
61 typedef 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
89 static 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  */
115 static svn_error_t *
116 blame_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  */
174 static svn_error_t *
175 blame_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  */
258 static svn_error_t *
259 blame_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 */
279 static svn_error_t *
280 create_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
321 svn_error_t *
322 svn_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 }