2
* log.c : routines for requesting and parsing log reports
4
* ====================================================================
5
* Copyright (c) 2000-2004 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>
29
#include <ne_socket.h>
31
#include "svn_error.h"
32
#include "svn_pools.h"
35
#include "../libsvn_ra/ra_loader.h"
43
/* Userdata for the Neon XML element callbacks. */
46
/* Allocate log message information.
47
* NOTE: this pool may be cleared multiple times as log messages are
52
/* Information about each log item in turn. */
53
svn_revnum_t revision;
58
/* Keys are the paths changed in this commit, allocated in SUBPOOL;
59
the table itself is also allocated in SUBPOOL. If this table is
60
NULL, no changed paths were indicated -- which doesn't mean no
61
paths were changed, just means that this log invocation didn't
62
ask for them to be reported. */
63
apr_hash_t *changed_paths;
65
/* The current changed path item. */
66
svn_log_changed_path_t *this_path_item;
68
/* Client's callback, invoked on the above fields when the end of an
70
svn_log_message_receiver_t receiver;
76
/* If `receiver' returns error, it is stored here. */
81
/* Prepare LB to start accumulating the next log item, by wiping all
82
* information related to the previous item and clearing the pool in
83
* which they were allocated. Do not touch any stored error, however.
86
reset_log_item (struct log_baton *lb)
88
lb->revision = SVN_INVALID_REVNUM;
92
lb->changed_paths = NULL;
94
svn_pool_clear (lb->subpool);
99
* This implements the `svn_ra_dav__xml_validate_cb' prototype.
102
log_validate(void *userdata, svn_ra_dav__xml_elmid parent,
103
svn_ra_dav__xml_elmid child)
106
return SVN_RA_DAV__XML_VALID;
110
* This implements the `svn_ra_dav__xml_startelm_cb' prototype.
113
log_start_element(void *userdata,
114
const svn_ra_dav__xml_elm_t *elm,
117
struct log_baton *lb = userdata;
118
const char *copyfrom_path, *copyfrom_revstr;
119
svn_revnum_t copyfrom_rev;
123
case ELEM_added_path:
124
case ELEM_replaced_path:
125
case ELEM_deleted_path:
126
case ELEM_modified_path:
127
lb->this_path_item = apr_pcalloc(lb->subpool,
128
sizeof(*(lb->this_path_item)));
129
lb->this_path_item->copyfrom_rev = SVN_INVALID_REVNUM;
131
/* See documentation for `svn_repos_node_t' in svn_repos.h,
132
and `svn_log_message_receiver_t' in svn_types.h, for more
133
about these action codes. */
134
if ((elm->id == ELEM_added_path) || (elm->id == ELEM_replaced_path))
136
lb->this_path_item->action
137
= (elm->id == ELEM_added_path) ? 'A' : 'R';
138
copyfrom_path = svn_xml_get_attr_value("copyfrom-path", atts);
139
copyfrom_revstr = svn_xml_get_attr_value("copyfrom-rev", atts);
140
if (copyfrom_path && copyfrom_revstr
141
&& (SVN_IS_VALID_REVNUM
142
(copyfrom_rev = SVN_STR_TO_REV (copyfrom_revstr))))
144
lb->this_path_item->copyfrom_path = apr_pstrdup(lb->subpool,
146
lb->this_path_item->copyfrom_rev = copyfrom_rev;
149
else if (elm->id == ELEM_deleted_path)
151
lb->this_path_item->action = 'D';
155
lb->this_path_item->action = 'M';
160
lb->this_path_item = NULL;
163
return SVN_RA_DAV__XML_VALID;
168
* This implements the `svn_ra_dav__xml_endelm_cb' prototype.
171
log_end_element(void *userdata,
172
const svn_ra_dav__xml_elm_t *elm,
175
struct log_baton *lb = userdata;
179
case ELEM_version_name:
180
lb->revision = SVN_STR_TO_REV (cdata);
182
case ELEM_creator_displayname:
183
lb->author = apr_pstrdup (lb->subpool, cdata);
186
lb->date = apr_pstrdup (lb->subpool, cdata);
188
case ELEM_added_path:
189
case ELEM_replaced_path:
190
case ELEM_deleted_path:
191
case ELEM_modified_path:
193
char *path = apr_pstrdup (lb->subpool, cdata);
194
if (! lb->changed_paths)
195
lb->changed_paths = apr_hash_make(lb->subpool);
196
apr_hash_set(lb->changed_paths, path, APR_HASH_KEY_STRING,
201
lb->msg = apr_pstrdup (lb->subpool, cdata);
206
/* Compatability cruft so that we can provide limit functionality
207
even if the server doesn't support it.
209
If we've seen as many log entries as we're going to show just
210
error out of the XML parser so we can avoid having to parse the
211
remaining XML, but set lb->err to SVN_NO_ERROR so no error will
212
end up being shown to the user. */
213
if (lb->limit && (++lb->count > lb->limit))
215
lb->err = SVN_NO_ERROR;
216
return SVN_RA_DAV__XML_INVALID;
219
err = (*(lb->receiver))(lb->receiver_baton,
231
/* Only remember the first error. */
235
svn_error_clear(err);
237
return SVN_RA_DAV__XML_INVALID; /* ### Any other way to express
242
case ELEM_log_report:
244
/* Do nothing. But...
248
* Greg Stein mused that we could treat log_receivers the way
249
* we treat delta window consumers -- "no more calls" would be
250
* indicated by a special last call that passes
251
* SVN_INVALID_REVNUM as the revision number. That would work
252
* fine, but right now most of the code just handles the
253
* first-call/last-call thing by having one-time code on
254
* either side of the iterator, which works just as well.
256
* I don't feel any compelling need to change this right now.
257
* If we do change it, the hot spots are:
259
* - libsvn_repos/log.c:
260
* svn_repos_get_logs() would need a new post-loop
261
* call to (*receiver)(), passing SVN_INVALID_REVNUM.
262
* Make sure not to destroy that subpool until
263
* after the new call! :-)
265
* - mod_dav_svn/log.c:
266
* `struct log_receiver_baton' would need a first_call
267
* flag; dav_svn__log_report() would set it up, and
268
* then log_receiver() would be responsible for
269
* emitting "<S:log-report>" and "</S:log-report>"
272
* - clients/cmdline/log-cmd.c:
273
* svn_cl__log() would no longer be responsible for
274
* emitting the "<log>" and "</log>" elements. The
275
* body of this function would get a lot simpler, mmm!
276
* Instead, log_message_receiver_xml() would pay
277
* attention to baton->first_call, and handle
278
* SVN_INVALID_REVNUM, to emit those elements
279
* instead. The old log_message_receiver() function
280
* wouldn't need to change at all, though, I think.
283
* We'd have a new call to (*(lb->receiver)), passing
284
* SVN_INVALID_REVNUM, of course.
286
* There, I think that's the change. Thoughts? :-)
292
return SVN_RA_DAV__XML_VALID;
296
svn_error_t * svn_ra_dav__get_log(svn_ra_session_t *session,
297
const apr_array_header_t *paths,
301
svn_boolean_t discover_changed_paths,
302
svn_boolean_t strict_node_history,
303
svn_log_message_receiver_t receiver,
304
void *receiver_baton,
307
/* The Plan: Send a request to the server for a log report.
308
* Somewhere in mod_dav_svn, there will be an implementation, R, of
309
* the `svn_log_message_receiver_t' function type. Some other
310
* function in mod_dav_svn will use svn_repos_get_logs() to loop R
311
* over the log messages, and the successive invocations of R will
312
* collectively transmit the report back here, where we parse the
313
* report and invoke RECEIVER (which is an entirely separate
314
* instance of `svn_log_message_receiver_t') on each individual
315
* message in that report.
319
svn_ra_dav__session_t *ras = session->priv;
320
svn_stringbuf_t *request_body = svn_stringbuf_create("", ras->pool);
322
svn_string_t bc_url, bc_relative;
323
const char *final_bc_url;
324
svn_revnum_t use_rev;
326
/* ### todo: I don't understand why the static, file-global
327
variables shared by update and status are called `report_head'
328
and `report_tail', instead of `request_head' and `request_tail'.
329
Maybe Greg can explain? Meanwhile, I'm tentatively using
330
"request_*" for my local vars below. */
332
static const char log_request_head[]
333
= "<S:log-report xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR;
335
static const char log_request_tail[] = "</S:log-report>" DEBUG_CR;
337
static const svn_ra_dav__xml_elm_t log_report_elements[] =
339
{ SVN_XML_NAMESPACE, "log-report", ELEM_log_report, 0 },
340
{ SVN_XML_NAMESPACE, "log-item", ELEM_log_item, 0 },
341
{ SVN_XML_NAMESPACE, "date", ELEM_log_date, SVN_RA_DAV__XML_CDATA },
342
{ SVN_XML_NAMESPACE, "added-path", ELEM_added_path,
343
SVN_RA_DAV__XML_CDATA },
344
{ SVN_XML_NAMESPACE, "deleted-path", ELEM_deleted_path,
345
SVN_RA_DAV__XML_CDATA },
346
{ SVN_XML_NAMESPACE, "modified-path", ELEM_modified_path,
347
SVN_RA_DAV__XML_CDATA },
348
{ SVN_XML_NAMESPACE, "replaced-path", ELEM_replaced_path,
349
SVN_RA_DAV__XML_CDATA },
350
{ "DAV:", "version-name", ELEM_version_name, SVN_RA_DAV__XML_CDATA },
351
{ "DAV:", "creator-displayname", ELEM_creator_displayname,
352
SVN_RA_DAV__XML_CDATA },
353
{ "DAV:", "comment", ELEM_comment, SVN_RA_DAV__XML_CDATA },
358
/* Construct the request body. */
359
svn_stringbuf_appendcstr(request_body, log_request_head);
360
svn_stringbuf_appendcstr(request_body,
361
apr_psprintf(ras->pool,
362
"<S:start-revision>%ld"
363
"</S:start-revision>", start));
364
svn_stringbuf_appendcstr(request_body,
365
apr_psprintf(ras->pool,
366
"<S:end-revision>%ld"
367
"</S:end-revision>", end));
370
svn_stringbuf_appendcstr(request_body,
371
apr_psprintf(ras->pool,
372
"<S:limit>%d</S:limit>", limit));
375
if (discover_changed_paths)
377
svn_stringbuf_appendcstr(request_body,
378
apr_psprintf(ras->pool,
379
"<S:discover-changed-paths/>"));
382
if (strict_node_history)
384
svn_stringbuf_appendcstr(request_body,
385
apr_psprintf(ras->pool,
386
"<S:strict-node-history/>"));
391
for (i = 0; i < paths->nelts; i++)
393
const char *this_path =
394
apr_xml_quote_string(ras->pool,
395
((const char **)paths->elts)[i],
397
svn_stringbuf_appendcstr(request_body, "<S:path>");
398
svn_stringbuf_appendcstr(request_body, this_path);
399
svn_stringbuf_appendcstr(request_body, "</S:path>");
403
svn_stringbuf_appendcstr(request_body, log_request_tail);
405
lb.receiver = receiver;
406
lb.receiver_baton = receiver_baton;
407
lb.subpool = svn_pool_create (ras->pool);
411
reset_log_item (&lb);
413
/* ras's URL may not exist in HEAD, and thus it's not safe to send
414
it as the main argument to the REPORT request; it might cause
415
dav_get_resource() to choke on the server. So instead, we pass a
416
baseline-collection URL, which we get from the largest of the
417
START and END revisions. */
418
use_rev = (start > end) ? start : end;
419
SVN_ERR( svn_ra_dav__get_baseline_info(NULL, &bc_url, &bc_relative, NULL,
420
ras->sess, ras->url, use_rev,
422
final_bc_url = svn_path_url_add_component(bc_url.data, bc_relative.data,
426
SVN_ERR( svn_ra_dav__parsed_request_compat(ras->sess,
445
svn_pool_destroy (lb.subpool);