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>
25 #include "svn_repos.h"
26 #include "svn_string.h"
27 #include "svn_types.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 */
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. */
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
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
;
77 /* This implements `svn_log_entry_receiver_t'.
78 BATON is a `struct log_receiver_baton *'. */
80 log_receiver(void *baton
,
81 svn_log_entry_t
*log_entry
,
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)
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
);
108 hi
= apr_hash_next(hi
))
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
,
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
,
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
,
132 (pool
, svn_xml_fuzzy_escape(value
->data
,
136 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
137 "<S:revprop name=\"%s\">"
140 apr_xml_quote_string(iterpool
, name
, 0),
141 apr_xml_quote_string(iterpool
,
147 if (log_entry
->has_children
)
149 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
150 "<S:has-children/>"));
154 if (log_entry
->changed_paths
)
156 apr_hash_index_t
*hi
;
159 for (hi
= apr_hash_first(pool
, log_entry
->changed_paths
);
161 hi
= apr_hash_next(hi
))
164 svn_log_changed_path_t
*log_item
;
166 svn_pool_clear(iterpool
);
167 apr_hash_this(hi
, (void *) &path
, NULL
, &val
);
170 /* ### todo: is there a D: namespace equivalent for
171 `changed-path'? Should use it if so. */
172 switch (log_item
->action
)
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
,
179 " copyfrom-path=\"%s\""
180 " copyfrom-rev=\"%ld\">"
181 "%s</S:added-path>" DEBUG_CR
,
184 log_item
->copyfrom_path
,
185 1), /* escape quotes */
186 log_item
->copyfrom_rev
,
187 apr_xml_quote_string(iterpool
,
190 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
191 "<S:added-path>%s</S:added-path>"
193 apr_xml_quote_string(iterpool
, path
,
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
,
202 " copyfrom-path=\"%s\""
203 " copyfrom-rev=\"%ld\">"
204 "%s</S:replaced-path>" DEBUG_CR
,
207 log_item
->copyfrom_path
,
208 1), /* escape quotes */
209 log_item
->copyfrom_rev
,
210 apr_xml_quote_string(iterpool
,
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
,
221 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
222 "<S:deleted-path>%s</S:deleted-path>"
224 apr_xml_quote_string(iterpool
, path
, 0)));
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)));
240 svn_pool_destroy(iterpool
);
242 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
, "</S:log-item>" DEBUG_CR
));
249 dav_svn__log_report(const dav_resource
*resource
,
250 const apr_xml_doc
*doc
,
254 apr_status_t apr_err
;
255 dav_error
*derr
= NULL
;
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
;
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
);
281 ns
= dav_svn__find_ns(doc
->namespaces
, SVN_XML_NAMESPACE
);
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
,
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 */
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)
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
)))
346 target
= svn_path_join(resource
->info
->repos_path
, rel_path
,
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
,
358 /* else unknown element; skip it */
361 if (!seen_revprop_element
)
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
);
377 lrb
.needs_header
= TRUE
;
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
,
392 discover_changed_paths
,
394 include_merged_revisions
,
396 dav_svn__authz_read_func(&arb
),
403 derr
= dav_svn__convert_err(serr
, HTTP_BAD_REQUEST
, serr
->message
,
408 if ((serr
= maybe_send_header(&lrb
)))
410 derr
= dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
411 "Error beginning REPORT response.",
416 if ((serr
= dav_svn__send_xml(lrb
.bb
, lrb
.output
, "</S:log-report>"
419 derr
= dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
420 "Error ending REPORT response.",
428 /* We've detected a 'high level' svn action to log. */
429 svn_stringbuf_t
*options
= svn_stringbuf_create("", resource
->pool
);
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
,
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.",