Reorganize the output to "svnserve --help".
[svn.git] / subversion / mod_dav_svn / reports / log.c
blob9c1be95515a9623b1175a28aaa99dc5a0821c4c2
1 /*
2 * log.c: handle the log-report request and response
4 * ====================================================================
5 * Copyright (c) 2000-2007 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 * ====================================================================
19 #include <apr_pools.h>
20 #include <apr_strings.h>
21 #include <apr_xml.h>
23 #include <mod_dav.h>
25 #include "svn_repos.h"
26 #include "svn_string.h"
27 #include "svn_types.h"
28 #include "svn_xml.h"
29 #include "svn_path.h"
30 #include "svn_dav.h"
31 #include "svn_pools.h"
33 #include "../dav_svn.h"
36 struct log_receiver_baton
38 /* this buffers the output for a bit and is automatically flushed,
39 at appropriate times, by the Apache filter system. */
40 apr_bucket_brigade *bb;
42 /* where to deliver the output */
43 ap_filter_t *output;
45 /* Whether we've written the <S:log-report> header. Allows for lazy
46 writes to support mod_dav-based error handling. */
47 svn_boolean_t needs_header;
49 /* How deep we are in the log message tree. We only need to surpress the
50 SVN_INVALID_REVNUM message if the stack_depth is 0. */
51 int stack_depth;
53 /* whether the client requested any custom revprops */
54 svn_boolean_t requested_custom_revprops;
58 /* If LRB->needs_header is true, send the "<S:log-report>" start
59 element and set LRB->needs_header to zero. Else do nothing.
60 This is basically duplicated in file_revs.c. Consider factoring if
61 duplicating again. */
62 static svn_error_t *
63 maybe_send_header(struct log_receiver_baton *lrb)
65 if (lrb->needs_header)
67 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
68 DAV_XML_HEADER DEBUG_CR
69 "<S:log-report xmlns:S=\"" SVN_XML_NAMESPACE
70 "\" " "xmlns:D=\"DAV:\">" DEBUG_CR));
71 lrb->needs_header = FALSE;
73 return SVN_NO_ERROR;
77 /* This implements `svn_log_entry_receiver_t'.
78 BATON is a `struct log_receiver_baton *'. */
79 static svn_error_t *
80 log_receiver(void *baton,
81 svn_log_entry_t *log_entry,
82 apr_pool_t *pool)
84 struct log_receiver_baton *lrb = baton;
85 apr_pool_t *iterpool = svn_pool_create(pool);
87 SVN_ERR(maybe_send_header(lrb));
89 if (log_entry->revision == SVN_INVALID_REVNUM)
91 /* If the stack depth is zero, we've seen the last revision, so don't
92 send it, just return. The footer will be sent later. */
93 if (lrb->stack_depth == 0)
94 return SVN_NO_ERROR;
95 else
96 lrb->stack_depth--;
99 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
100 "<S:log-item>" DEBUG_CR "<D:version-name>%ld"
101 "</D:version-name>" DEBUG_CR, log_entry->revision));
103 if (log_entry->revprops)
105 apr_hash_index_t *hi;
106 for (hi = apr_hash_first(pool, log_entry->revprops);
107 hi != NULL;
108 hi = apr_hash_next(hi))
110 char *name;
111 svn_string_t *value;
113 svn_pool_clear(iterpool);
114 apr_hash_this(hi, (void *)&name, NULL, (void *)&value);
115 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
116 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
117 "<D:creator-displayname>%s"
118 "</D:creator-displayname>" DEBUG_CR,
119 apr_xml_quote_string(iterpool,
120 value->data, 0)));
121 else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
122 /* ### this should be DAV:creation-date, but we need to format
123 ### that date a bit differently */
124 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
125 "<S:date>%s</S:date>" DEBUG_CR,
126 apr_xml_quote_string(iterpool,
127 value->data, 0)));
128 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
129 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
130 "<D:comment>%s</D:comment>" DEBUG_CR,
131 apr_xml_quote_string
132 (pool, svn_xml_fuzzy_escape(value->data,
133 iterpool), 0)));
134 else
136 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
137 "<S:revprop name=\"%s\">"
138 "%s</S:revprop>"
139 DEBUG_CR,
140 apr_xml_quote_string(iterpool, name, 0),
141 apr_xml_quote_string(iterpool,
142 value->data, 0)));
147 if (log_entry->has_children)
149 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
150 "<S:has-children/>"));
151 lrb->stack_depth++;
154 if (log_entry->changed_paths)
156 apr_hash_index_t *hi;
157 char *path;
159 for (hi = apr_hash_first(pool, log_entry->changed_paths);
160 hi != NULL;
161 hi = apr_hash_next(hi))
163 void *val;
164 svn_log_changed_path_t *log_item;
166 svn_pool_clear(iterpool);
167 apr_hash_this(hi, (void *) &path, NULL, &val);
168 log_item = val;
170 /* ### todo: is there a D: namespace equivalent for
171 `changed-path'? Should use it if so. */
172 switch (log_item->action)
174 case 'A':
175 if (log_item->copyfrom_path
176 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
177 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
178 "<S:added-path"
179 " copyfrom-path=\"%s\""
180 " copyfrom-rev=\"%ld\">"
181 "%s</S:added-path>" DEBUG_CR,
182 apr_xml_quote_string
183 (iterpool,
184 log_item->copyfrom_path,
185 1), /* escape quotes */
186 log_item->copyfrom_rev,
187 apr_xml_quote_string(iterpool,
188 path, 0)));
189 else
190 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
191 "<S:added-path>%s</S:added-path>"
192 DEBUG_CR,
193 apr_xml_quote_string(iterpool, path,
194 0)));
195 break;
197 case 'R':
198 if (log_item->copyfrom_path
199 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
200 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
201 "<S:replaced-path"
202 " copyfrom-path=\"%s\""
203 " copyfrom-rev=\"%ld\">"
204 "%s</S:replaced-path>" DEBUG_CR,
205 apr_xml_quote_string
206 (iterpool,
207 log_item->copyfrom_path,
208 1), /* escape quotes */
209 log_item->copyfrom_rev,
210 apr_xml_quote_string(iterpool,
211 path, 0)));
212 else
213 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
214 "<S:replaced-path>%s"
215 "</S:replaced-path>" DEBUG_CR,
216 apr_xml_quote_string(iterpool, path,
217 0)));
218 break;
220 case 'D':
221 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
222 "<S:deleted-path>%s</S:deleted-path>"
223 DEBUG_CR,
224 apr_xml_quote_string(iterpool, path, 0)));
225 break;
227 case 'M':
228 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
229 "<S:modified-path>%s"
230 "</S:modified-path>" DEBUG_CR,
231 apr_xml_quote_string(iterpool, path, 0)));
232 break;
234 default:
235 break;
240 svn_pool_destroy(iterpool);
242 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output, "</S:log-item>" DEBUG_CR));
244 return SVN_NO_ERROR;
248 dav_error *
249 dav_svn__log_report(const dav_resource *resource,
250 const apr_xml_doc *doc,
251 ap_filter_t *output)
253 svn_error_t *serr;
254 apr_status_t apr_err;
255 dav_error *derr = NULL;
256 apr_xml_elem *child;
257 struct log_receiver_baton lrb;
258 dav_svn__authz_read_baton arb;
259 const dav_svn_repos *repos = resource->info->repos;
260 const char *target = NULL;
261 int limit = 0;
262 int ns;
263 svn_boolean_t seen_revprop_element;
265 /* These get determined from the request document. */
266 svn_revnum_t start = SVN_INVALID_REVNUM; /* defaults to HEAD */
267 svn_revnum_t end = SVN_INVALID_REVNUM; /* defaults to HEAD */
268 svn_boolean_t discover_changed_paths = FALSE; /* off by default */
269 svn_boolean_t strict_node_history = FALSE; /* off by default */
270 svn_boolean_t include_merged_revisions = FALSE; /* off by default */
271 apr_array_header_t *revprops = apr_array_make(resource->pool, 3,
272 sizeof(const char *));
273 apr_array_header_t *paths
274 = apr_array_make(resource->pool, 1, sizeof(const char *));
275 svn_stringbuf_t *space_separated_paths =
276 svn_stringbuf_create("", resource->pool);
277 svn_stringbuf_t *space_separated_revprops =
278 svn_stringbuf_create("", resource->pool);
280 /* Sanity check. */
281 ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
282 if (ns == -1)
284 return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
285 "The request does not contain the 'svn:' "
286 "namespace, so it is not going to have "
287 "certain required elements.",
288 SVN_DAV_ERROR_NAMESPACE,
289 SVN_DAV_ERROR_TAG);
292 /* If this is still FALSE after the loop, we haven't seen either of
293 the revprop elements, meaning a pre-1.5 client; we'll return the
294 standard author/date/log revprops. */
295 seen_revprop_element = FALSE;
297 lrb.requested_custom_revprops = FALSE;
298 for (child = doc->root->first_child; child != NULL; child = child->next)
300 /* if this element isn't one of ours, then skip it */
301 if (child->ns != ns)
302 continue;
304 if (strcmp(child->name, "start-revision") == 0)
305 start = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1));
306 else if (strcmp(child->name, "end-revision") == 0)
307 end = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1));
308 else if (strcmp(child->name, "limit") == 0)
309 limit = atoi(dav_xml_get_cdata(child, resource->pool, 1));
310 else if (strcmp(child->name, "discover-changed-paths") == 0)
311 discover_changed_paths = TRUE; /* presence indicates positivity */
312 else if (strcmp(child->name, "strict-node-history") == 0)
313 strict_node_history = TRUE; /* presence indicates positivity */
314 else if (strcmp(child->name, "include-merged-revisions") == 0)
315 include_merged_revisions = TRUE; /* presence indicates positivity */
316 else if (strcmp(child->name, "all-revprops") == 0)
318 revprops = NULL; /* presence indicates fetch all revprops */
319 seen_revprop_element = lrb.requested_custom_revprops = TRUE;
321 else if (strcmp(child->name, "revprop") == 0)
323 if (revprops)
325 /* We're not fetching all revprops, append to fetch list. */
326 const char *name = dav_xml_get_cdata(child, resource->pool, 0);
327 APR_ARRAY_PUSH(revprops, const char *) = name;
328 if (!lrb.requested_custom_revprops
329 && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
330 && strcmp(name, SVN_PROP_REVISION_DATE) != 0
331 && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
332 lrb.requested_custom_revprops = TRUE;
334 /* Gather a formatted list of revprops for operational logging. */
335 if (space_separated_revprops->len > 1)
336 svn_stringbuf_appendcstr(space_separated_revprops, " ");
337 svn_stringbuf_appendcstr(space_separated_revprops, name);
339 seen_revprop_element = TRUE;
341 else if (strcmp(child->name, "path") == 0)
343 const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
344 if ((derr = dav_svn__test_canonical(rel_path, resource->pool)))
345 return derr;
346 target = svn_path_join(resource->info->repos_path, rel_path,
347 resource->pool);
348 APR_ARRAY_PUSH(paths, const char *) = target;
350 /* Gather a formatted list of paths to include in our
351 operational logging. */
352 if (space_separated_paths->len > 1)
353 svn_stringbuf_appendcstr(space_separated_paths, " ");
354 svn_stringbuf_appendcstr(space_separated_paths,
355 svn_path_uri_encode(target,
356 resource->pool));
358 /* else unknown element; skip it */
361 if (!seen_revprop_element)
363 /* pre-1.5 client */
364 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
365 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
366 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
369 /* Build authz read baton */
370 arb.r = resource->info->r;
371 arb.repos = resource->info->repos;
373 /* Build log receiver baton */
374 lrb.bb = apr_brigade_create(resource->pool, /* not the subpool! */
375 output->c->bucket_alloc);
376 lrb.output = output;
377 lrb.needs_header = TRUE;
378 lrb.stack_depth = 0;
379 /* lrb.requested_custom_revprops set above */
381 /* Our svn_log_entry_receiver_t sends the <S:log-report> header in
382 a lazy fashion. Before writing the first log message, it assures
383 that the header has already been sent (checking the needs_header
384 flag in our log_receiver_baton structure). */
386 /* Send zero or more log items. */
387 serr = svn_repos_get_logs4(repos->repos,
388 paths,
389 start,
390 end,
391 limit,
392 discover_changed_paths,
393 strict_node_history,
394 include_merged_revisions,
395 revprops,
396 dav_svn__authz_read_func(&arb),
397 &arb,
398 log_receiver,
399 &lrb,
400 resource->pool);
401 if (serr)
403 derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
404 resource->pool);
405 goto cleanup;
408 if ((serr = maybe_send_header(&lrb)))
410 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
411 "Error beginning REPORT response.",
412 resource->pool);
413 goto cleanup;
416 if ((serr = dav_svn__send_xml(lrb.bb, lrb.output, "</S:log-report>"
417 DEBUG_CR)))
419 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
420 "Error ending REPORT response.",
421 resource->pool);
422 goto cleanup;
425 cleanup:
428 /* We've detected a 'high level' svn action to log. */
429 svn_stringbuf_t *options = svn_stringbuf_create("", resource->pool);
430 const char *action;
432 if (limit)
434 char *tmp = apr_psprintf(resource->pool, " limit=%d", limit);
435 svn_stringbuf_appendcstr(options, tmp);
437 if (discover_changed_paths)
438 svn_stringbuf_appendcstr(options, " discover-changed-paths");
439 if (strict_node_history)
440 svn_stringbuf_appendcstr(options, " strict");
441 if (include_merged_revisions)
442 svn_stringbuf_appendcstr(options, " include-merged-revisions");
443 if (revprops == NULL)
444 svn_stringbuf_appendcstr(options, " revprops=all");
445 else if (revprops->nelts > 0)
447 svn_stringbuf_appendcstr(options, " revprops=(");
448 svn_stringbuf_appendstr(options, space_separated_revprops);
449 svn_stringbuf_appendcstr(options, ")");
452 action = apr_psprintf(resource->pool, "log (%s) r%ld:%ld%s",
453 space_separated_paths->data, start, end,
454 options->data);
455 dav_svn__operational_log(resource->info, action);
458 /* Flush the contents of the brigade (returning an error only if we
459 don't already have one). */
460 if (!lrb.needs_header)
462 apr_err = ap_fflush(output, lrb.bb);
463 if (!derr && apr_err)
465 derr = dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
466 HTTP_INTERNAL_SERVER_ERROR,
467 "Error flushing brigade.",
468 resource->pool);
471 return derr;