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 */
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 *
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)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 *
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)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 *
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)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 *
create_file_revs_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)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 *
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)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 }
380