Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_client / log.c
blobfb512a68c8597c5ddd1e3d644b3a10f3854b4c4f
1 /*
2 * log.c: return log messages
4 * ====================================================================
5 * Copyright (c) 2000-2008 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 /* ==================================================================== */
23 /*** Includes. ***/
25 #define APR_WANT_STRFUNC
26 #include <apr_want.h>
28 #include <apr_strings.h>
29 #include <apr_pools.h>
31 #include "client.h"
33 #include "svn_pools.h"
34 #include "svn_client.h"
35 #include "svn_compat.h"
36 #include "svn_error.h"
37 #include "svn_path.h"
38 #include "svn_sorts.h"
40 #include "svn_private_config.h"
41 #include "private/svn_wc_private.h"
44 /*** Getting misc. information ***/
46 /* A log callback conforming to the svn_log_entry_receiver_t
47 interface for obtaining the last revision of a node at a path and
48 storing it in *BATON (an svn_revnum_t). */
49 static svn_error_t *
50 revnum_receiver(void *baton,
51 svn_log_entry_t *log_entry,
52 apr_pool_t *pool)
54 if (SVN_IS_VALID_REVNUM(log_entry->revision))
55 *((svn_revnum_t *) baton) = log_entry->revision;
57 return SVN_NO_ERROR;
60 svn_error_t *
61 svn_client__oldest_rev_at_path(svn_revnum_t *oldest_rev,
62 svn_ra_session_t *ra_session,
63 const char *rel_path,
64 svn_revnum_t rev,
65 apr_pool_t *pool)
67 apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
68 apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
69 *oldest_rev = SVN_INVALID_REVNUM;
70 APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
72 /* Trace back in history to find the revision at which this node
73 was created (copied or added). */
74 return svn_ra_get_log2(ra_session, rel_paths, 1, rev, 1, FALSE, TRUE,
75 FALSE, revprops, revnum_receiver, oldest_rev, pool);
78 /* The baton for use with copyfrom_info_receiver(). */
79 typedef struct
81 const char *target_path;
82 const char *path;
83 svn_revnum_t rev;
84 apr_pool_t *pool;
85 } copyfrom_info_t;
87 /* A log callback conforming to the svn_log_message_receiver_t
88 interface for obtaining the copy source of a node at a path and
89 storing it in *BATON (a struct copyfrom_info_t *).
90 Implements svn_log_entry_receiver_t. */
91 static svn_error_t *
92 copyfrom_info_receiver(void *baton,
93 svn_log_entry_t *log_entry,
94 apr_pool_t *pool)
96 copyfrom_info_t *copyfrom_info = baton;
97 if (copyfrom_info->path)
98 /* The copy source has already been found. */
99 return SVN_NO_ERROR;
101 if (log_entry->changed_paths)
103 int i;
104 const char *path;
105 svn_log_changed_path_t *changed_path;
106 /* Sort paths into depth-first order. */
107 apr_array_header_t *sorted_changed_paths =
108 svn_sort__hash(log_entry->changed_paths,
109 svn_sort_compare_items_as_paths, pool);
111 for (i = (sorted_changed_paths->nelts -1) ; i >= 0 ; i--)
113 svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_changed_paths, i,
114 svn_sort__item_t);
115 path = item->key;
116 changed_path = item->value;
118 /* Consider only the path we're interested in. */
119 if (changed_path->copyfrom_path &&
120 SVN_IS_VALID_REVNUM(changed_path->copyfrom_rev) &&
121 svn_path_is_ancestor(path, copyfrom_info->target_path))
123 /* Copy source found! Determine path and note revision. */
124 if (strcmp(path, copyfrom_info->target_path) == 0)
126 /* We have the details for a direct copy to
127 copyfrom_info->target_path. */
128 copyfrom_info->path =
129 apr_pstrdup(copyfrom_info->pool,
130 changed_path->copyfrom_path);
132 else
134 /* We have a parent of copyfrom_info->target_path. */
135 copyfrom_info->path =
136 apr_pstrcat(copyfrom_info->pool,
137 changed_path->copyfrom_path,
138 copyfrom_info->target_path +
139 strlen(path), NULL);
141 copyfrom_info->rev = changed_path->copyfrom_rev;
142 break;
146 return SVN_NO_ERROR;
149 svn_error_t *
150 svn_client__get_copy_source(const char *path_or_url,
151 const svn_opt_revision_t *revision,
152 const char **copyfrom_path,
153 svn_revnum_t *copyfrom_rev,
154 svn_client_ctx_t *ctx,
155 apr_pool_t *pool)
157 svn_error_t *err;
158 copyfrom_info_t copyfrom_info = { NULL, NULL, SVN_INVALID_REVNUM, pool };
159 apr_array_header_t *targets = apr_array_make(pool, 1, sizeof(path_or_url));
160 apr_pool_t *sesspool = svn_pool_create(pool);
161 svn_ra_session_t *ra_session;
162 svn_revnum_t at_rev;
163 const char *at_url;
165 SVN_ERR(svn_client__ra_session_from_path(&ra_session, &at_rev, &at_url,
166 path_or_url, NULL,
167 revision, revision,
168 ctx, sesspool));
169 SVN_ERR(svn_client__path_relative_to_root(&copyfrom_info.target_path,
170 path_or_url, NULL, TRUE,
171 ra_session, NULL, pool));
172 APR_ARRAY_PUSH(targets, const char *) = "";
174 /* Find the copy source. Trace back in history to find the revision
175 at which this node was created (copied or added). */
176 err = svn_ra_get_log2(ra_session, targets, at_rev, 1, 0, TRUE,
177 TRUE, FALSE,
178 apr_array_make(pool, 0, sizeof(const char *)),
179 copyfrom_info_receiver, &copyfrom_info, pool);
181 svn_pool_destroy(sesspool);
183 if (err)
185 if (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
186 err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
188 /* A locally-added but uncommitted versioned resource won't
189 exist in the repository. */
190 svn_error_clear(err);
191 err = SVN_NO_ERROR;
193 *copyfrom_path = NULL;
194 *copyfrom_rev = SVN_INVALID_REVNUM;
196 return err;
199 *copyfrom_path = copyfrom_info.path;
200 *copyfrom_rev = copyfrom_info.rev;
201 return SVN_NO_ERROR;
205 /* compatibility with pre-1.5 servers, which send only author/date/log
206 *revprops in log entries */
207 typedef struct
209 svn_client_ctx_t *ctx;
210 /* ra session for retrieving revprops from old servers */
211 svn_ra_session_t *ra_session;
212 /* caller's list of requested revprops, receiver, and baton */
213 const apr_array_header_t *revprops;
214 svn_log_entry_receiver_t receiver;
215 void *baton;
216 } pre_15_receiver_baton_t;
218 static svn_error_t *
219 pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
221 pre_15_receiver_baton_t *rb = baton;
223 if (log_entry->revision == SVN_INVALID_REVNUM)
224 return rb->receiver(rb->baton, log_entry, pool);
226 /* If only some revprops are requested, get them one at a time on the
227 second ra connection. If all are requested, get them all with
228 svn_ra_rev_proplist. This avoids getting unrequested revprops (which
229 may be arbitrarily large), but means one round-trip per requested
230 revprop. epg isn't entirely sure which should be optimized for. */
231 if (rb->revprops)
233 int i;
234 svn_boolean_t want_author, want_date, want_log;
235 want_author = want_date = want_log = FALSE;
236 for (i = 0; i < rb->revprops->nelts; i++)
238 const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
239 svn_string_t *value;
241 /* If a standard revprop is requested, we know it is already in
242 log_entry->revprops if available. */
243 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
245 want_author = TRUE;
246 continue;
248 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
250 want_date = TRUE;
251 continue;
253 if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
255 want_log = TRUE;
256 continue;
258 SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
259 name, &value, pool));
260 if (log_entry->revprops == NULL)
261 log_entry->revprops = apr_hash_make(pool);
262 apr_hash_set(log_entry->revprops, (const void *)name,
263 APR_HASH_KEY_STRING, (const void *)value);
265 if (log_entry->revprops)
267 /* Pre-1.5 servers send the standard revprops unconditionally;
268 clear those the caller doesn't want. */
269 if (!want_author)
270 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
271 APR_HASH_KEY_STRING, NULL);
272 if (!want_date)
273 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
274 APR_HASH_KEY_STRING, NULL);
275 if (!want_log)
276 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
277 APR_HASH_KEY_STRING, NULL);
280 else
282 SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
283 &log_entry->revprops, pool));
286 return rb->receiver(rb->baton, log_entry, pool);
290 /*** Public Interface. ***/
293 svn_error_t *
294 svn_client_log4(const apr_array_header_t *targets,
295 const svn_opt_revision_t *peg_revision,
296 const svn_opt_revision_t *start,
297 const svn_opt_revision_t *end,
298 int limit,
299 svn_boolean_t discover_changed_paths,
300 svn_boolean_t strict_node_history,
301 svn_boolean_t include_merged_revisions,
302 const apr_array_header_t *revprops,
303 svn_log_entry_receiver_t real_receiver,
304 void *real_receiver_baton,
305 svn_client_ctx_t *ctx,
306 apr_pool_t *pool)
308 svn_ra_session_t *ra_session;
309 const char *url_or_path;
310 const char *actual_url;
311 apr_array_header_t *condensed_targets;
312 svn_revnum_t ignored_revnum;
313 svn_opt_revision_t session_opt_rev;
314 const char *ra_target;
316 if ((start->kind == svn_opt_revision_unspecified)
317 || (end->kind == svn_opt_revision_unspecified))
319 return svn_error_create
320 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
321 _("Missing required revision specification"));
324 url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
326 /* Use the passed URL, if there is one. */
327 if (svn_path_is_url(url_or_path))
329 if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind) ||
330 SVN_CLIENT__REVKIND_NEEDS_WC(start->kind) ||
331 SVN_CLIENT__REVKIND_NEEDS_WC(end->kind))
333 return svn_error_create
334 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
335 _("Revision type requires a working copy path, not a URL"));
337 /* Initialize this array, since we'll be building it below */
338 condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
340 /* The logic here is this: If we get passed one argument, we assume
341 it is the full URL to a file/dir we want log info for. If we get
342 a URL plus some paths, then we assume that the URL is the base,
343 and that the paths passed are relative to it. */
344 if (targets->nelts > 1)
346 int i;
348 /* We have some paths, let's use them. Start after the URL. */
349 for (i = 1; i < targets->nelts; i++)
350 APR_ARRAY_PUSH(condensed_targets, const char *) =
351 APR_ARRAY_IDX(targets, i, const char *);
353 else
355 /* If we have a single URL, then the session will be rooted at
356 it, so just send an empty string for the paths we are
357 interested in. */
358 APR_ARRAY_PUSH(condensed_targets, const char *) = "";
361 else
363 svn_wc_adm_access_t *adm_access;
364 apr_array_header_t *target_urls;
365 apr_array_header_t *real_targets;
366 apr_pool_t *iterpool;
367 int i;
369 /* See FIXME about multiple wc targets, below. */
370 if (targets->nelts > 1)
371 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
372 _("When specifying working copy paths, only "
373 "one target may be given"));
375 /* Get URLs for each target */
376 target_urls = apr_array_make(pool, 1, sizeof(const char *));
377 real_targets = apr_array_make(pool, 1, sizeof(const char *));
378 iterpool = svn_pool_create(pool);
379 for (i = 0; i < targets->nelts; i++)
381 const svn_wc_entry_t *entry;
382 const char *URL;
383 const char *target = APR_ARRAY_IDX(targets, i, const char *);
385 svn_pool_clear(iterpool);
386 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, target,
387 FALSE, 0, ctx->cancel_func,
388 ctx->cancel_baton, iterpool));
389 SVN_ERR(svn_wc__entry_versioned(&entry, target, adm_access, FALSE,
390 iterpool));
392 if (! entry->url)
393 return svn_error_createf
394 (SVN_ERR_ENTRY_MISSING_URL, NULL,
395 _("Entry '%s' has no URL"),
396 svn_path_local_style(target, pool));
398 URL = apr_pstrdup(pool, entry->url);
399 SVN_ERR(svn_wc_adm_close(adm_access));
400 APR_ARRAY_PUSH(target_urls, const char *) = URL;
401 APR_ARRAY_PUSH(real_targets, const char *) = target;
403 svn_pool_destroy(iterpool);
405 /* if we have no valid target_urls, just exit. */
406 if (target_urls->nelts == 0)
407 return SVN_NO_ERROR;
409 /* Find the base URL and condensed targets relative to it. */
410 SVN_ERR(svn_path_condense_targets(&url_or_path, &condensed_targets,
411 target_urls, TRUE, pool));
413 if (condensed_targets->nelts == 0)
414 APR_ARRAY_PUSH(condensed_targets, const char *) = "";
416 /* 'targets' now becomes 'real_targets', which has bogus,
417 unversioned things removed from it. */
418 targets = real_targets;
421 /* Determine the revision to open the RA session to. */
422 if (start->kind == svn_opt_revision_number &&
423 end->kind == svn_opt_revision_number)
424 session_opt_rev = (start->value.number > end->value.number ?
425 *start : *end);
426 else if (start->kind == svn_opt_revision_date &&
427 end->kind == svn_opt_revision_date)
428 session_opt_rev = (start->value.date > end->value.date ? *start : *end);
429 else
430 session_opt_rev.kind = svn_opt_revision_unspecified;
433 /* If this is a revision type that requires access to the working copy,
434 * we use our initial target path to figure out where to root the RA
435 * session, otherwise we use our URL. */
436 if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
437 SVN_ERR(svn_path_condense_targets(&ra_target, NULL, targets, TRUE, pool));
438 else
439 ra_target = url_or_path;
441 SVN_ERR(svn_client__ra_session_from_path(&ra_session, &ignored_revnum,
442 &actual_url, ra_target, NULL,
443 peg_revision, &session_opt_rev,
444 ctx, pool));
447 /* It's a bit complex to correctly handle the special revision words
448 * such as "BASE", "COMMITTED", and "PREV". For example, if the
449 * user runs
451 * $ svn log -rCOMMITTED foo.txt bar.c
453 * which committed rev should be used? The younger of the two? The
454 * first one? Should we just error?
456 * None of the above, I think. Rather, the committed rev of each
457 * target in turn should be used. This is what most users would
458 * expect, and is the most useful interpretation. Of course, this
459 * goes for the other dynamic (i.e., local) revision words too.
461 * Note that the code to do this is a bit more complex than a simple
462 * loop, because the user might run
464 * $ svn log -rCOMMITTED:42 foo.txt bar.c
466 * in which case we want to avoid recomputing the static revision on
467 * every iteration.
469 * ### FIXME: However, we can't yet handle multiple wc targets anyway.
471 * We used to iterate over each target in turn, getting the logs for
472 * the named range. This led to revisions being printed in strange
473 * order or being printed more than once. This is issue 1550.
475 * In r11599, jpieper blocked multiple wc targets in svn/log-cmd.c,
476 * meaning this block not only doesn't work right in that case, but isn't
477 * even testable that way (svn has no unit test suite; we can only test
478 * via the svn command). So, that check is now moved into this function
479 * (see above).
481 * kfogel ponders future enhancements in r4186:
482 * I think that's okay behavior, since the sense of the command is
483 * that one wants a particular range of logs for *this* file, then
484 * another range for *that* file, and so on. But we should
485 * probably put some sort of separator header between the log
486 * groups. Of course, libsvn_client can't just print stuff out --
487 * it has to take a callback from the client to do that. So we
488 * need to define that callback interface, then have the command
489 * line client pass one down here.
491 * epg wonders if the repository could send a unified stream of log
492 * entries if the paths and revisions were passed down.
495 svn_revnum_t start_revnum, end_revnum, youngest_rev = SVN_INVALID_REVNUM;
496 const char *path = APR_ARRAY_IDX(targets, 0, const char *);
497 svn_boolean_t has_log_revprops;
499 SVN_ERR(svn_client__get_revision_number
500 (&start_revnum, &youngest_rev, ra_session, start, path, pool));
501 SVN_ERR(svn_client__get_revision_number
502 (&end_revnum, &youngest_rev, ra_session, end, path, pool));
504 SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
505 SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
506 if (has_log_revprops)
507 return svn_ra_get_log2(ra_session,
508 condensed_targets,
509 start_revnum,
510 end_revnum,
511 limit,
512 discover_changed_paths,
513 strict_node_history,
514 include_merged_revisions,
515 revprops,
516 real_receiver,
517 real_receiver_baton,
518 pool);
519 else
521 /* See above pre-1.5 notes. */
522 pre_15_receiver_baton_t rb;
523 rb.ctx = ctx;
524 SVN_ERR(svn_client_open_ra_session(&rb.ra_session, actual_url,
525 ctx, pool));
526 rb.revprops = revprops;
527 rb.receiver = real_receiver;
528 rb.baton = real_receiver_baton;
529 SVN_ERR(svn_client__ra_session_from_path(&ra_session,
530 &ignored_revnum,
531 &actual_url, ra_target, NULL,
532 peg_revision,
533 &session_opt_rev,
534 ctx, pool));
535 return svn_ra_get_log2(ra_session,
536 condensed_targets,
537 start_revnum,
538 end_revnum,
539 limit,
540 discover_changed_paths,
541 strict_node_history,
542 include_merged_revisions,
543 svn_compat_log_revprops_in(pool),
544 pre_15_receiver,
545 &rb,
546 pool);
551 svn_error_t *
552 svn_client_log3(const apr_array_header_t *targets,
553 const svn_opt_revision_t *peg_revision,
554 const svn_opt_revision_t *start,
555 const svn_opt_revision_t *end,
556 int limit,
557 svn_boolean_t discover_changed_paths,
558 svn_boolean_t strict_node_history,
559 svn_log_message_receiver_t receiver,
560 void *receiver_baton,
561 svn_client_ctx_t *ctx,
562 apr_pool_t *pool)
564 svn_log_entry_receiver_t receiver2;
565 void *receiver2_baton;
567 svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
568 receiver, receiver_baton,
569 pool);
571 return svn_client_log4(targets, peg_revision, start, end, limit,
572 discover_changed_paths, strict_node_history, FALSE,
573 svn_compat_log_revprops_in(pool),
574 receiver2, receiver2_baton, ctx, pool);
577 svn_error_t *
578 svn_client_log2(const apr_array_header_t *targets,
579 const svn_opt_revision_t *start,
580 const svn_opt_revision_t *end,
581 int limit,
582 svn_boolean_t discover_changed_paths,
583 svn_boolean_t strict_node_history,
584 svn_log_message_receiver_t receiver,
585 void *receiver_baton,
586 svn_client_ctx_t *ctx,
587 apr_pool_t *pool)
589 svn_opt_revision_t peg_revision;
590 peg_revision.kind = svn_opt_revision_unspecified;
591 return svn_client_log3(targets, &peg_revision, start, end, limit,
592 discover_changed_paths, strict_node_history,
593 receiver, receiver_baton, ctx, pool);
596 svn_error_t *
597 svn_client_log(const apr_array_header_t *targets,
598 const svn_opt_revision_t *start,
599 const svn_opt_revision_t *end,
600 svn_boolean_t discover_changed_paths,
601 svn_boolean_t strict_node_history,
602 svn_log_message_receiver_t receiver,
603 void *receiver_baton,
604 svn_client_ctx_t *ctx,
605 apr_pool_t *pool)
607 svn_error_t *err = SVN_NO_ERROR;
609 err = svn_client_log2(targets, start, end, 0, discover_changed_paths,
610 strict_node_history, receiver, receiver_baton, ctx,
611 pool);
613 /* Special case: If there have been no commits, we'll get an error
614 * for requesting log of a revision higher than 0. But the
615 * default behavior of "svn log" is to give revisions HEAD through
616 * 1, on the assumption that HEAD >= 1.
618 * So if we got that error for that reason, and it looks like the
619 * user was just depending on the defaults (rather than explicitly
620 * requesting the log for revision 1), then we don't error. Instead
621 * we just invoke the receiver manually on a hand-constructed log
622 * message for revision 0.
624 * See also http://subversion.tigris.org/issues/show_bug.cgi?id=692.
626 if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
627 && (start->kind == svn_opt_revision_head)
628 && ((end->kind == svn_opt_revision_number)
629 && (end->value.number == 1)))
632 /* We don't need to check if HEAD is 0, because that must be the case,
633 * by logical deduction: The revision range specified is HEAD:1.
634 * HEAD cannot not exist, so the revision to which "no such revision"
635 * applies is 1. If revision 1 does not exist, then HEAD is 0.
636 * Hence, we deduce the repository is empty without needing access
637 * to further information. */
639 svn_error_clear(err);
640 err = SVN_NO_ERROR;
642 /* Log receivers are free to handle revision 0 specially... But
643 just in case some don't, we make up a message here. */
644 SVN_ERR(receiver(receiver_baton,
645 NULL, 0, "", "", _("No commits in repository"),
646 pool));
649 return err;