Followup to r29625: fix getopt tests.
[svn.git] / subversion / libsvn_ra_neon / log.c
blobf13f70928ddc2c44077549c26fab661abebe4323
1 /*
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>
27 #include <apr_xml.h>
29 #include "svn_error.h"
30 #include "svn_pools.h"
31 #include "svn_path.h"
32 #include "svn_xml.h"
34 #include "private/svn_dav_protocol.h"
35 #include "../libsvn_ra/ra_loader.h"
37 #include "ra_neon.h"
41 /*** Code ***/
43 /* Userdata for the Neon XML element callbacks. */
44 struct log_baton
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
53 * received.
55 apr_pool_t *subpool;
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
70 item is seen. */
71 svn_log_entry_receiver_t receiver;
72 void *receiver_baton;
74 int limit;
75 int count;
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.
91 static void
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.
106 static svn_error_t *
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 },
135 { NULL }
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;
141 if (!elm)
142 return SVN_NO_ERROR;
144 switch (elm->id)
146 case ELEM_creator_displayname:
147 case ELEM_log_date:
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:
153 case ELEM_revprop:
154 case ELEM_comment:
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",
161 atts));
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"));
166 break;
167 case ELEM_has_children:
168 lb->log_entry->has_children = TRUE;
169 break;
171 default:
172 lb->want_cdata = NULL;
173 break;
176 switch (elm->id)
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,
200 copyfrom_path);
201 lb->this_path_item->copyfrom_rev = copyfrom_rev;
204 else if (elm->id == ELEM_deleted_path)
206 lb->this_path_item->action = 'D';
208 else
210 lb->this_path_item->action = 'M';
212 break;
214 default:
215 lb->this_path_item = NULL;
216 break;
218 return SVN_NO_ERROR;
223 * This implements the `svn_ra_neon__xml_endelm_cb' prototype.
225 static svn_error_t *
226 log_end_element(void *baton, int state,
227 const char *nspace, const char *name)
229 struct log_baton *lb = baton;
231 switch (state)
233 case ELEM_version_name:
234 lb->log_entry->revision = SVN_STR_TO_REV(lb->cdata->data);
235 break;
236 case ELEM_creator_displayname:
237 if (lb->want_author)
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,
242 APR_HASH_KEY_STRING,
243 svn_string_create_from_buf(lb->cdata, lb->subpool));
245 break;
246 case ELEM_log_date:
247 if (lb->want_date)
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,
252 APR_HASH_KEY_STRING,
253 svn_string_create_from_buf(lb->cdata, lb->subpool));
255 break;
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,
265 lb->this_path_item);
266 break;
268 case ELEM_revprop:
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,
272 APR_HASH_KEY_STRING,
273 svn_string_create_from_buf(lb->cdata, lb->subpool));
274 break;
275 case ELEM_comment:
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,
281 APR_HASH_KEY_STRING,
282 svn_string_create_from_buf(lb->cdata, lb->subpool));
284 break;
285 case ELEM_log_item:
287 /* Compatability 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,
301 lb->log_entry,
302 lb->subpool));
303 reset_log_item(lb);
305 break;
308 /* Stop collecting cdata */
309 lb->want_cdata = NULL;
310 return SVN_NO_ERROR;
314 svn_error_t * svn_ra_neon__get_log(svn_ra_session_t *session,
315 const apr_array_header_t *paths,
316 svn_revnum_t start,
317 svn_revnum_t end,
318 int limit,
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,
325 apr_pool_t *pool)
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.
338 int i;
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;
342 struct log_baton lb;
343 svn_string_t bc_url, bc_relative;
344 const char *final_bc_url;
345 svn_revnum_t use_rev;
346 svn_error_t *err;
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,
362 apr_psprintf(pool,
363 "<S:start-revision>%ld"
364 "</S:start-revision>", start));
365 svn_stringbuf_appendcstr(request_body,
366 apr_psprintf(pool,
367 "<S:end-revision>%ld"
368 "</S:end-revision>", end));
369 if (limit)
371 svn_stringbuf_appendcstr(request_body,
372 apr_psprintf(pool,
373 "<S:limit>%d</S:limit>", limit));
376 if (discover_changed_paths)
378 svn_stringbuf_appendcstr(request_body,
379 apr_psprintf(pool,
380 "<S:discover-changed-paths/>"));
383 if (strict_node_history)
385 svn_stringbuf_appendcstr(request_body,
386 apr_psprintf(pool,
387 "<S:strict-node-history/>"));
390 if (include_merged_revisions)
392 svn_stringbuf_appendcstr(request_body,
393 apr_psprintf(pool,
394 "<S:include-merged-revisions/>"));
397 if (revprops)
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)
410 lb.want_date = TRUE;
411 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
412 lb.want_message = TRUE;
413 else
414 want_custom_revprops = TRUE;
417 else
419 svn_stringbuf_appendcstr(request_body,
420 apr_psprintf(pool,
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_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"
434 " via log"));
438 if (paths)
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);
457 lb.limit = limit;
458 lb.count = 0;
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;
463 reset_log_item(&lb);
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,
473 pool));
474 final_bc_url = svn_path_url_add_component(bc_url.data, bc_relative.data,
475 pool);
478 err = svn_ra_neon__parsed_request(ras,
479 "REPORT",
480 final_bc_url,
481 request_body->data,
482 0, /* ignored */
483 NULL,
484 log_start_element,
485 svn_ra_neon__xml_collect_cdata,
486 log_end_element,
487 &lb,
488 NULL,
489 NULL,
490 FALSE,
491 pool);
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);
505 return err;