Merge the svnserve-logging branch, in its entirety, to trunk, using the
[svn.git] / subversion / mod_dav_svn / reports / log.c
blob19b805f62c77b472fc1b18e1b2952415d4d121f9
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"
32 #include "private/svn_log.h"
34 #include "../dav_svn.h"
37 struct log_receiver_baton
39 /* this buffers the output for a bit and is automatically flushed,
40 at appropriate times, by the Apache filter system. */
41 apr_bucket_brigade *bb;
43 /* where to deliver the output */
44 ap_filter_t *output;
46 /* Whether we've written the <S:log-report> header. Allows for lazy
47 writes to support mod_dav-based error handling. */
48 svn_boolean_t needs_header;
50 /* How deep we are in the log message tree. We only need to surpress the
51 SVN_INVALID_REVNUM message if the stack_depth is 0. */
52 int stack_depth;
54 /* whether the client requested any custom revprops */
55 svn_boolean_t requested_custom_revprops;
59 /* If LRB->needs_header is true, send the "<S:log-report>" start
60 element and set LRB->needs_header to zero. Else do nothing.
61 This is basically duplicated in file_revs.c. Consider factoring if
62 duplicating again. */
63 static svn_error_t *
64 maybe_send_header(struct log_receiver_baton *lrb)
66 if (lrb->needs_header)
68 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
69 DAV_XML_HEADER DEBUG_CR
70 "<S:log-report xmlns:S=\"" SVN_XML_NAMESPACE
71 "\" " "xmlns:D=\"DAV:\">" DEBUG_CR));
72 lrb->needs_header = FALSE;
74 return SVN_NO_ERROR;
78 /* This implements `svn_log_entry_receiver_t'.
79 BATON is a `struct log_receiver_baton *'. */
80 static svn_error_t *
81 log_receiver(void *baton,
82 svn_log_entry_t *log_entry,
83 apr_pool_t *pool)
85 struct log_receiver_baton *lrb = baton;
86 apr_pool_t *iterpool = svn_pool_create(pool);
88 SVN_ERR(maybe_send_header(lrb));
90 if (log_entry->revision == SVN_INVALID_REVNUM)
92 /* If the stack depth is zero, we've seen the last revision, so don't
93 send it, just return. The footer will be sent later. */
94 if (lrb->stack_depth == 0)
95 return SVN_NO_ERROR;
96 else
97 lrb->stack_depth--;
100 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
101 "<S:log-item>" DEBUG_CR "<D:version-name>%ld"
102 "</D:version-name>" DEBUG_CR, log_entry->revision));
104 if (log_entry->revprops)
106 apr_hash_index_t *hi;
107 for (hi = apr_hash_first(pool, log_entry->revprops);
108 hi != NULL;
109 hi = apr_hash_next(hi))
111 char *name;
112 svn_string_t *value;
114 svn_pool_clear(iterpool);
115 apr_hash_this(hi, (void *)&name, NULL, (void *)&value);
116 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
117 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
118 "<D:creator-displayname>%s"
119 "</D:creator-displayname>" DEBUG_CR,
120 apr_xml_quote_string(iterpool,
121 value->data, 0)));
122 else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
123 /* ### this should be DAV:creation-date, but we need to format
124 ### that date a bit differently */
125 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
126 "<S:date>%s</S:date>" DEBUG_CR,
127 apr_xml_quote_string(iterpool,
128 value->data, 0)));
129 else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
130 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
131 "<D:comment>%s</D:comment>" DEBUG_CR,
132 apr_xml_quote_string
133 (pool, svn_xml_fuzzy_escape(value->data,
134 iterpool), 0)));
135 else
137 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
138 "<S:revprop name=\"%s\">"
139 "%s</S:revprop>"
140 DEBUG_CR,
141 apr_xml_quote_string(iterpool, name, 0),
142 apr_xml_quote_string(iterpool,
143 value->data, 0)));
148 if (log_entry->has_children)
150 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
151 "<S:has-children/>"));
152 lrb->stack_depth++;
155 if (log_entry->changed_paths)
157 apr_hash_index_t *hi;
158 char *path;
160 for (hi = apr_hash_first(pool, log_entry->changed_paths);
161 hi != NULL;
162 hi = apr_hash_next(hi))
164 void *val;
165 svn_log_changed_path_t *log_item;
167 svn_pool_clear(iterpool);
168 apr_hash_this(hi, (void *) &path, NULL, &val);
169 log_item = val;
171 /* ### todo: is there a D: namespace equivalent for
172 `changed-path'? Should use it if so. */
173 switch (log_item->action)
175 case 'A':
176 if (log_item->copyfrom_path
177 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
178 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
179 "<S:added-path"
180 " copyfrom-path=\"%s\""
181 " copyfrom-rev=\"%ld\">"
182 "%s</S:added-path>" DEBUG_CR,
183 apr_xml_quote_string
184 (iterpool,
185 log_item->copyfrom_path,
186 1), /* escape quotes */
187 log_item->copyfrom_rev,
188 apr_xml_quote_string(iterpool,
189 path, 0)));
190 else
191 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
192 "<S:added-path>%s</S:added-path>"
193 DEBUG_CR,
194 apr_xml_quote_string(iterpool, path,
195 0)));
196 break;
198 case 'R':
199 if (log_item->copyfrom_path
200 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
201 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
202 "<S:replaced-path"
203 " copyfrom-path=\"%s\""
204 " copyfrom-rev=\"%ld\">"
205 "%s</S:replaced-path>" DEBUG_CR,
206 apr_xml_quote_string
207 (iterpool,
208 log_item->copyfrom_path,
209 1), /* escape quotes */
210 log_item->copyfrom_rev,
211 apr_xml_quote_string(iterpool,
212 path, 0)));
213 else
214 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
215 "<S:replaced-path>%s"
216 "</S:replaced-path>" DEBUG_CR,
217 apr_xml_quote_string(iterpool, path,
218 0)));
219 break;
221 case 'D':
222 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
223 "<S:deleted-path>%s</S:deleted-path>"
224 DEBUG_CR,
225 apr_xml_quote_string(iterpool, path, 0)));
226 break;
228 case 'M':
229 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output,
230 "<S:modified-path>%s"
231 "</S:modified-path>" DEBUG_CR,
232 apr_xml_quote_string(iterpool, path, 0)));
233 break;
235 default:
236 break;
241 svn_pool_destroy(iterpool);
243 SVN_ERR(dav_svn__send_xml(lrb->bb, lrb->output, "</S:log-item>" DEBUG_CR));
245 return SVN_NO_ERROR;
249 dav_error *
250 dav_svn__log_report(const dav_resource *resource,
251 const apr_xml_doc *doc,
252 ap_filter_t *output)
254 svn_error_t *serr;
255 apr_status_t apr_err;
256 dav_error *derr = NULL;
257 apr_xml_elem *child;
258 struct log_receiver_baton lrb;
259 dav_svn__authz_read_baton arb;
260 const dav_svn_repos *repos = resource->info->repos;
261 const char *target = NULL;
262 int limit = 0;
263 int ns;
264 svn_boolean_t seen_revprop_element;
266 /* These get determined from the request document. */
267 svn_revnum_t start = SVN_INVALID_REVNUM; /* defaults to HEAD */
268 svn_revnum_t end = SVN_INVALID_REVNUM; /* defaults to HEAD */
269 svn_boolean_t discover_changed_paths = FALSE; /* off by default */
270 svn_boolean_t strict_node_history = FALSE; /* off by default */
271 svn_boolean_t include_merged_revisions = FALSE; /* off by default */
272 apr_array_header_t *revprops = apr_array_make(resource->pool, 3,
273 sizeof(const char *));
274 apr_array_header_t *paths
275 = apr_array_make(resource->pool, 1, sizeof(const char *));
277 /* Sanity check. */
278 ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
279 if (ns == -1)
281 return dav_svn__new_error_tag(resource->pool, HTTP_BAD_REQUEST, 0,
282 "The request does not contain the 'svn:' "
283 "namespace, so it is not going to have "
284 "certain required elements.",
285 SVN_DAV_ERROR_NAMESPACE,
286 SVN_DAV_ERROR_TAG);
289 /* If this is still FALSE after the loop, we haven't seen either of
290 the revprop elements, meaning a pre-1.5 client; we'll return the
291 standard author/date/log revprops. */
292 seen_revprop_element = FALSE;
294 lrb.requested_custom_revprops = FALSE;
295 for (child = doc->root->first_child; child != NULL; child = child->next)
297 /* if this element isn't one of ours, then skip it */
298 if (child->ns != ns)
299 continue;
301 if (strcmp(child->name, "start-revision") == 0)
302 start = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1));
303 else if (strcmp(child->name, "end-revision") == 0)
304 end = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1));
305 else if (strcmp(child->name, "limit") == 0)
306 limit = atoi(dav_xml_get_cdata(child, resource->pool, 1));
307 else if (strcmp(child->name, "discover-changed-paths") == 0)
308 discover_changed_paths = TRUE; /* presence indicates positivity */
309 else if (strcmp(child->name, "strict-node-history") == 0)
310 strict_node_history = TRUE; /* presence indicates positivity */
311 else if (strcmp(child->name, "include-merged-revisions") == 0)
312 include_merged_revisions = TRUE; /* presence indicates positivity */
313 else if (strcmp(child->name, "all-revprops") == 0)
315 revprops = NULL; /* presence indicates fetch all revprops */
316 seen_revprop_element = lrb.requested_custom_revprops = TRUE;
318 else if (strcmp(child->name, "revprop") == 0)
320 if (revprops)
322 /* We're not fetching all revprops, append to fetch list. */
323 const char *name = dav_xml_get_cdata(child, resource->pool, 0);
324 APR_ARRAY_PUSH(revprops, const char *) = name;
325 if (!lrb.requested_custom_revprops
326 && strcmp(name, SVN_PROP_REVISION_AUTHOR) != 0
327 && strcmp(name, SVN_PROP_REVISION_DATE) != 0
328 && strcmp(name, SVN_PROP_REVISION_LOG) != 0)
329 lrb.requested_custom_revprops = TRUE;
331 seen_revprop_element = TRUE;
333 else if (strcmp(child->name, "path") == 0)
335 const char *rel_path = dav_xml_get_cdata(child, resource->pool, 0);
336 if ((derr = dav_svn__test_canonical(rel_path, resource->pool)))
337 return derr;
338 target = svn_path_join(resource->info->repos_path, rel_path,
339 resource->pool);
340 APR_ARRAY_PUSH(paths, const char *) = target;
342 /* else unknown element; skip it */
345 if (!seen_revprop_element)
347 /* pre-1.5 client */
348 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
349 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
350 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
353 /* Build authz read baton */
354 arb.r = resource->info->r;
355 arb.repos = resource->info->repos;
357 /* Build log receiver baton */
358 lrb.bb = apr_brigade_create(resource->pool, /* not the subpool! */
359 output->c->bucket_alloc);
360 lrb.output = output;
361 lrb.needs_header = TRUE;
362 lrb.stack_depth = 0;
363 /* lrb.requested_custom_revprops set above */
365 /* Our svn_log_entry_receiver_t sends the <S:log-report> header in
366 a lazy fashion. Before writing the first log message, it assures
367 that the header has already been sent (checking the needs_header
368 flag in our log_receiver_baton structure). */
370 /* Send zero or more log items. */
371 serr = svn_repos_get_logs4(repos->repos,
372 paths,
373 start,
374 end,
375 limit,
376 discover_changed_paths,
377 strict_node_history,
378 include_merged_revisions,
379 revprops,
380 dav_svn__authz_read_func(&arb),
381 &arb,
382 log_receiver,
383 &lrb,
384 resource->pool);
385 if (serr)
387 derr = dav_svn__convert_err(serr, HTTP_BAD_REQUEST, serr->message,
388 resource->pool);
389 goto cleanup;
392 if ((serr = maybe_send_header(&lrb)))
394 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
395 "Error beginning REPORT response.",
396 resource->pool);
397 goto cleanup;
400 if ((serr = dav_svn__send_xml(lrb.bb, lrb.output, "</S:log-report>"
401 DEBUG_CR)))
403 derr = dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
404 "Error ending REPORT response.",
405 resource->pool);
406 goto cleanup;
409 cleanup:
411 dav_svn__operational_log(resource->info,
412 svn_log__log(paths, start, end, limit,
413 discover_changed_paths,
414 strict_node_history,
415 include_merged_revisions, revprops,
416 resource->pool));
418 /* Flush the contents of the brigade (returning an error only if we
419 don't already have one). */
420 if (!lrb.needs_header)
422 apr_err = ap_fflush(output, lrb.bb);
423 if (!derr && apr_err)
425 derr = dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
426 HTTP_INTERNAL_SERVER_ERROR,
427 "Error flushing brigade.",
428 resource->pool);
431 return derr;