In the command-line client, forbid
[svn.git] / subversion / libsvn_client / log.c
blobae44f05a30f5f417a41e76682c16b01ec3d19a95
1 /*
2 * log.c: return log messages
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 /* ==================================================================== */
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_client.h"
34 #include "svn_compat.h"
35 #include "svn_error.h"
36 #include "svn_path.h"
37 #include "svn_sorts.h"
39 #include "svn_private_config.h"
40 #include "private/svn_wc_private.h"
43 /*** Getting misc. information ***/
45 /* A log callback conforming to the svn_log_entry_receiver_t
46 interface for obtaining the last revision of a node at a path and
47 storing it in *BATON (an svn_revnum_t). */
48 static svn_error_t *
49 revnum_receiver(void *baton,
50 svn_log_entry_t *log_entry,
51 apr_pool_t *pool)
53 if (SVN_IS_VALID_REVNUM(log_entry->revision))
54 *((svn_revnum_t *) baton) = log_entry->revision;
56 return SVN_NO_ERROR;
59 svn_error_t *
60 svn_client__oldest_rev_at_path(svn_revnum_t *oldest_rev,
61 svn_ra_session_t *ra_session,
62 const char *rel_path,
63 svn_revnum_t rev,
64 apr_pool_t *pool)
66 apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path));
67 apr_array_header_t *revprops = apr_array_make(pool, 0, sizeof(char *));
68 *oldest_rev = SVN_INVALID_REVNUM;
69 APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
71 /* Trace back in history to find the revision at which this node
72 was created (copied or added). */
73 return svn_ra_get_log2(ra_session, rel_paths, 1, rev, 1, FALSE, TRUE,
74 FALSE, revprops, revnum_receiver, oldest_rev, pool);
77 /* The baton for use with copyfrom_info_receiver(). */
78 typedef struct
80 const char *target_path;
81 const char *path;
82 svn_revnum_t rev;
83 apr_pool_t *pool;
84 } copyfrom_info_t;
86 /* A log callback conforming to the svn_log_message_receiver_t
87 interface for obtaining the copy source of a node at a path and
88 storing it in *BATON (a struct copyfrom_info_t *). */
89 static svn_error_t *
90 copyfrom_info_receiver(void *baton,
91 apr_hash_t *changed_paths,
92 svn_revnum_t revision,
93 const char *author,
94 const char *date,
95 const char *message,
96 apr_pool_t *pool)
98 copyfrom_info_t *copyfrom_info = baton;
99 if (copyfrom_info->path)
100 /* The copy source has already been found. */
101 return SVN_NO_ERROR;
103 if (changed_paths)
105 int i;
106 const char *path;
107 svn_log_changed_path_t *changed_path;
108 /* Sort paths into depth-first order. */
109 apr_array_header_t *sorted_changed_paths =
110 svn_sort__hash(changed_paths, svn_sort_compare_items_as_paths, pool);
112 for (i = (sorted_changed_paths->nelts -1) ; i >= 0 ; i--)
114 svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_changed_paths, i,
115 svn_sort__item_t);
116 path = item->key;
117 changed_path = item->value;
119 /* Consider only the path we're interested in. */
120 if (changed_path->copyfrom_path &&
121 SVN_IS_VALID_REVNUM(changed_path->copyfrom_rev) &&
122 svn_path_is_ancestor(path, copyfrom_info->target_path))
124 /* Copy source found! Determine path and note revision. */
125 if (strcmp(path, copyfrom_info->target_path) == 0)
127 /* We have the details for a direct copy to
128 copyfrom_info->target_path. */
129 copyfrom_info->path =
130 apr_pstrdup(copyfrom_info->pool,
131 changed_path->copyfrom_path);
133 else
135 /* We have a parent of copyfrom_info->target_path. */
136 copyfrom_info->path =
137 apr_pstrcat(copyfrom_info->pool,
138 changed_path->copyfrom_path,
139 copyfrom_info->target_path +
140 strlen(path), NULL);
142 copyfrom_info->rev = changed_path->copyfrom_rev;
143 break;
147 return SVN_NO_ERROR;
150 svn_error_t *
151 svn_client__get_copy_source(const char *path_or_url,
152 const svn_opt_revision_t *revision,
153 const char **copyfrom_path,
154 svn_revnum_t *copyfrom_rev,
155 svn_client_ctx_t *ctx,
156 apr_pool_t *pool)
158 svn_error_t *err;
159 copyfrom_info_t copyfrom_info = { NULL, NULL, SVN_INVALID_REVNUM, pool };
160 apr_array_header_t *targets = apr_array_make(pool, 1, sizeof(path_or_url));
161 svn_opt_revision_t oldest_rev;
163 oldest_rev.kind = svn_opt_revision_number;
164 oldest_rev.value.number = 1;
167 svn_ra_session_t *ra_session;
168 svn_revnum_t at_rev;
169 const char *at_url;
170 SVN_ERR(svn_client__ra_session_from_path(&ra_session, &at_rev, &at_url,
171 path_or_url, NULL,
172 revision, revision,
173 ctx, pool));
175 SVN_ERR(svn_client__path_relative_to_root(&copyfrom_info.target_path,
176 path_or_url, NULL, TRUE,
177 ra_session, NULL, pool));
180 APR_ARRAY_PUSH(targets, const char *) = path_or_url;
182 /* Find the copy source. Trace back in history to find the revision
183 at which this node was created (copied or added). */
184 err = svn_client_log3(targets, revision, revision, &oldest_rev, 0,
185 TRUE, TRUE, copyfrom_info_receiver, &copyfrom_info,
186 ctx, pool);
187 /* ### Reuse ra_session by way of svn_ra_get_log()?
188 err = svn_ra_get_log(ra_session, rel_paths, revision, 1, 0, TRUE, TRUE,
189 copyfrom_info_receiver, &copyfrom_info, pool);
191 if (err)
193 if (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
194 err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
196 /* A locally-added but uncommitted versioned resource won't
197 exist in the repository. */
198 svn_error_clear(err);
199 err = SVN_NO_ERROR;
201 *copyfrom_path = NULL;
202 *copyfrom_rev = SVN_INVALID_REVNUM;
204 return err;
207 *copyfrom_path = copyfrom_info.path;
208 *copyfrom_rev = copyfrom_info.rev;
209 return SVN_NO_ERROR;
213 /* compatibility with pre-1.5 servers, which send only author/date/log
214 *revprops in log entries */
215 typedef struct
217 svn_client_ctx_t *ctx;
218 /* ra session for retrieving revprops from old servers */
219 svn_ra_session_t *ra_session;
220 /* caller's list of requested revprops, receiver, and baton */
221 apr_array_header_t *revprops;
222 svn_log_entry_receiver_t receiver;
223 void *baton;
224 } pre_15_receiver_baton_t;
226 static svn_error_t *
227 pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
229 pre_15_receiver_baton_t *rb = baton;
231 if (log_entry->revision == SVN_INVALID_REVNUM)
232 return rb->receiver(rb->baton, log_entry, pool);
234 /* If only some revprops are requested, get them one at a time on the
235 second ra connection. If all are requested, get them all with
236 svn_ra_rev_proplist. This avoids getting unrequested revprops (which
237 may be arbitrarily large), but means one round-trip per requested
238 revprop. epg isn't entirely sure which should be optimized for. */
239 if (rb->revprops)
241 int i;
242 svn_boolean_t want_author, want_date, want_log;
243 want_author = want_date = want_log = FALSE;
244 for (i = 0; i < rb->revprops->nelts; i++)
246 const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
247 svn_string_t *value;
249 /* If a standard revprop is requested, we know it is already in
250 log_entry->revprops if available. */
251 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
253 want_author = TRUE;
254 continue;
256 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
258 want_date = TRUE;
259 continue;
261 if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
263 want_log = TRUE;
264 continue;
266 SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
267 name, &value, pool));
268 if (log_entry->revprops == NULL)
269 log_entry->revprops = apr_hash_make(pool);
270 apr_hash_set(log_entry->revprops, (const void *)name,
271 APR_HASH_KEY_STRING, (const void *)value);
273 if (log_entry->revprops)
275 /* Pre-1.5 servers send the standard revprops unconditionally;
276 clear those the caller doesn't want. */
277 if (!want_author)
278 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
279 APR_HASH_KEY_STRING, NULL);
280 if (!want_date)
281 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_DATE,
282 APR_HASH_KEY_STRING, NULL);
283 if (!want_log)
284 apr_hash_set(log_entry->revprops, SVN_PROP_REVISION_LOG,
285 APR_HASH_KEY_STRING, NULL);
288 else
290 SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
291 &log_entry->revprops, pool));
294 return rb->receiver(rb->baton, log_entry, pool);
298 /*** Public Interface. ***/
301 svn_error_t *
302 svn_client_log4(const apr_array_header_t *targets,
303 const svn_opt_revision_t *peg_revision,
304 const svn_opt_revision_t *start,
305 const svn_opt_revision_t *end,
306 int limit,
307 svn_boolean_t discover_changed_paths,
308 svn_boolean_t strict_node_history,
309 svn_boolean_t include_merged_revisions,
310 apr_array_header_t *revprops,
311 svn_log_entry_receiver_t real_receiver,
312 void *real_receiver_baton,
313 svn_client_ctx_t *ctx,
314 apr_pool_t *pool)
316 svn_ra_session_t *ra_session;
317 const char *url_or_path;
318 const char *actual_url;
319 apr_array_header_t *condensed_targets;
320 svn_revnum_t ignored_revnum;
321 svn_opt_revision_t session_opt_rev;
322 const char *ra_target;
324 if ((start->kind == svn_opt_revision_unspecified)
325 || (end->kind == svn_opt_revision_unspecified))
327 return svn_error_create
328 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
329 _("Missing required revision specification"));
332 url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
334 /* Use the passed URL, if there is one. */
335 if (svn_path_is_url(url_or_path))
337 if (peg_revision->kind == svn_opt_revision_base
338 || peg_revision->kind == svn_opt_revision_committed
339 || peg_revision->kind == svn_opt_revision_previous)
340 return svn_error_create
341 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
342 _("Revision type requires a working copy path, not a URL"));
344 /* Initialize this array, since we'll be building it below */
345 condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
347 /* The logic here is this: If we get passed one argument, we assume
348 it is the full URL to a file/dir we want log info for. If we get
349 a URL plus some paths, then we assume that the URL is the base,
350 and that the paths passed are relative to it. */
351 if (targets->nelts > 1)
353 int i;
355 /* We have some paths, let's use them. Start after the URL. */
356 for (i = 1; i < targets->nelts; i++)
357 APR_ARRAY_PUSH(condensed_targets, const char *) =
358 APR_ARRAY_IDX(targets, i, const char *);
360 else
362 /* If we have a single URL, then the session will be rooted at
363 it, so just send an empty string for the paths we are
364 interested in. */
365 APR_ARRAY_PUSH(condensed_targets, const char *) = "";
368 else
370 svn_wc_adm_access_t *adm_access;
371 apr_array_header_t *target_urls;
372 apr_array_header_t *real_targets;
373 int i;
375 /* See FIXME about multiple wc targets, below. */
376 if (targets->nelts > 1)
377 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
378 _("When specifying working copy paths, only "
379 "one target may be given"));
381 /* Get URLs for each target */
382 target_urls = apr_array_make(pool, 1, sizeof(const char *));
383 real_targets = apr_array_make(pool, 1, sizeof(const char *));
384 for (i = 0; i < targets->nelts; i++)
386 const svn_wc_entry_t *entry;
387 const char *URL;
388 const char *target = APR_ARRAY_IDX(targets, i, const char *);
389 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, target,
390 FALSE, 0, ctx->cancel_func,
391 ctx->cancel_baton, pool));
392 SVN_ERR(svn_wc__entry_versioned(&entry, target, adm_access, FALSE,
393 pool));
395 if (! entry->url)
396 return svn_error_createf
397 (SVN_ERR_ENTRY_MISSING_URL, NULL,
398 _("Entry '%s' has no URL"),
399 svn_path_local_style(target, pool));
401 URL = apr_pstrdup(pool, entry->url);
402 SVN_ERR(svn_wc_adm_close(adm_access));
403 APR_ARRAY_PUSH(target_urls, const char *) = URL;
404 APR_ARRAY_PUSH(real_targets, const char *) = target;
407 /* if we have no valid target_urls, just exit. */
408 if (target_urls->nelts == 0)
409 return SVN_NO_ERROR;
411 /* Find the base URL and condensed targets relative to it. */
412 SVN_ERR(svn_path_condense_targets(&url_or_path, &condensed_targets,
413 target_urls, TRUE, pool));
415 if (condensed_targets->nelts == 0)
416 APR_ARRAY_PUSH(condensed_targets, const char *) = "";
418 /* 'targets' now becomes 'real_targets', which has bogus,
419 unversioned things removed from it. */
420 targets = real_targets;
423 /* Determine the revision to open the RA session to. */
424 if (start->kind == svn_opt_revision_number &&
425 end->kind == svn_opt_revision_number)
426 session_opt_rev = (start->value.number > end->value.number ?
427 *start : *end);
428 else if (start->kind == svn_opt_revision_date &&
429 end->kind == svn_opt_revision_date)
430 session_opt_rev = (start->value.date > end->value.date ? *start : *end);
431 else
432 session_opt_rev.kind = svn_opt_revision_unspecified;
435 /* If this is a revision type that requires access to the working copy,
436 * we use our initial target path to figure out where to root the RA
437 * session, otherwise we use our URL. */
438 if (peg_revision->kind == svn_opt_revision_base
439 || peg_revision->kind == svn_opt_revision_committed
440 || peg_revision->kind == svn_opt_revision_previous
441 || peg_revision->kind == svn_opt_revision_working)
442 SVN_ERR(svn_path_condense_targets(&ra_target, NULL, targets, TRUE, pool));
443 else
444 ra_target = url_or_path;
446 SVN_ERR(svn_client__ra_session_from_path(&ra_session, &ignored_revnum,
447 &actual_url, ra_target, NULL,
448 peg_revision, &session_opt_rev,
449 ctx, pool));
452 /* It's a bit complex to correctly handle the special revision words
453 * such as "BASE", "COMMITTED", and "PREV". For example, if the
454 * user runs
456 * $ svn log -rCOMMITTED foo.txt bar.c
458 * which committed rev should be used? The younger of the two? The
459 * first one? Should we just error?
461 * None of the above, I think. Rather, the committed rev of each
462 * target in turn should be used. This is what most users would
463 * expect, and is the most useful interpretation. Of course, this
464 * goes for the other dynamic (i.e., local) revision words too.
466 * Note that the code to do this is a bit more complex than a simple
467 * loop, because the user might run
469 * $ svn log -rCOMMITTED:42 foo.txt bar.c
471 * in which case we want to avoid recomputing the static revision on
472 * every iteration.
474 * ### FIXME: However, we can't yet handle multiple wc targets anyway.
476 * We used to iterate over each target in turn, getting the logs for
477 * the named range. This led to revisions being printed in strange
478 * order or being printed more than once. This is issue 1550.
480 * In r11599, jpieper blocked multiple wc targets in svn/log-cmd.c,
481 * meaning this block not only doesn't work right in that case, but isn't
482 * even testable that way (svn has no unit test suite; we can only test
483 * via the svn command). So, that check is now moved into this function
484 * (see above).
486 * kfogel ponders future enhancements in r4186:
487 * I think that's okay behavior, since the sense of the command is
488 * that one wants a particular range of logs for *this* file, then
489 * another range for *that* file, and so on. But we should
490 * probably put some sort of separator header between the log
491 * groups. Of course, libsvn_client can't just print stuff out --
492 * it has to take a callback from the client to do that. So we
493 * need to define that callback interface, then have the command
494 * line client pass one down here.
496 * epg wonders if the repository could send a unified stream of log
497 * entries if the paths and revisions were passed down.
500 svn_revnum_t start_revnum, end_revnum, youngest_rev = SVN_INVALID_REVNUM;
501 const char *path = APR_ARRAY_IDX(targets, 0, const char *);
502 svn_boolean_t has_log_revprops;
504 SVN_ERR(svn_client__get_revision_number
505 (&start_revnum, &youngest_rev, ra_session, start, path, pool));
506 SVN_ERR(svn_client__get_revision_number
507 (&end_revnum, &youngest_rev, ra_session, end, path, pool));
509 SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
510 SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
511 if (has_log_revprops)
512 return svn_ra_get_log2(ra_session,
513 condensed_targets,
514 start_revnum,
515 end_revnum,
516 limit,
517 discover_changed_paths,
518 strict_node_history,
519 include_merged_revisions,
520 revprops,
521 real_receiver,
522 real_receiver_baton,
523 pool);
524 else
526 /* See above pre-1.5 notes. */
527 pre_15_receiver_baton_t rb;
528 rb.ctx = ctx;
529 SVN_ERR(svn_client_open_ra_session(&rb.ra_session, actual_url,
530 ctx, pool));
531 rb.revprops = revprops;
532 rb.receiver = real_receiver;
533 rb.baton = real_receiver_baton;
534 SVN_ERR(svn_client__ra_session_from_path(&ra_session,
535 &ignored_revnum,
536 &actual_url, ra_target, NULL,
537 peg_revision,
538 &session_opt_rev,
539 ctx, pool));
540 return svn_ra_get_log2(ra_session,
541 condensed_targets,
542 start_revnum,
543 end_revnum,
544 limit,
545 discover_changed_paths,
546 strict_node_history,
547 include_merged_revisions,
548 svn_compat_log_revprops_in(pool),
549 pre_15_receiver,
550 &rb,
551 pool);
556 svn_error_t *
557 svn_client_log3(const apr_array_header_t *targets,
558 const svn_opt_revision_t *peg_revision,
559 const svn_opt_revision_t *start,
560 const svn_opt_revision_t *end,
561 int limit,
562 svn_boolean_t discover_changed_paths,
563 svn_boolean_t strict_node_history,
564 svn_log_message_receiver_t receiver,
565 void *receiver_baton,
566 svn_client_ctx_t *ctx,
567 apr_pool_t *pool)
569 svn_log_entry_receiver_t receiver2;
570 void *receiver2_baton;
572 svn_compat_wrap_log_receiver(&receiver2, &receiver2_baton,
573 receiver, receiver_baton,
574 pool);
576 return svn_client_log4(targets, peg_revision, start, end, limit,
577 discover_changed_paths, strict_node_history, FALSE,
578 svn_compat_log_revprops_in(pool),
579 receiver2, receiver2_baton, ctx, pool);
582 svn_error_t *
583 svn_client_log2(const apr_array_header_t *targets,
584 const svn_opt_revision_t *start,
585 const svn_opt_revision_t *end,
586 int limit,
587 svn_boolean_t discover_changed_paths,
588 svn_boolean_t strict_node_history,
589 svn_log_message_receiver_t receiver,
590 void *receiver_baton,
591 svn_client_ctx_t *ctx,
592 apr_pool_t *pool)
594 svn_opt_revision_t peg_revision;
595 peg_revision.kind = svn_opt_revision_unspecified;
596 return svn_client_log3(targets, &peg_revision, start, end, limit,
597 discover_changed_paths, strict_node_history,
598 receiver, receiver_baton, ctx, pool);
601 svn_error_t *
602 svn_client_log(const apr_array_header_t *targets,
603 const svn_opt_revision_t *start,
604 const svn_opt_revision_t *end,
605 svn_boolean_t discover_changed_paths,
606 svn_boolean_t strict_node_history,
607 svn_log_message_receiver_t receiver,
608 void *receiver_baton,
609 svn_client_ctx_t *ctx,
610 apr_pool_t *pool)
612 svn_error_t *err = SVN_NO_ERROR;
614 err = svn_client_log2(targets, start, end, 0, discover_changed_paths,
615 strict_node_history, receiver, receiver_baton, ctx,
616 pool);
618 /* Special case: If there have been no commits, we'll get an error
619 * for requesting log of a revision higher than 0. But the
620 * default behavior of "svn log" is to give revisions HEAD through
621 * 1, on the assumption that HEAD >= 1.
623 * So if we got that error for that reason, and it looks like the
624 * user was just depending on the defaults (rather than explicitly
625 * requesting the log for revision 1), then we don't error. Instead
626 * we just invoke the receiver manually on a hand-constructed log
627 * message for revision 0.
629 * See also http://subversion.tigris.org/issues/show_bug.cgi?id=692.
631 if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
632 && (start->kind == svn_opt_revision_head)
633 && ((end->kind == svn_opt_revision_number)
634 && (end->value.number == 1)))
637 /* We don't need to check if HEAD is 0, because that must be the case,
638 * by logical deduction: The revision range specified is HEAD:1.
639 * HEAD cannot not exist, so the revision to which "no such revision"
640 * applies is 1. If revision 1 does not exist, then HEAD is 0.
641 * Hence, we deduce the repository is empty without needing access
642 * to further information. */
644 svn_error_clear(err);
645 err = SVN_NO_ERROR;
647 /* Log receivers are free to handle revision 0 specially... But
648 just in case some don't, we make up a message here. */
649 SVN_ERR(receiver(receiver_baton,
650 NULL, 0, "", "", _("No commits in repository"),
651 pool));
654 return err;