]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_serf/blame.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.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   svn_ra_serf__session_t *session;
85
86 } blame_context_t;
87
88
89 #define D_ "DAV:"
90 #define S_ SVN_XML_NAMESPACE
91 static const svn_ra_serf__xml_transition_t blame_ttable[] = {
92   { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
93     FALSE, { NULL }, FALSE },
94
95   { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
96     FALSE, { "path", "rev", NULL }, TRUE },
97
98   { FILE_REV, S_, "rev-prop", REV_PROP,
99     TRUE, { "name", "?encoding", NULL }, TRUE },
100
101   { FILE_REV, S_, "set-prop", SET_PROP,
102     TRUE, { "name", "?encoding", NULL }, TRUE },
103
104   { FILE_REV, S_, "remove-prop", REMOVE_PROP,
105     FALSE, { "name", NULL }, TRUE },
106
107   { FILE_REV, S_, "merged-revision", MERGED_REVISION,
108     FALSE, { NULL }, TRUE },
109
110   { FILE_REV, S_, "txdelta", TXDELTA,
111     FALSE, { NULL }, TRUE },
112
113   { 0 }
114 };
115
116 /* Conforms to svn_ra_serf__xml_opened_t  */
117 static svn_error_t *
118 blame_opened(svn_ra_serf__xml_estate_t *xes,
119              void *baton,
120              int entered_state,
121              const svn_ra_serf__dav_props_t *tag,
122              apr_pool_t *scratch_pool)
123 {
124   blame_context_t *blame_ctx = baton;
125
126   if (entered_state == FILE_REV)
127     {
128       apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
129
130       /* Child elements will store properties in these structures.  */
131       blame_ctx->rev_props = apr_hash_make(state_pool);
132       blame_ctx->prop_diffs = apr_array_make(state_pool,
133                                              5, sizeof(svn_prop_t));
134       blame_ctx->state_pool = state_pool;
135
136       /* Clear this, so we can detect the absence of a TXDELTA.  */
137       blame_ctx->stream = NULL;
138     }
139   else if (entered_state == TXDELTA)
140     {
141       apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
142       apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
143       const char *path;
144       const char *rev_str;
145       const char *merged_revision;
146       svn_txdelta_window_handler_t txdelta;
147       void *txdelta_baton;
148       apr_int64_t rev;
149
150       path = svn_hash_gets(gathered, "path");
151       rev_str = svn_hash_gets(gathered, "rev");
152
153       SVN_ERR(svn_cstring_atoi64(&rev, rev_str));
154       merged_revision = svn_hash_gets(gathered, "merged-revision");
155
156       SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
157                                   path, (svn_revnum_t)rev,
158                                   blame_ctx->rev_props,
159                                   merged_revision != NULL,
160                                   &txdelta, &txdelta_baton,
161                                   blame_ctx->prop_diffs,
162                                   state_pool));
163
164       blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
165                                               txdelta, txdelta_baton,
166                                               TRUE /* error_on_early_close */,
167                                               state_pool),
168                                             state_pool);
169     }
170
171   return SVN_NO_ERROR;
172 }
173
174
175 /* Conforms to svn_ra_serf__xml_closed_t  */
176 static svn_error_t *
177 blame_closed(svn_ra_serf__xml_estate_t *xes,
178              void *baton,
179              int leaving_state,
180              const svn_string_t *cdata,
181              apr_hash_t *attrs,
182              apr_pool_t *scratch_pool)
183 {
184   blame_context_t *blame_ctx = baton;
185
186   if (leaving_state == FILE_REV)
187     {
188       /* Note that we test STREAM, but any pointer is currently invalid.
189          It was closed when left the TXDELTA state.  */
190       if (blame_ctx->stream == NULL)
191         {
192           const char *path;
193           const char *rev;
194
195           path = svn_hash_gets(attrs, "path");
196           rev = svn_hash_gets(attrs, "rev");
197
198           /* Send a "no content" notification.  */
199           SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
200                                       path, SVN_STR_TO_REV(rev),
201                                       blame_ctx->rev_props,
202                                       FALSE /* result_of_merge */,
203                                       NULL, NULL, /* txdelta / baton */
204                                       blame_ctx->prop_diffs,
205                                       scratch_pool));
206         }
207     }
208   else if (leaving_state == MERGED_REVISION)
209     {
210       svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
211     }
212   else if (leaving_state == TXDELTA)
213     {
214       SVN_ERR(svn_stream_close(blame_ctx->stream));
215     }
216   else
217     {
218       const char *name;
219       const svn_string_t *value;
220
221       SVN_ERR_ASSERT(leaving_state == REV_PROP
222                      || leaving_state == SET_PROP
223                      || leaving_state == REMOVE_PROP);
224
225       name = apr_pstrdup(blame_ctx->state_pool,
226                          svn_hash_gets(attrs, "name"));
227
228       if (leaving_state == REMOVE_PROP)
229         {
230           value = NULL;
231         }
232       else
233         {
234           const char *encoding = svn_hash_gets(attrs, "encoding");
235
236           if (encoding && strcmp(encoding, "base64") == 0)
237             value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
238           else
239             value = svn_string_dup(cdata, blame_ctx->state_pool);
240         }
241
242       if (leaving_state == REV_PROP)
243         {
244           svn_hash_sets(blame_ctx->rev_props, name, value);
245         }
246       else
247         {
248           svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
249
250           prop->name = name;
251           prop->value = value;
252         }
253     }
254
255   return SVN_NO_ERROR;
256 }
257
258
259 /* Conforms to svn_ra_serf__xml_cdata_t  */
260 static svn_error_t *
261 blame_cdata(svn_ra_serf__xml_estate_t *xes,
262             void *baton,
263             int current_state,
264             const char *data,
265             apr_size_t len,
266             apr_pool_t *scratch_pool)
267 {
268   blame_context_t *blame_ctx = baton;
269
270   if (current_state == TXDELTA)
271     {
272       SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
273       /* Ignore the returned LEN value.  */
274     }
275
276   return SVN_NO_ERROR;
277 }
278
279
280 /* Implements svn_ra_serf__request_body_delegate_t */
281 static svn_error_t *
282 create_file_revs_body(serf_bucket_t **body_bkt,
283                       void *baton,
284                       serf_bucket_alloc_t *alloc,
285                       apr_pool_t *pool /* request pool */,
286                       apr_pool_t *scratch_pool)
287 {
288   serf_bucket_t *buckets;
289   blame_context_t *blame_ctx = baton;
290
291   buckets = serf_bucket_aggregate_create(alloc);
292
293   svn_ra_serf__add_open_tag_buckets(buckets, alloc,
294                                     "S:file-revs-report",
295                                     "xmlns:S", SVN_XML_NAMESPACE,
296                                     SVN_VA_NULL);
297
298   svn_ra_serf__add_tag_buckets(buckets,
299                                "S:start-revision", apr_ltoa(pool, blame_ctx->start),
300                                alloc);
301
302   svn_ra_serf__add_tag_buckets(buckets,
303                                "S:end-revision", apr_ltoa(pool, blame_ctx->end),
304                                alloc);
305
306   if (blame_ctx->include_merged_revisions)
307     {
308       svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
309                                          "S:include-merged-revisions", SVN_VA_NULL);
310     }
311
312   svn_ra_serf__add_tag_buckets(buckets,
313                                "S:path", blame_ctx->path,
314                                alloc);
315
316   svn_ra_serf__add_close_tag_buckets(buckets, alloc,
317                                      "S:file-revs-report");
318
319   *body_bkt = buckets;
320   return SVN_NO_ERROR;
321 }
322
323 /* Implements svn_ra_serf__request_header_delegate_t */
324 static svn_error_t *
325 setup_headers(serf_bucket_t *headers,
326               void *baton,
327               apr_pool_t *request_pool,
328               apr_pool_t *scratch_pool)
329 {
330   blame_context_t *blame_ctx = baton;
331
332   svn_ra_serf__setup_svndiff_accept_encoding(headers, blame_ctx->session);
333
334   return SVN_NO_ERROR;
335 }
336
337 svn_error_t *
338 svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
339                            const char *path,
340                            svn_revnum_t start,
341                            svn_revnum_t end,
342                            svn_boolean_t include_merged_revisions,
343                            svn_file_rev_handler_t rev_handler,
344                            void *rev_handler_baton,
345                            apr_pool_t *pool)
346 {
347   blame_context_t *blame_ctx;
348   svn_ra_serf__session_t *session = ra_session->priv;
349   svn_ra_serf__handler_t *handler;
350   svn_ra_serf__xml_context_t *xmlctx;
351   const char *req_url;
352   svn_revnum_t peg_rev;
353
354   blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
355   blame_ctx->pool = pool;
356   blame_ctx->path = path;
357   blame_ctx->file_rev = rev_handler;
358   blame_ctx->file_rev_baton = rev_handler_baton;
359   blame_ctx->start = start;
360   blame_ctx->end = end;
361   blame_ctx->include_merged_revisions = include_merged_revisions;
362   blame_ctx->session = session;
363
364   /* Since Subversion 1.8 we allow retrieving blames backwards. So we can't
365      just unconditionally use end_rev as the peg revision as before */
366   if (end > start)
367     peg_rev = end;
368   else
369     peg_rev = start;
370
371   SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
372                                       session,
373                                       NULL /* url */, peg_rev,
374                                       pool, pool));
375
376   xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
377                                            blame_opened,
378                                            blame_closed,
379                                            blame_cdata,
380                                            blame_ctx,
381                                            pool);
382   handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
383
384   handler->method = "REPORT";
385   handler->path = req_url;
386   handler->body_type = "text/xml";
387   handler->body_delegate = create_file_revs_body;
388   handler->body_delegate_baton = blame_ctx;
389   handler->custom_accept_encoding = TRUE;
390   handler->header_delegate = setup_headers;
391   handler->header_delegate_baton = blame_ctx;
392
393   SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
394
395   if (handler->sline.code != 200)
396     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
397
398   return SVN_NO_ERROR;
399 }