2 * log.c : routines for requesting and parsing log reports
4 * ====================================================================
5 * Copyright (c) 2000-2008 CollabNet. All rights reserved.
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.
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 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h> /* for strcmp() */
24 #include <apr_pools.h>
25 #include <apr_tables.h>
26 #include <apr_strings.h>
29 #include "svn_error.h"
30 #include "svn_pools.h"
34 #include "private/svn_dav_protocol.h"
35 #include "../libsvn_ra/ra_loader.h"
43 /* Userdata for the Neon XML element callbacks. */
46 /*WARNING: WANT_CDATA should stay the first element in the baton:
47 svn_ra_neon__xml_collect_cdata() assumes the baton starts with a stringbuf.
49 svn_stringbuf_t
*want_cdata
;
50 svn_stringbuf_t
*cdata
;
51 /* Allocate log message information.
52 * NOTE: this pool may be cleared multiple times as log messages are
57 /* Information about each log item in turn. */
58 svn_log_entry_t
*log_entry
;
59 /* Place to hold revprop name. */
60 const char *revprop_name
;
61 /* pre-1.5 compatibility */
62 svn_boolean_t want_author
;
63 svn_boolean_t want_date
;
64 svn_boolean_t want_message
;
66 /* The current changed path item. */
67 svn_log_changed_path_t
*this_path_item
;
69 /* Client's callback, invoked on the above fields when the end of an
71 svn_log_entry_receiver_t receiver
;
77 /* If we're in backwards compatibility mode for the svn log --limit
78 stuff, we need to be able to bail out while parsing log messages.
79 The way we do that is returning an error to neon, but we need to
80 be able to tell that the error we returned wasn't actually a
81 problem, so if this is TRUE it means we can safely ignore that
82 error and return success. */
83 svn_boolean_t limit_compat_bailout
;
87 /* Prepare LB to start accumulating the next log item, by wiping all
88 * information related to the previous item and clearing the pool in
89 * which they were allocated. Do not touch any stored error, however.
92 reset_log_item(struct log_baton
*lb
)
94 lb
->log_entry
->revision
= SVN_INVALID_REVNUM
;
95 lb
->log_entry
->revprops
= NULL
;
96 lb
->log_entry
->changed_paths
= NULL
;
97 lb
->log_entry
->has_children
= FALSE
;
99 svn_pool_clear(lb
->subpool
);
104 * This implements the `svn_ra_neon__xml_startelm_cb' prototype.
107 log_start_element(int *elem
, void *baton
, int parent
,
108 const char *nspace
, const char *name
, const char **atts
)
110 const char *copyfrom_path
, *copyfrom_revstr
;
111 svn_revnum_t copyfrom_rev
;
112 struct log_baton
*lb
= baton
;
113 static const svn_ra_neon__xml_elm_t log_report_elements
[] =
115 { SVN_XML_NAMESPACE
, "log-report", ELEM_log_report
, 0 },
116 { SVN_XML_NAMESPACE
, "log-item", ELEM_log_item
, 0 },
117 { SVN_XML_NAMESPACE
, "date", ELEM_log_date
, SVN_RA_NEON__XML_CDATA
},
118 { SVN_XML_NAMESPACE
, "added-path", ELEM_added_path
,
119 SVN_RA_NEON__XML_CDATA
},
120 { SVN_XML_NAMESPACE
, "deleted-path", ELEM_deleted_path
,
121 SVN_RA_NEON__XML_CDATA
},
122 { SVN_XML_NAMESPACE
, "modified-path", ELEM_modified_path
,
123 SVN_RA_NEON__XML_CDATA
},
124 { SVN_XML_NAMESPACE
, "replaced-path", ELEM_replaced_path
,
125 SVN_RA_NEON__XML_CDATA
},
126 { SVN_XML_NAMESPACE
, "revprop", ELEM_revprop
,
127 SVN_RA_NEON__XML_CDATA
},
128 { "DAV:", SVN_DAV__VERSION_NAME
, ELEM_version_name
,
129 SVN_RA_NEON__XML_CDATA
},
130 { "DAV:", "creator-displayname", ELEM_creator_displayname
,
131 SVN_RA_NEON__XML_CDATA
},
132 { "DAV:", "comment", ELEM_comment
, SVN_RA_NEON__XML_CDATA
},
133 { SVN_XML_NAMESPACE
, "has-children", ELEM_has_children
,
134 SVN_RA_NEON__XML_CDATA
},
137 const svn_ra_neon__xml_elm_t
*elm
138 = svn_ra_neon__lookup_xml_elem(log_report_elements
, nspace
, name
);
140 *elem
= elm
? elm
->id
: SVN_RA_NEON__XML_DECLINE
;
146 case ELEM_creator_displayname
:
148 case ELEM_version_name
:
149 case ELEM_added_path
:
150 case ELEM_replaced_path
:
151 case ELEM_deleted_path
:
152 case ELEM_modified_path
:
155 lb
->want_cdata
= lb
->cdata
;
156 svn_stringbuf_setempty(lb
->cdata
);
157 if (elm
->id
== ELEM_revprop
)
159 lb
->revprop_name
= apr_pstrdup(lb
->subpool
,
160 svn_xml_get_attr_value("name",
162 if (lb
->revprop_name
== NULL
)
163 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
164 _("Missing name attr in revprop element"));
167 case ELEM_has_children
:
168 lb
->log_entry
->has_children
= TRUE
;
172 lb
->want_cdata
= NULL
;
178 case ELEM_added_path
:
179 case ELEM_replaced_path
:
180 case ELEM_deleted_path
:
181 case ELEM_modified_path
:
182 lb
->this_path_item
= apr_pcalloc(lb
->subpool
,
183 sizeof(*(lb
->this_path_item
)));
184 lb
->this_path_item
->copyfrom_rev
= SVN_INVALID_REVNUM
;
186 /* See documentation for `svn_repos_node_t' in svn_repos.h,
187 and `svn_log_changed_path_t' in svn_types.h, for more
188 about these action codes. */
189 if ((elm
->id
== ELEM_added_path
) || (elm
->id
== ELEM_replaced_path
))
191 lb
->this_path_item
->action
192 = (elm
->id
== ELEM_added_path
) ? 'A' : 'R';
193 copyfrom_path
= svn_xml_get_attr_value("copyfrom-path", atts
);
194 copyfrom_revstr
= svn_xml_get_attr_value("copyfrom-rev", atts
);
195 if (copyfrom_path
&& copyfrom_revstr
196 && (SVN_IS_VALID_REVNUM
197 (copyfrom_rev
= SVN_STR_TO_REV(copyfrom_revstr
))))
199 lb
->this_path_item
->copyfrom_path
= apr_pstrdup(lb
->subpool
,
201 lb
->this_path_item
->copyfrom_rev
= copyfrom_rev
;
204 else if (elm
->id
== ELEM_deleted_path
)
206 lb
->this_path_item
->action
= 'D';
210 lb
->this_path_item
->action
= 'M';
215 lb
->this_path_item
= NULL
;
223 * This implements the `svn_ra_neon__xml_endelm_cb' prototype.
226 log_end_element(void *baton
, int state
,
227 const char *nspace
, const char *name
)
229 struct log_baton
*lb
= baton
;
233 case ELEM_version_name
:
234 lb
->log_entry
->revision
= SVN_STR_TO_REV(lb
->cdata
->data
);
236 case ELEM_creator_displayname
:
239 if (! lb
->log_entry
->revprops
)
240 lb
->log_entry
->revprops
= apr_hash_make(lb
->subpool
);
241 apr_hash_set(lb
->log_entry
->revprops
, SVN_PROP_REVISION_AUTHOR
,
243 svn_string_create_from_buf(lb
->cdata
, lb
->subpool
));
249 if (! lb
->log_entry
->revprops
)
250 lb
->log_entry
->revprops
= apr_hash_make(lb
->subpool
);
251 apr_hash_set(lb
->log_entry
->revprops
, SVN_PROP_REVISION_DATE
,
253 svn_string_create_from_buf(lb
->cdata
, lb
->subpool
));
256 case ELEM_added_path
:
257 case ELEM_replaced_path
:
258 case ELEM_deleted_path
:
259 case ELEM_modified_path
:
261 char *path
= apr_pstrdup(lb
->subpool
, lb
->cdata
->data
);
262 if (! lb
->log_entry
->changed_paths
)
263 lb
->log_entry
->changed_paths
= apr_hash_make(lb
->subpool
);
264 apr_hash_set(lb
->log_entry
->changed_paths
, path
, APR_HASH_KEY_STRING
,
269 if (! lb
->log_entry
->revprops
)
270 lb
->log_entry
->revprops
= apr_hash_make(lb
->subpool
);
271 apr_hash_set(lb
->log_entry
->revprops
, lb
->revprop_name
,
273 svn_string_create_from_buf(lb
->cdata
, lb
->subpool
));
276 if (lb
->want_message
)
278 if (! lb
->log_entry
->revprops
)
279 lb
->log_entry
->revprops
= apr_hash_make(lb
->subpool
);
280 apr_hash_set(lb
->log_entry
->revprops
, SVN_PROP_REVISION_LOG
,
282 svn_string_create_from_buf(lb
->cdata
, lb
->subpool
));
287 /* Compatibility cruft so that we can provide limit functionality
288 even if the server doesn't support it.
290 If we've seen as many log entries as we're going to show just
291 error out of the XML parser so we can avoid having to parse the
292 remaining XML, but set lb->err to SVN_NO_ERROR so no error will
293 end up being shown to the user. */
294 if (lb
->limit
&& (++lb
->count
> lb
->limit
))
296 lb
->limit_compat_bailout
= TRUE
;
297 return svn_error_create(APR_EGENERAL
, NULL
, NULL
);
300 SVN_ERR((*(lb
->receiver
))(lb
->receiver_baton
,
308 /* Stop collecting cdata */
309 lb
->want_cdata
= NULL
;
314 svn_error_t
* svn_ra_neon__get_log(svn_ra_session_t
*session
,
315 const apr_array_header_t
*paths
,
319 svn_boolean_t discover_changed_paths
,
320 svn_boolean_t strict_node_history
,
321 svn_boolean_t include_merged_revisions
,
322 const apr_array_header_t
*revprops
,
323 svn_log_entry_receiver_t receiver
,
324 void *receiver_baton
,
327 /* The Plan: Send a request to the server for a log report.
328 * Somewhere in mod_dav_svn, there will be an implementation, R, of
329 * the `svn_log_entry_receiver_t' function type. Some other
330 * function in mod_dav_svn will use svn_repos_get_logs() to loop R
331 * over the log messages, and the successive invocations of R will
332 * collectively transmit the report back here, where we parse the
333 * report and invoke RECEIVER (which is an entirely separate
334 * instance of `svn_log_entry_receiver_t') on each individual
335 * message in that report.
339 svn_ra_neon__session_t
*ras
= session
->priv
;
340 svn_stringbuf_t
*request_body
= svn_stringbuf_create("", pool
);
341 svn_boolean_t want_custom_revprops
;
343 svn_string_t bc_url
, bc_relative
;
344 const char *final_bc_url
;
345 svn_revnum_t use_rev
;
348 /* ### todo: I don't understand why the static, file-global
349 variables shared by update and status are called `report_head'
350 and `report_tail', instead of `request_head' and `request_tail'.
351 Maybe Greg can explain? Meanwhile, I'm tentatively using
352 "request_*" for my local vars below. */
354 static const char log_request_head
[]
355 = "<S:log-report xmlns:S=\"" SVN_XML_NAMESPACE
"\">" DEBUG_CR
;
357 static const char log_request_tail
[] = "</S:log-report>" DEBUG_CR
;
359 /* Construct the request body. */
360 svn_stringbuf_appendcstr(request_body
, log_request_head
);
361 svn_stringbuf_appendcstr(request_body
,
363 "<S:start-revision>%ld"
364 "</S:start-revision>", start
));
365 svn_stringbuf_appendcstr(request_body
,
367 "<S:end-revision>%ld"
368 "</S:end-revision>", end
));
371 svn_stringbuf_appendcstr(request_body
,
373 "<S:limit>%d</S:limit>", limit
));
376 if (discover_changed_paths
)
378 svn_stringbuf_appendcstr(request_body
,
380 "<S:discover-changed-paths/>"));
383 if (strict_node_history
)
385 svn_stringbuf_appendcstr(request_body
,
387 "<S:strict-node-history/>"));
390 if (include_merged_revisions
)
392 svn_stringbuf_appendcstr(request_body
,
394 "<S:include-merged-revisions/>"));
399 lb
.want_author
= lb
.want_date
= lb
.want_message
= FALSE
;
400 want_custom_revprops
= FALSE
;
401 for (i
= 0; i
< revprops
->nelts
; i
++)
403 char *name
= APR_ARRAY_IDX(revprops
, i
, char *);
404 svn_stringbuf_appendcstr(request_body
, "<S:revprop>");
405 svn_stringbuf_appendcstr(request_body
, name
);
406 svn_stringbuf_appendcstr(request_body
, "</S:revprop>");
407 if (strcmp(name
, SVN_PROP_REVISION_AUTHOR
) == 0)
408 lb
.want_author
= TRUE
;
409 else if (strcmp(name
, SVN_PROP_REVISION_DATE
) == 0)
411 else if (strcmp(name
, SVN_PROP_REVISION_LOG
) == 0)
412 lb
.want_message
= TRUE
;
414 want_custom_revprops
= TRUE
;
419 svn_stringbuf_appendcstr(request_body
,
421 "<S:all-revprops/>"));
422 lb
.want_author
= lb
.want_date
= lb
.want_message
= TRUE
;
423 want_custom_revprops
= TRUE
;
426 if (want_custom_revprops
)
428 svn_boolean_t has_log_revprops
;
429 SVN_ERR(svn_ra_neon__has_capability(session
, &has_log_revprops
,
430 SVN_RA_CAPABILITY_LOG_REVPROPS
, pool
));
431 if (!has_log_revprops
)
432 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, NULL
,
433 _("Server does not support custom revprops"
440 for (i
= 0; i
< paths
->nelts
; i
++)
442 const char *this_path
=
443 apr_xml_quote_string(pool
,
444 APR_ARRAY_IDX(paths
, i
, const char *),
446 svn_stringbuf_appendcstr(request_body
, "<S:path>");
447 svn_stringbuf_appendcstr(request_body
, this_path
);
448 svn_stringbuf_appendcstr(request_body
, "</S:path>");
452 svn_stringbuf_appendcstr(request_body
, log_request_tail
);
454 lb
.receiver
= receiver
;
455 lb
.receiver_baton
= receiver_baton
;
456 lb
.subpool
= svn_pool_create(pool
);
459 lb
.limit_compat_bailout
= FALSE
;
460 lb
.cdata
= svn_stringbuf_create("", pool
);
461 lb
.log_entry
= svn_log_entry_create(pool
);
462 lb
.want_cdata
= NULL
;
465 /* ras's URL may not exist in HEAD, and thus it's not safe to send
466 it as the main argument to the REPORT request; it might cause
467 dav_get_resource() to choke on the server. So instead, we pass a
468 baseline-collection URL, which we get from the largest of the
469 START and END revisions. */
470 use_rev
= (start
> end
) ? start
: end
;
471 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
, &bc_url
, &bc_relative
, NULL
,
472 ras
, ras
->url
->data
, use_rev
,
474 final_bc_url
= svn_path_url_add_component(bc_url
.data
, bc_relative
.data
,
478 err
= svn_ra_neon__parsed_request(ras
,
485 svn_ra_neon__xml_collect_cdata
,
492 svn_pool_destroy(lb
.subpool
);
494 if (err
&& lb
.limit_compat_bailout
)
496 svn_log_entry_t
*log_entry
;
498 svn_error_clear(err
);
500 log_entry
= svn_log_entry_create(pool
);
501 log_entry
->revision
= SVN_INVALID_REVNUM
;
502 return receiver(receiver_baton
, log_entry
, pool
);