2
* log.c : routines for requesting and parsing log reports
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
13
* http://www.apache.org/licenses/LICENSE-2.0
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
21
* ====================================================================
24
#define APR_WANT_STRFUNC
25
#include <apr_want.h> /* for strcmp() */
27
#include <apr_pools.h>
28
#include <apr_tables.h>
29
#include <apr_strings.h>
32
#include "svn_error.h"
33
#include "svn_pools.h"
35
#include "svn_base64.h"
37
#include "svn_props.h"
39
#include "private/svn_dav_protocol.h"
40
#include "../libsvn_ra/ra_loader.h"
48
/* Userdata for the Neon XML element callbacks. */
51
/*WARNING: WANT_CDATA should stay the first element in the baton:
52
svn_ra_neon__xml_collect_cdata() assumes the baton starts with a stringbuf.
54
svn_stringbuf_t *want_cdata;
55
svn_stringbuf_t *cdata;
56
const char *cdata_encoding; /* encoding of CDATA (NULL or "base64") */
58
/* Allocate log message information.
59
* NOTE: this pool may be cleared multiple times as log messages are
64
/* Information about each log item in turn. */
65
svn_log_entry_t *log_entry;
66
/* Place to hold revprop name. */
67
const char *revprop_name;
68
/* pre-1.5 compatibility */
69
svn_boolean_t want_author;
70
svn_boolean_t want_date;
71
svn_boolean_t want_message;
73
/* The current changed path item. */
74
svn_log_changed_path2_t *this_path_item;
76
/* Client's callback, invoked on the above fields when the end of an
78
svn_log_entry_receiver_t receiver;
82
int nest_level; /* used to track mergeinfo nesting levels */
83
int count; /* only incremented when nest_level == 0 */
85
/* If we're in backwards compatibility mode for the svn log --limit
86
stuff, we need to be able to bail out while parsing log messages.
87
The way we do that is returning an error to neon, but we need to
88
be able to tell that the error we returned wasn't actually a
89
problem, so if this is TRUE it means we can safely ignore that
90
error and return success. */
91
svn_boolean_t limit_compat_bailout;
95
/* Prepare LB to start accumulating the next log item, by wiping all
96
* information related to the previous item and clearing the pool in
97
* which they were allocated. Do not touch any stored error, however.
100
reset_log_item(struct log_baton *lb)
102
lb->log_entry->revision = SVN_INVALID_REVNUM;
103
lb->log_entry->revprops = NULL;
104
lb->log_entry->changed_paths = NULL;
105
lb->log_entry->has_children = FALSE;
106
lb->log_entry->changed_paths2 = NULL;
107
lb->log_entry->subtractive_merge = FALSE;
109
svn_pool_clear(lb->subpool);
113
* This implements the `svn_ra_neon__xml_startelm_cb' prototype.
116
log_start_element(int *elem, void *baton, int parent,
117
const char *nspace, const char *name, const char **atts)
119
const char *copyfrom_path, *copyfrom_revstr;
120
svn_revnum_t copyfrom_rev;
121
struct log_baton *lb = baton;
122
static const svn_ra_neon__xml_elm_t log_report_elements[] =
124
{ SVN_XML_NAMESPACE, "log-report", ELEM_log_report, 0 },
125
{ SVN_XML_NAMESPACE, "log-item", ELEM_log_item, 0 },
126
{ SVN_XML_NAMESPACE, "date", ELEM_log_date, SVN_RA_NEON__XML_CDATA },
127
{ SVN_XML_NAMESPACE, "added-path", ELEM_added_path,
128
SVN_RA_NEON__XML_CDATA },
129
{ SVN_XML_NAMESPACE, "deleted-path", ELEM_deleted_path,
130
SVN_RA_NEON__XML_CDATA },
131
{ SVN_XML_NAMESPACE, "modified-path", ELEM_modified_path,
132
SVN_RA_NEON__XML_CDATA },
133
{ SVN_XML_NAMESPACE, "replaced-path", ELEM_replaced_path,
134
SVN_RA_NEON__XML_CDATA },
135
{ SVN_XML_NAMESPACE, "revprop", ELEM_revprop,
136
SVN_RA_NEON__XML_CDATA },
137
{ "DAV:", SVN_DAV__VERSION_NAME, ELEM_version_name,
138
SVN_RA_NEON__XML_CDATA },
139
{ "DAV:", "creator-displayname", ELEM_creator_displayname,
140
SVN_RA_NEON__XML_CDATA },
141
{ "DAV:", "comment", ELEM_comment, SVN_RA_NEON__XML_CDATA },
142
{ SVN_XML_NAMESPACE, "has-children", ELEM_has_children,
143
SVN_RA_NEON__XML_CDATA },
144
{ SVN_XML_NAMESPACE, "subtractive-merge", ELEM_subtractive_merge,
145
SVN_RA_NEON__XML_CDATA },
148
const svn_ra_neon__xml_elm_t *elm
149
= svn_ra_neon__lookup_xml_elem(log_report_elements, nspace, name);
151
*elem = elm ? elm->id : SVN_RA_NEON__XML_DECLINE;
157
case ELEM_creator_displayname:
159
case ELEM_version_name:
160
case ELEM_added_path:
161
case ELEM_replaced_path:
162
case ELEM_deleted_path:
163
case ELEM_modified_path:
166
lb->want_cdata = lb->cdata;
167
svn_stringbuf_setempty(lb->cdata);
168
lb->cdata_encoding = NULL;
170
/* Some tags might contain encoded CDATA. */
171
if ((elm->id == ELEM_comment) ||
172
(elm->id == ELEM_creator_displayname) ||
173
(elm->id == ELEM_log_date) ||
174
(elm->id == ELEM_rev_prop))
176
lb->cdata_encoding = svn_xml_get_attr_value("encoding", atts);
177
if (lb->cdata_encoding)
178
lb->cdata_encoding = apr_pstrdup(lb->subpool, lb->cdata_encoding);
181
/* revprop tags have names. */
182
if (elm->id == ELEM_revprop)
184
const char *revprop_name = svn_xml_get_attr_value("name", atts);
185
if (revprop_name == NULL)
186
return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
187
_("Missing name attr in revprop element"));
188
lb->revprop_name = apr_pstrdup(lb->subpool, revprop_name);
191
case ELEM_has_children:
192
lb->log_entry->has_children = TRUE;
194
case ELEM_subtractive_merge:
195
lb->log_entry->subtractive_merge = TRUE;
199
lb->want_cdata = NULL;
205
case ELEM_added_path:
206
case ELEM_replaced_path:
207
case ELEM_deleted_path:
208
case ELEM_modified_path:
209
lb->this_path_item = svn_log_changed_path2_create(lb->subpool);
210
lb->this_path_item->node_kind = svn_node_kind_from_word(
211
svn_xml_get_attr_value("node-kind", atts));
212
lb->this_path_item->copyfrom_rev = SVN_INVALID_REVNUM;
214
lb->this_path_item->text_modified = svn_tristate__from_word(
215
svn_xml_get_attr_value("text-mods", atts));
216
lb->this_path_item->props_modified = svn_tristate__from_word(
217
svn_xml_get_attr_value("prop-mods", atts));
219
/* See documentation for `svn_repos_node_t' in svn_repos.h,
220
and `svn_log_changed_path_t' in svn_types.h, for more
221
about these action codes. */
222
if ((elm->id == ELEM_added_path) || (elm->id == ELEM_replaced_path))
224
lb->this_path_item->action
225
= (elm->id == ELEM_added_path) ? 'A' : 'R';
226
copyfrom_path = svn_xml_get_attr_value("copyfrom-path", atts);
227
copyfrom_revstr = svn_xml_get_attr_value("copyfrom-rev", atts);
228
if (copyfrom_path && copyfrom_revstr
229
&& (SVN_IS_VALID_REVNUM
230
(copyfrom_rev = SVN_STR_TO_REV(copyfrom_revstr))))
232
lb->this_path_item->copyfrom_path = apr_pstrdup(lb->subpool,
234
lb->this_path_item->copyfrom_rev = copyfrom_rev;
237
else if (elm->id == ELEM_deleted_path)
239
lb->this_path_item->action = 'D';
243
lb->this_path_item->action = 'M';
248
lb->this_path_item = NULL;
255
* Set *DECODED_CDATA to a copy of current CDATA being tracked in LB,
256
* decoded as necessary, and allocated from LB->subpool.
259
maybe_decode_log_cdata(const svn_string_t **decoded_cdata,
260
struct log_baton *lb)
262
if (lb->cdata_encoding)
265
in.data = lb->cdata->data;
266
in.len = lb->cdata->len;
268
/* Check for a known encoding type. This is easy -- there's
270
if (strcmp(lb->cdata_encoding, "base64") != 0)
271
return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
273
*decoded_cdata = svn_base64_decode_string(&in, lb->subpool);
277
*decoded_cdata = svn_string_create_from_buf(lb->cdata, lb->subpool);
286
* This implements the `svn_ra_neon__xml_endelm_cb' prototype.
289
log_end_element(void *baton, int state,
290
const char *nspace, const char *name)
292
struct log_baton *lb = baton;
293
const svn_string_t *decoded_cdata;
296
SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, lb));
300
case ELEM_version_name:
301
lb->log_entry->revision = SVN_STR_TO_REV(lb->cdata->data);
303
case ELEM_creator_displayname:
306
if (! lb->log_entry->revprops)
307
lb->log_entry->revprops = apr_hash_make(lb->subpool);
308
apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
309
APR_HASH_KEY_STRING, decoded_cdata);
315
if (! lb->log_entry->revprops)
316
lb->log_entry->revprops = apr_hash_make(lb->subpool);
317
apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_DATE,
318
APR_HASH_KEY_STRING, decoded_cdata);
321
case ELEM_added_path:
322
case ELEM_replaced_path:
323
case ELEM_deleted_path:
324
case ELEM_modified_path:
326
char *path = apr_pstrdup(lb->subpool, lb->cdata->data);
327
if (! lb->log_entry->changed_paths2)
329
lb->log_entry->changed_paths2 = apr_hash_make(lb->subpool);
330
lb->log_entry->changed_paths = lb->log_entry->changed_paths2;
332
apr_hash_set(lb->log_entry->changed_paths2, path, APR_HASH_KEY_STRING,
337
if (! lb->log_entry->revprops)
338
lb->log_entry->revprops = apr_hash_make(lb->subpool);
339
apr_hash_set(lb->log_entry->revprops, lb->revprop_name,
340
APR_HASH_KEY_STRING, decoded_cdata);
343
if (lb->want_message)
345
if (! lb->log_entry->revprops)
346
lb->log_entry->revprops = apr_hash_make(lb->subpool);
347
apr_hash_set(lb->log_entry->revprops, SVN_PROP_REVISION_LOG,
348
APR_HASH_KEY_STRING, decoded_cdata);
353
/* Compatibility cruft so that we can provide limit functionality
354
even if the server doesn't support it.
356
If we've seen as many log entries as we're going to show just
357
error out of the XML parser so we can avoid having to parse the
358
remaining XML, but set a flag that we will later use to ensure
359
this error will not be shown to the user. */
360
if (lb->limit && (lb->nest_level == 0) && (++lb->count > lb->limit))
362
lb->limit_compat_bailout = TRUE;
363
return svn_error_create(APR_EGENERAL, NULL, NULL);
365
SVN_ERR((*(lb->receiver))(lb->receiver_baton,
368
if (lb->log_entry->has_children)
372
if (! SVN_IS_VALID_REVNUM(lb->log_entry->revision))
374
SVN_ERR_ASSERT(lb->nest_level);
382
/* Stop collecting cdata */
383
lb->want_cdata = NULL;
388
svn_error_t * svn_ra_neon__get_log(svn_ra_session_t *session,
389
const apr_array_header_t *paths,
393
svn_boolean_t discover_changed_paths,
394
svn_boolean_t strict_node_history,
395
svn_boolean_t include_merged_revisions,
396
const apr_array_header_t *revprops,
397
svn_log_entry_receiver_t receiver,
398
void *receiver_baton,
401
/* The Plan: Send a request to the server for a log report.
402
* Somewhere in mod_dav_svn, there will be an implementation, R, of
403
* the `svn_log_entry_receiver_t' function type. Some other
404
* function in mod_dav_svn will use svn_repos_get_logs() to loop R
405
* over the log messages, and the successive invocations of R will
406
* collectively transmit the report back here, where we parse the
407
* report and invoke RECEIVER (which is an entirely separate
408
* instance of `svn_log_entry_receiver_t') on each individual
409
* message in that report.
413
svn_ra_neon__session_t *ras = session->priv;
414
svn_stringbuf_t *request_body = svn_stringbuf_create("", pool);
415
svn_boolean_t want_custom_revprops;
418
const char *bc_relative;
419
const char *final_bc_url;
420
svn_revnum_t use_rev;
423
/* ### todo: I don't understand why the static, file-global
424
variables shared by update and status are called `report_head'
425
and `report_tail', instead of `request_head' and `request_tail'.
426
Maybe Greg can explain? Meanwhile, I'm tentatively using
427
"request_*" for my local vars below. */
429
static const char log_request_head[] = "<S:log-report xmlns:S=\""
430
SVN_XML_NAMESPACE "\">" DEBUG_CR "<S:encode-binary-props/>";
432
static const char log_request_tail[] = "</S:log-report>" DEBUG_CR;
434
/* Construct the request body. */
435
svn_stringbuf_appendcstr(request_body, log_request_head);
436
svn_stringbuf_appendcstr(request_body,
438
"<S:start-revision>%ld"
439
"</S:start-revision>", start));
440
svn_stringbuf_appendcstr(request_body,
442
"<S:end-revision>%ld"
443
"</S:end-revision>", end));
446
svn_stringbuf_appendcstr(request_body,
448
"<S:limit>%d</S:limit>", limit));
451
if (discover_changed_paths)
453
svn_stringbuf_appendcstr(request_body,
455
"<S:discover-changed-paths/>"));
458
if (strict_node_history)
460
svn_stringbuf_appendcstr(request_body,
462
"<S:strict-node-history/>"));
465
if (include_merged_revisions)
467
svn_stringbuf_appendcstr(request_body,
469
"<S:include-merged-revisions/>"));
474
lb.want_author = lb.want_date = lb.want_message = FALSE;
475
want_custom_revprops = FALSE;
476
for (i = 0; i < revprops->nelts; i++)
478
char *name = APR_ARRAY_IDX(revprops, i, char *);
479
svn_stringbuf_appendcstr(request_body, "<S:revprop>");
480
svn_stringbuf_appendcstr(request_body, name);
481
svn_stringbuf_appendcstr(request_body, "</S:revprop>");
482
if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
483
lb.want_author = TRUE;
484
else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
486
else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
487
lb.want_message = TRUE;
489
want_custom_revprops = TRUE;
491
if (revprops->nelts == 0)
493
svn_stringbuf_appendcstr(request_body, "<S:no-revprops/>");
498
svn_stringbuf_appendcstr(request_body,
500
"<S:all-revprops/>"));
501
lb.want_author = lb.want_date = lb.want_message = TRUE;
502
want_custom_revprops = TRUE;
505
if (want_custom_revprops)
507
svn_boolean_t has_log_revprops;
508
SVN_ERR(svn_ra_neon__has_capability(session, &has_log_revprops,
509
SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
510
if (!has_log_revprops)
511
return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
512
_("Server does not support custom revprops"
519
for (i = 0; i < paths->nelts; i++)
521
const char *this_path =
522
apr_xml_quote_string(pool,
523
APR_ARRAY_IDX(paths, i, const char *),
525
svn_stringbuf_appendcstr(request_body, "<S:path>");
526
svn_stringbuf_appendcstr(request_body, this_path);
527
svn_stringbuf_appendcstr(request_body, "</S:path>");
531
svn_stringbuf_appendcstr(request_body, log_request_tail);
533
lb.receiver = receiver;
534
lb.receiver_baton = receiver_baton;
535
lb.subpool = svn_pool_create(pool);
539
lb.limit_compat_bailout = FALSE;
540
lb.cdata = svn_stringbuf_create("", pool);
541
lb.log_entry = svn_log_entry_create(pool);
542
lb.want_cdata = NULL;
543
lb.cdata_encoding = NULL;
546
/* ras's URL may not exist in HEAD, and thus it's not safe to send
547
it as the main argument to the REPORT request; it might cause
548
dav_get_resource() to choke on the server. So instead, we pass a
549
baseline-collection URL, which we get from the largest of the
550
START and END revisions. */
551
use_rev = (start > end) ? start : end;
552
SVN_ERR(svn_ra_neon__get_baseline_info(&bc_url, &bc_relative, NULL, ras,
553
ras->url->data, use_rev, pool));
554
final_bc_url = svn_path_url_add_component2(bc_url, bc_relative, pool);
557
err = svn_ra_neon__parsed_request(ras,
564
svn_ra_neon__xml_collect_cdata,
571
svn_pool_destroy(lb.subpool);
573
if (err && lb.limit_compat_bailout)
575
svn_error_clear(err);