1 | /* |
---|
2 | * blame.c : entry point for blame RA functions for ra_serf |
---|
3 | * |
---|
4 | * ==================================================================== |
---|
5 | * Copyright (c) 2006-2007 CollabNet. All rights reserved. |
---|
6 | * |
---|
7 | * This software is licensed as described in the file COPYING, which |
---|
8 | * you should have received as part of this distribution. The terms |
---|
9 | * are also available at http://subversion.tigris.org/license-1.html. |
---|
10 | * If newer versions of this license are posted there, you may use a |
---|
11 | * newer version instead, at your option. |
---|
12 | * |
---|
13 | * This software consists of voluntary contributions made by many |
---|
14 | * individuals. For exact contribution history, see the revision |
---|
15 | * history and logs, available at http://subversion.tigris.org/. |
---|
16 | * ==================================================================== |
---|
17 | */ |
---|
18 | |
---|
19 | #include <apr_uri.h> |
---|
20 | |
---|
21 | #include <expat.h> |
---|
22 | |
---|
23 | #include <serf.h> |
---|
24 | |
---|
25 | #include "svn_pools.h" |
---|
26 | #include "svn_ra.h" |
---|
27 | #include "svn_dav.h" |
---|
28 | #include "svn_xml.h" |
---|
29 | #include "svn_config.h" |
---|
30 | #include "svn_delta.h" |
---|
31 | #include "svn_version.h" |
---|
32 | #include "svn_path.h" |
---|
33 | #include "svn_base64.h" |
---|
34 | #include "svn_props.h" |
---|
35 | |
---|
36 | #include "svn_private_config.h" |
---|
37 | |
---|
38 | #include "ra_serf.h" |
---|
39 | #include "../libsvn_ra/ra_loader.h" |
---|
40 | |
---|
41 | |
---|
42 | /* |
---|
43 | * This enum represents the current state of our XML parsing for a REPORT. |
---|
44 | */ |
---|
45 | typedef enum { |
---|
46 | NONE = 0, |
---|
47 | FILE_REVS_REPORT, |
---|
48 | FILE_REV, |
---|
49 | REV_PROP, |
---|
50 | SET_PROP, |
---|
51 | REMOVE_PROP, |
---|
52 | MERGED_REVISION, |
---|
53 | TXDELTA, |
---|
54 | } blame_state_e; |
---|
55 | |
---|
56 | typedef struct { |
---|
57 | /* Current pool. */ |
---|
58 | apr_pool_t *pool; |
---|
59 | |
---|
60 | /* our suspicious file */ |
---|
61 | const char *path; |
---|
62 | |
---|
63 | /* the intended suspect */ |
---|
64 | svn_revnum_t rev; |
---|
65 | |
---|
66 | /* Hashtable of revision properties */ |
---|
67 | apr_hash_t *rev_props; |
---|
68 | |
---|
69 | /* Added and removed properties (svn_prop_t*'s) */ |
---|
70 | apr_array_header_t *prop_diffs; |
---|
71 | |
---|
72 | /* txdelta */ |
---|
73 | svn_txdelta_window_handler_t txdelta; |
---|
74 | void *txdelta_baton; |
---|
75 | |
---|
76 | /* returned txdelta stream */ |
---|
77 | svn_stream_t *stream; |
---|
78 | |
---|
79 | /* Is this property base64-encoded? */ |
---|
80 | svn_boolean_t prop_base64; |
---|
81 | |
---|
82 | /* The currently collected value as we build it up */ |
---|
83 | const char *prop_name; |
---|
84 | const char *prop_attr; |
---|
85 | apr_size_t prop_attr_len; |
---|
86 | |
---|
87 | svn_string_t *prop_string; |
---|
88 | |
---|
89 | /* Merged revision flag */ |
---|
90 | svn_boolean_t merged_revision; |
---|
91 | |
---|
92 | } blame_info_t; |
---|
93 | |
---|
94 | typedef struct { |
---|
95 | /* pool passed to get_file_revs */ |
---|
96 | apr_pool_t *pool; |
---|
97 | |
---|
98 | /* parameters set by our caller */ |
---|
99 | const char *path; |
---|
100 | svn_revnum_t start; |
---|
101 | svn_revnum_t end; |
---|
102 | |
---|
103 | /* are we done? */ |
---|
104 | svn_boolean_t done; |
---|
105 | |
---|
106 | /* blame handler and baton */ |
---|
107 | svn_file_rev_handler_t file_rev; |
---|
108 | void *file_rev_baton; |
---|
109 | } blame_context_t; |
---|
110 | |
---|
111 | |
---|
112 | static blame_info_t * |
---|
113 | push_state(svn_ra_serf__xml_parser_t *parser, |
---|
114 | blame_context_t *blame_ctx, |
---|
115 | blame_state_e state) |
---|
116 | { |
---|
117 | svn_ra_serf__xml_push_state(parser, state); |
---|
118 | |
---|
119 | if (state == FILE_REV) |
---|
120 | { |
---|
121 | blame_info_t *info; |
---|
122 | |
---|
123 | info = apr_palloc(parser->state->pool, sizeof(*info)); |
---|
124 | |
---|
125 | info->pool = parser->state->pool; |
---|
126 | |
---|
127 | info->rev = SVN_INVALID_REVNUM; |
---|
128 | info->path = NULL; |
---|
129 | |
---|
130 | info->rev_props = apr_hash_make(info->pool); |
---|
131 | info->prop_diffs = apr_array_make(info->pool, 0, sizeof(svn_prop_t)); |
---|
132 | |
---|
133 | info->stream = NULL; |
---|
134 | info->merged_revision = FALSE; |
---|
135 | |
---|
136 | parser->state->private = info; |
---|
137 | } |
---|
138 | |
---|
139 | return parser->state->private; |
---|
140 | } |
---|
141 | |
---|
142 | static const svn_string_t * |
---|
143 | create_propval(blame_info_t *info) |
---|
144 | { |
---|
145 | const svn_string_t *s; |
---|
146 | |
---|
147 | if (!info->prop_attr) |
---|
148 | { |
---|
149 | return svn_string_create("", info->pool); |
---|
150 | } |
---|
151 | else |
---|
152 | { |
---|
153 | info->prop_attr = apr_pmemdup(info->pool, info->prop_attr, |
---|
154 | info->prop_attr_len + 1); |
---|
155 | } |
---|
156 | |
---|
157 | /* Include the null term. */ |
---|
158 | s = svn_string_ncreate(info->prop_attr, info->prop_attr_len + 1, info->pool); |
---|
159 | if (info->prop_base64 == TRUE) |
---|
160 | { |
---|
161 | s = svn_base64_decode_string(s, info->pool); |
---|
162 | } |
---|
163 | return s; |
---|
164 | } |
---|
165 | |
---|
166 | static svn_error_t * |
---|
167 | start_blame(svn_ra_serf__xml_parser_t *parser, |
---|
168 | void *userData, |
---|
169 | svn_ra_serf__dav_props_t name, |
---|
170 | const char **attrs) |
---|
171 | { |
---|
172 | blame_context_t *blame_ctx = userData; |
---|
173 | blame_state_e state; |
---|
174 | |
---|
175 | state = parser->state->current_state; |
---|
176 | |
---|
177 | if (state == NONE && strcmp(name.name, "file-revs-report") == 0) |
---|
178 | { |
---|
179 | push_state(parser, blame_ctx, FILE_REVS_REPORT); |
---|
180 | } |
---|
181 | else if (state == FILE_REVS_REPORT && |
---|
182 | strcmp(name.name, "file-rev") == 0) |
---|
183 | { |
---|
184 | blame_info_t *info; |
---|
185 | |
---|
186 | info = push_state(parser, blame_ctx, FILE_REV); |
---|
187 | |
---|
188 | info->path = apr_pstrdup(info->pool, |
---|
189 | svn_xml_get_attr_value("path", attrs)); |
---|
190 | info->rev = SVN_STR_TO_REV(svn_xml_get_attr_value("rev", attrs)); |
---|
191 | } |
---|
192 | else if (state == FILE_REV) |
---|
193 | { |
---|
194 | blame_info_t *info; |
---|
195 | const char *enc; |
---|
196 | |
---|
197 | info = parser->state->private; |
---|
198 | |
---|
199 | if (strcmp(name.name, "rev-prop") == 0) |
---|
200 | { |
---|
201 | push_state(parser, blame_ctx, REV_PROP); |
---|
202 | } |
---|
203 | else if (strcmp(name.name, "set-prop") == 0) |
---|
204 | { |
---|
205 | push_state(parser, blame_ctx, SET_PROP); |
---|
206 | } |
---|
207 | if (strcmp(name.name, "remove-prop") == 0) |
---|
208 | { |
---|
209 | push_state(parser, blame_ctx, REMOVE_PROP); |
---|
210 | } |
---|
211 | else if (strcmp(name.name, "merged-revision") == 0) |
---|
212 | { |
---|
213 | push_state(parser, blame_ctx, MERGED_REVISION); |
---|
214 | } |
---|
215 | else if (strcmp(name.name, "txdelta") == 0) |
---|
216 | { |
---|
217 | SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton, |
---|
218 | info->path, info->rev, |
---|
219 | info->rev_props, info->merged_revision, |
---|
220 | &info->txdelta, &info->txdelta_baton, |
---|
221 | info->prop_diffs, info->pool)); |
---|
222 | |
---|
223 | info->stream = svn_base64_decode |
---|
224 | (svn_txdelta_parse_svndiff(info->txdelta, info->txdelta_baton, |
---|
225 | TRUE, info->pool), info->pool); |
---|
226 | |
---|
227 | push_state(parser, blame_ctx, TXDELTA); |
---|
228 | } |
---|
229 | |
---|
230 | state = parser->state->current_state; |
---|
231 | |
---|
232 | switch (state) |
---|
233 | { |
---|
234 | case REV_PROP: |
---|
235 | case SET_PROP: |
---|
236 | case REMOVE_PROP: |
---|
237 | info->prop_name = apr_pstrdup(info->pool, |
---|
238 | svn_xml_get_attr_value("name", attrs)); |
---|
239 | info->prop_attr = NULL; |
---|
240 | info->prop_attr_len = 0; |
---|
241 | |
---|
242 | enc = svn_xml_get_attr_value("encoding", attrs); |
---|
243 | if (enc && strcmp(enc, "base64") == 0) |
---|
244 | { |
---|
245 | info->prop_base64 = TRUE; |
---|
246 | } |
---|
247 | else |
---|
248 | { |
---|
249 | info->prop_base64 = FALSE; |
---|
250 | } |
---|
251 | break; |
---|
252 | case MERGED_REVISION: |
---|
253 | info->merged_revision = TRUE; |
---|
254 | break; |
---|
255 | default: |
---|
256 | break; |
---|
257 | } |
---|
258 | } |
---|
259 | |
---|
260 | return SVN_NO_ERROR; |
---|
261 | } |
---|
262 | |
---|
263 | static svn_error_t * |
---|
264 | end_blame(svn_ra_serf__xml_parser_t *parser, |
---|
265 | void *userData, |
---|
266 | svn_ra_serf__dav_props_t name) |
---|
267 | { |
---|
268 | blame_context_t *blame_ctx = userData; |
---|
269 | blame_state_e state; |
---|
270 | blame_info_t *info; |
---|
271 | |
---|
272 | state = parser->state->current_state; |
---|
273 | info = parser->state->private; |
---|
274 | |
---|
275 | if (state == NONE) |
---|
276 | { |
---|
277 | return SVN_NO_ERROR; |
---|
278 | } |
---|
279 | |
---|
280 | if (state == FILE_REVS_REPORT && |
---|
281 | strcmp(name.name, "file-revs-report") == 0) |
---|
282 | { |
---|
283 | svn_ra_serf__xml_pop_state(parser); |
---|
284 | } |
---|
285 | else if (state == FILE_REV && |
---|
286 | strcmp(name.name, "file-rev") == 0) |
---|
287 | { |
---|
288 | /* no file changes. */ |
---|
289 | if (!info->stream) |
---|
290 | { |
---|
291 | SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton, |
---|
292 | info->path, info->rev, |
---|
293 | info->rev_props, FALSE, |
---|
294 | NULL, NULL, |
---|
295 | info->prop_diffs, info->pool)); |
---|
296 | } |
---|
297 | svn_ra_serf__xml_pop_state(parser); |
---|
298 | } |
---|
299 | else if (state == REV_PROP && |
---|
300 | strcmp(name.name, "rev-prop") == 0) |
---|
301 | { |
---|
302 | apr_hash_set(info->rev_props, |
---|
303 | info->prop_name, APR_HASH_KEY_STRING, |
---|
304 | create_propval(info)); |
---|
305 | |
---|
306 | svn_ra_serf__xml_pop_state(parser); |
---|
307 | } |
---|
308 | else if ((state == SET_PROP && |
---|
309 | strcmp(name.name, "set-prop") == 0) || |
---|
310 | (state == REMOVE_PROP && |
---|
311 | strcmp(name.name, "remove-prop") == 0)) |
---|
312 | { |
---|
313 | svn_prop_t *prop = apr_array_push(info->prop_diffs); |
---|
314 | prop->name = info->prop_name; |
---|
315 | prop->value = create_propval(info); |
---|
316 | |
---|
317 | svn_ra_serf__xml_pop_state(parser); |
---|
318 | } |
---|
319 | else if (state == MERGED_REVISION && |
---|
320 | strcmp(name.name, "merged-revision") == 0) |
---|
321 | { |
---|
322 | svn_ra_serf__xml_pop_state(parser); |
---|
323 | } |
---|
324 | else if (state == TXDELTA && |
---|
325 | strcmp(name.name, "txdelta") == 0) |
---|
326 | { |
---|
327 | svn_stream_close(info->stream); |
---|
328 | |
---|
329 | svn_ra_serf__xml_pop_state(parser); |
---|
330 | } |
---|
331 | |
---|
332 | return SVN_NO_ERROR; |
---|
333 | } |
---|
334 | |
---|
335 | static svn_error_t * |
---|
336 | cdata_blame(svn_ra_serf__xml_parser_t *parser, |
---|
337 | void *userData, |
---|
338 | const char *data, |
---|
339 | apr_size_t len) |
---|
340 | { |
---|
341 | blame_context_t *blame_ctx = userData; |
---|
342 | blame_state_e state; |
---|
343 | blame_info_t *info; |
---|
344 | |
---|
345 | UNUSED_CTX(blame_ctx); |
---|
346 | |
---|
347 | state = parser->state->current_state; |
---|
348 | info = parser->state->private; |
---|
349 | |
---|
350 | if (state == NONE) |
---|
351 | { |
---|
352 | return SVN_NO_ERROR; |
---|
353 | } |
---|
354 | |
---|
355 | switch (state) |
---|
356 | { |
---|
357 | case REV_PROP: |
---|
358 | case SET_PROP: |
---|
359 | svn_ra_serf__expand_string(&info->prop_attr, &info->prop_attr_len, |
---|
360 | data, len, parser->state->pool); |
---|
361 | break; |
---|
362 | case TXDELTA: |
---|
363 | if (info->stream) |
---|
364 | { |
---|
365 | apr_size_t ret_len; |
---|
366 | |
---|
367 | ret_len = len; |
---|
368 | |
---|
369 | SVN_ERR(svn_stream_write(info->stream, data, &ret_len)); |
---|
370 | } |
---|
371 | break; |
---|
372 | default: |
---|
373 | break; |
---|
374 | } |
---|
375 | |
---|
376 | return SVN_NO_ERROR; |
---|
377 | } |
---|
378 | |
---|
379 | svn_error_t * |
---|
380 | svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, |
---|
381 | const char *path, |
---|
382 | svn_revnum_t start, |
---|
383 | svn_revnum_t end, |
---|
384 | svn_boolean_t include_merged_revisions, |
---|
385 | svn_file_rev_handler_t rev_handler, |
---|
386 | void *rev_handler_baton, |
---|
387 | apr_pool_t *pool) |
---|
388 | { |
---|
389 | blame_context_t *blame_ctx; |
---|
390 | svn_ra_serf__session_t *session = ra_session->priv; |
---|
391 | svn_ra_serf__handler_t *handler; |
---|
392 | svn_ra_serf__xml_parser_t *parser_ctx; |
---|
393 | serf_bucket_t *buckets; |
---|
394 | const char *relative_url, *basecoll_url, *req_url; |
---|
395 | int status_code; |
---|
396 | svn_error_t *err; |
---|
397 | |
---|
398 | blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx)); |
---|
399 | blame_ctx->pool = pool; |
---|
400 | blame_ctx->file_rev = rev_handler; |
---|
401 | blame_ctx->file_rev_baton = rev_handler_baton; |
---|
402 | blame_ctx->start = start; |
---|
403 | blame_ctx->end = end; |
---|
404 | blame_ctx->done = FALSE; |
---|
405 | |
---|
406 | buckets = serf_bucket_aggregate_create(session->bkt_alloc); |
---|
407 | |
---|
408 | svn_ra_serf__add_open_tag_buckets(buckets, session->bkt_alloc, |
---|
409 | "S:file-revs-report", |
---|
410 | "xmlns:S", SVN_XML_NAMESPACE, |
---|
411 | NULL); |
---|
412 | |
---|
413 | svn_ra_serf__add_tag_buckets(buckets, |
---|
414 | "S:start-revision", apr_ltoa(pool, start), |
---|
415 | session->bkt_alloc); |
---|
416 | |
---|
417 | svn_ra_serf__add_tag_buckets(buckets, |
---|
418 | "S:end-revision", apr_ltoa(pool, end), |
---|
419 | session->bkt_alloc); |
---|
420 | |
---|
421 | if (include_merged_revisions) |
---|
422 | { |
---|
423 | svn_ra_serf__add_tag_buckets(buckets, |
---|
424 | "S:include-merged-revisions", NULL, |
---|
425 | session->bkt_alloc); |
---|
426 | } |
---|
427 | |
---|
428 | svn_ra_serf__add_tag_buckets(buckets, |
---|
429 | "S:path", path, |
---|
430 | session->bkt_alloc); |
---|
431 | |
---|
432 | svn_ra_serf__add_close_tag_buckets(buckets, session->bkt_alloc, |
---|
433 | "S:file-revs-report"); |
---|
434 | |
---|
435 | SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, |
---|
436 | session, session->repos_url.path, |
---|
437 | end, NULL, pool)); |
---|
438 | req_url = svn_path_url_add_component(basecoll_url, relative_url, pool); |
---|
439 | |
---|
440 | handler = apr_pcalloc(pool, sizeof(*handler)); |
---|
441 | |
---|
442 | handler->method = "REPORT"; |
---|
443 | handler->path = req_url; |
---|
444 | handler->body_buckets = buckets; |
---|
445 | handler->body_type = "text/xml"; |
---|
446 | handler->conn = session->conns[0]; |
---|
447 | handler->session = session; |
---|
448 | |
---|
449 | parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx)); |
---|
450 | |
---|
451 | parser_ctx->pool = pool; |
---|
452 | parser_ctx->user_data = blame_ctx; |
---|
453 | parser_ctx->start = start_blame; |
---|
454 | parser_ctx->end = end_blame; |
---|
455 | parser_ctx->cdata = cdata_blame; |
---|
456 | parser_ctx->done = &blame_ctx->done; |
---|
457 | parser_ctx->status_code = &status_code; |
---|
458 | |
---|
459 | handler->response_handler = svn_ra_serf__handle_xml_parser; |
---|
460 | handler->response_baton = parser_ctx; |
---|
461 | |
---|
462 | svn_ra_serf__request_create(handler); |
---|
463 | |
---|
464 | err = svn_ra_serf__context_run_wait(&blame_ctx->done, session, pool); |
---|
465 | |
---|
466 | if (parser_ctx->error) |
---|
467 | { |
---|
468 | svn_error_clear(err); |
---|
469 | err = SVN_NO_ERROR; |
---|
470 | SVN_ERR(parser_ctx->error); |
---|
471 | } |
---|
472 | |
---|
473 | SVN_ERR(svn_ra_serf__error_on_status(status_code, handler->path)); |
---|
474 | |
---|
475 | return err; |
---|
476 | } |
---|