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"
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 */
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. */
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
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
;
78 /* This implements `svn_log_entry_receiver_t'.
79 BATON is a `struct log_receiver_baton *'. */
81 log_receiver(void *baton
,
82 svn_log_entry_t
*log_entry
,
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)
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
);
109 hi
= apr_hash_next(hi
))
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
,
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
,
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
,
133 (pool
, svn_xml_fuzzy_escape(value
->data
,
137 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
138 "<S:revprop name=\"%s\">"
141 apr_xml_quote_string(iterpool
, name
, 0),
142 apr_xml_quote_string(iterpool
,
148 if (log_entry
->has_children
)
150 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
151 "<S:has-children/>"));
155 if (log_entry
->changed_paths
)
157 apr_hash_index_t
*hi
;
160 for (hi
= apr_hash_first(pool
, log_entry
->changed_paths
);
162 hi
= apr_hash_next(hi
))
165 svn_log_changed_path_t
*log_item
;
167 svn_pool_clear(iterpool
);
168 apr_hash_this(hi
, (void *) &path
, NULL
, &val
);
171 /* ### todo: is there a D: namespace equivalent for
172 `changed-path'? Should use it if so. */
173 switch (log_item
->action
)
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
,
180 " copyfrom-path=\"%s\""
181 " copyfrom-rev=\"%ld\">"
182 "%s</S:added-path>" DEBUG_CR
,
185 log_item
->copyfrom_path
,
186 1), /* escape quotes */
187 log_item
->copyfrom_rev
,
188 apr_xml_quote_string(iterpool
,
191 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
192 "<S:added-path>%s</S:added-path>"
194 apr_xml_quote_string(iterpool
, path
,
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
,
203 " copyfrom-path=\"%s\""
204 " copyfrom-rev=\"%ld\">"
205 "%s</S:replaced-path>" DEBUG_CR
,
208 log_item
->copyfrom_path
,
209 1), /* escape quotes */
210 log_item
->copyfrom_rev
,
211 apr_xml_quote_string(iterpool
,
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
,
222 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
,
223 "<S:deleted-path>%s</S:deleted-path>"
225 apr_xml_quote_string(iterpool
, path
, 0)));
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)));
241 svn_pool_destroy(iterpool
);
243 SVN_ERR(dav_svn__send_xml(lrb
->bb
, lrb
->output
, "</S:log-item>" DEBUG_CR
));
250 dav_svn__log_report(const dav_resource
*resource
,
251 const apr_xml_doc
*doc
,
255 apr_status_t apr_err
;
256 dav_error
*derr
= NULL
;
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
;
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 *));
278 ns
= dav_svn__find_ns(doc
->namespaces
, SVN_XML_NAMESPACE
);
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
,
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 */
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)
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
)))
338 target
= svn_path_join(resource
->info
->repos_path
, rel_path
,
340 APR_ARRAY_PUSH(paths
, const char *) = target
;
342 /* else unknown element; skip it */
345 if (!seen_revprop_element
)
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
);
361 lrb
.needs_header
= TRUE
;
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
,
376 discover_changed_paths
,
378 include_merged_revisions
,
380 dav_svn__authz_read_func(&arb
),
387 derr
= dav_svn__convert_err(serr
, HTTP_BAD_REQUEST
, serr
->message
,
392 if ((serr
= maybe_send_header(&lrb
)))
394 derr
= dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
395 "Error beginning REPORT response.",
400 if ((serr
= dav_svn__send_xml(lrb
.bb
, lrb
.output
, "</S:log-report>"
403 derr
= dav_svn__convert_err(serr
, HTTP_INTERNAL_SERVER_ERROR
,
404 "Error ending REPORT response.",
411 dav_svn__operational_log(resource
->info
,
412 svn_log__log(paths
, start
, end
, limit
,
413 discover_changed_paths
,
415 include_merged_revisions
, revprops
,
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.",