1 /*
2 * log.c: return log messages
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h>
26
27 #include <apr_strings.h>
28 #include <apr_pools.h>
29
30 #include "svn_pools.h"
31 #include "svn_client.h"
32 #include "svn_compat.h"
33 #include "svn_error.h"
34 #include "svn_dirent_uri.h"
35 #include "svn_hash.h"
36 #include "svn_path.h"
37 #include "svn_sorts.h"
38 #include "svn_props.h"
39
40 #include "client.h"
41
42 #include "svn_private_config.h"
43 #include "private/svn_wc_private.h"
44
45 #include <assert.h>
46
47 /*** Getting misc. information ***/
48
49 /* The baton for use with copyfrom_info_receiver(). */
50 typedef struct copyfrom_info_t
51 {
52 svn_boolean_t is_first;
53 const char *path;
54 svn_revnum_t rev;
55 apr_pool_t *pool;
56 } copyfrom_info_t;
57
58 /* A location segment callback for obtaining the copy source of
59 a node at a path and storing it in *BATON (a struct copyfrom_info_t *).
60 Implements svn_location_segment_receiver_t. */
61 static svn_error_t *
copyfrom_info_receiver(svn_location_segment_t * segment,void * baton,apr_pool_t * pool)62 copyfrom_info_receiver(svn_location_segment_t *segment,
63 void *baton,
64 apr_pool_t *pool)
65 {
66 copyfrom_info_t *copyfrom_info = baton;
67
68 /* If we've already identified the copy source, there's nothing more
69 to do.
70 ### FIXME: We *should* be able to send */
71 if (copyfrom_info->path)
72 return SVN_NO_ERROR;
73
74 /* If this is the first segment, it's not of interest to us. Otherwise
75 (so long as this segment doesn't represent a history gap), it holds
76 our path's previous location (from which it was last copied). */
77 if (copyfrom_info->is_first)
78 {
79 copyfrom_info->is_first = FALSE;
80 }
81 else if (segment->path)
82 {
83 /* The end of the second non-gap segment is the location copied from. */
84 copyfrom_info->path = apr_pstrdup(copyfrom_info->pool, segment->path);
85 copyfrom_info->rev = segment->range_end;
86
87 /* ### FIXME: We *should* be able to return SVN_ERR_CEASE_INVOCATION
88 ### here so we don't get called anymore. */
89 }
90
91 return SVN_NO_ERROR;
92 }
93
94 svn_error_t *
svn_client__get_copy_source(const char ** original_repos_relpath,svn_revnum_t * original_revision,const char * path_or_url,const svn_opt_revision_t * revision,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)95 svn_client__get_copy_source(const char **original_repos_relpath,
96 svn_revnum_t *original_revision,
97 const char *path_or_url,
98 const svn_opt_revision_t *revision,
99 svn_ra_session_t *ra_session,
100 svn_client_ctx_t *ctx,
101 apr_pool_t *result_pool,
102 apr_pool_t *scratch_pool)
103 {
104 svn_error_t *err;
105 copyfrom_info_t copyfrom_info = { 0 };
106 apr_pool_t *sesspool = svn_pool_create(scratch_pool);
107 svn_client__pathrev_t *at_loc;
108 const char *old_session_url = NULL;
109
110 copyfrom_info.is_first = TRUE;
111 copyfrom_info.path = NULL;
112 copyfrom_info.rev = SVN_INVALID_REVNUM;
113 copyfrom_info.pool = result_pool;
114
115 if (!ra_session)
116 {
117 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &at_loc,
118 path_or_url, NULL,
119 revision, revision,
120 ctx, sesspool));
121 }
122 else
123 {
124 const char *url;
125 if (svn_path_is_url(path_or_url))
126 url = path_or_url;
127 else
128 {
129 SVN_ERR(svn_client_url_from_path2(&url, path_or_url, ctx, sesspool,
130 sesspool));
131
132 if (! url)
133 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
134 _("'%s' has no URL"), path_or_url);
135 }
136
137 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
138 url, sesspool));
139
140 err = svn_client__resolve_rev_and_url(&at_loc, ra_session, path_or_url,
141 revision, revision, ctx,
142 sesspool);
143
144 /* On error reparent back (and return), otherwise reparent to new
145 location */
146 SVN_ERR(svn_error_compose_create(
147 err,
148 svn_ra_reparent(ra_session, err ? old_session_url
149 : at_loc->url, sesspool)));
150 }
151
152 /* Find the copy source. Walk the location segments to find the revision
153 at which this node was created (copied or added). */
154
155 err = svn_ra_get_location_segments(ra_session, "", at_loc->rev, at_loc->rev,
156 SVN_INVALID_REVNUM,
157 copyfrom_info_receiver, ©from_info,
158 scratch_pool);
159
160 if (old_session_url)
161 err = svn_error_compose_create(
162 err,
163 svn_ra_reparent(ra_session, old_session_url, sesspool));
164
165 svn_pool_destroy(sesspool);
166
167 if (err)
168 {
169 if (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
170 err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
171 {
172 /* A locally-added but uncommitted versioned resource won't
173 exist in the repository. */
174 svn_error_clear(err);
175 err = SVN_NO_ERROR;
176
177 *original_repos_relpath = NULL;
178 *original_revision = SVN_INVALID_REVNUM;
179 }
180 return svn_error_trace(err);
181 }
182
183 *original_repos_relpath = copyfrom_info.path;
184 *original_revision = copyfrom_info.rev;
185 return SVN_NO_ERROR;
186 }
187
188
189 /* compatibility with pre-1.5 servers, which send only author/date/log
190 *revprops in log entries */
191 typedef struct pre_15_receiver_baton_t
192 {
193 svn_client_ctx_t *ctx;
194 /* ra session for retrieving revprops from old servers */
195 svn_ra_session_t *ra_session;
196 /* caller's list of requested revprops, receiver, and baton */
197 const char *ra_session_url;
198 apr_pool_t *ra_session_pool;
199 const apr_array_header_t *revprops;
200 svn_log_entry_receiver_t receiver;
201 void *baton;
202 } pre_15_receiver_baton_t;
203
204 static svn_error_t *
pre_15_receiver(void * baton,svn_log_entry_t * log_entry,apr_pool_t * pool)205 pre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
206 {
207 pre_15_receiver_baton_t *rb = baton;
208
209 if (log_entry->revision == SVN_INVALID_REVNUM)
210 return rb->receiver(rb->baton, log_entry, pool);
211
212 /* If only some revprops are requested, get them one at a time on the
213 second ra connection. If all are requested, get them all with
214 svn_ra_rev_proplist. This avoids getting unrequested revprops (which
215 may be arbitrarily large), but means one round-trip per requested
216 revprop. epg isn't entirely sure which should be optimized for. */
217 if (rb->revprops)
218 {
219 int i;
220 svn_boolean_t want_author, want_date, want_log;
221 want_author = want_date = want_log = FALSE;
222 for (i = 0; i < rb->revprops->nelts; i++)
223 {
224 const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
225 svn_string_t *value;
226
227 /* If a standard revprop is requested, we know it is already in
228 log_entry->revprops if available. */
229 if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
230 {
231 want_author = TRUE;
232 continue;
233 }
234 if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
235 {
236 want_date = TRUE;
237 continue;
238 }
239 if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
240 {
241 want_log = TRUE;
242 continue;
243 }
244
245 if (rb->ra_session == NULL)
246 SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
247 rb->ra_session_url, NULL,
248 rb->ctx, rb->ra_session_pool,
249 pool));
250
251 SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
252 name, &value, pool));
253 if (log_entry->revprops == NULL)
254 log_entry->revprops = apr_hash_make(pool);
255 svn_hash_sets(log_entry->revprops, name, value);
256 }
257 if (log_entry->revprops)
258 {
259 /* Pre-1.5 servers send the standard revprops unconditionally;
260 clear those the caller doesn't want. */
261 if (!want_author)
262 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, NULL);
263 if (!want_date)
264 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, NULL);
265 if (!want_log)
266 svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, NULL);
267 }
268 }
269 else
270 {
271 if (rb->ra_session == NULL)
272 SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
273 rb->ra_session_url, NULL,
274 rb->ctx, rb->ra_session_pool,
275 pool));
276
277 SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
278 &log_entry->revprops, pool));
279 }
280
281 return rb->receiver(rb->baton, log_entry, pool);
282 }
283
284 /* limit receiver */
285 typedef struct limit_receiver_baton_t
286 {
287 int limit;
288 svn_log_entry_receiver_t receiver;
289 void *baton;
290 } limit_receiver_baton_t;
291
292 static svn_error_t *
limit_receiver(void * baton,svn_log_entry_t * log_entry,apr_pool_t * pool)293 limit_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
294 {
295 limit_receiver_baton_t *rb = baton;
296
297 rb->limit--;
298
299 return rb->receiver(rb->baton, log_entry, pool);
300 }
301
302 /* Resolve the URLs or WC path in TARGETS as per the svn_client_log5 API.
303
304 The limitations on TARGETS specified by svn_client_log5 are enforced here.
305 So TARGETS can only contain a single WC path or a URL and zero or more
306 relative paths -- anything else will raise an error.
307
308 PEG_REVISION, TARGETS, and CTX are as per svn_client_log5.
309
310 If TARGETS contains a single WC path then set *RA_TARGET to the absolute
311 path of that single path if PEG_REVISION is dependent on the working copy
312 (e.g. PREV). Otherwise set *RA_TARGET to the corresponding URL for the
313 single WC path. Set *RELATIVE_TARGETS to an array with a single
314 element "".
315
316 If TARGETS contains only a single URL, then set *RA_TARGET to a copy of
317 that URL and *RELATIVE_TARGETS to an array with a single element "".
318
319 If TARGETS contains a single URL and one or more relative paths, then
320 set *RA_TARGET to a copy of that URL and *RELATIVE_TARGETS to a copy of
321 each relative path after the URL.
322
323 If *PEG_REVISION is svn_opt_revision_unspecified, then *PEG_REVISION is
324 set to svn_opt_revision_head for URLs or svn_opt_revision_working for a
325 WC path.
326
327 *RA_TARGET and *RELATIVE_TARGETS are allocated in RESULT_POOL. */
328 static svn_error_t *
resolve_log_targets(apr_array_header_t ** relative_targets,const char ** ra_target,svn_opt_revision_t * peg_revision,const apr_array_header_t * targets,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)329 resolve_log_targets(apr_array_header_t **relative_targets,
330 const char **ra_target,
331 svn_opt_revision_t *peg_revision,
332 const apr_array_header_t *targets,
333 svn_client_ctx_t *ctx,
334 apr_pool_t *result_pool,
335 apr_pool_t *scratch_pool)
336 {
337 int i;
338 svn_boolean_t url_targets;
339
340 /* Per svn_client_log5, TARGETS contains either a URL followed by zero or
341 more relative paths, or one working copy path. */
342 const char *url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
343
344 /* svn_client_log5 requires at least one target. */
345 if (targets->nelts == 0)
346 return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
347 _("No valid target found"));
348
349 /* Initialize the output array. At a minimum, we need room for one
350 (possibly empty) relpath. Otherwise, we have to hold a relpath
351 for every item in TARGETS except the first. */
352 *relative_targets = apr_array_make(result_pool,
353 MAX(1, targets->nelts - 1),
354 sizeof(const char *));
355
356 if (svn_path_is_url(url_or_path))
357 {
358 /* An unspecified PEG_REVISION for a URL path defaults
359 to svn_opt_revision_head. */
360 if (peg_revision->kind == svn_opt_revision_unspecified)
361 peg_revision->kind = svn_opt_revision_head;
362
363 /* The logic here is this: If we get passed one argument, we assume
364 it is the full URL to a file/dir we want log info for. If we get
365 a URL plus some paths, then we assume that the URL is the base,
366 and that the paths passed are relative to it. */
367 if (targets->nelts > 1)
368 {
369 /* We have some paths, let's use them. Start after the URL. */
370 for (i = 1; i < targets->nelts; i++)
371 {
372 const char *target;
373
374 target = APR_ARRAY_IDX(targets, i, const char *);
375
376 if (svn_path_is_url(target) || svn_dirent_is_absolute(target))
377 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
378 _("'%s' is not a relative path"),
379 target);
380
381 APR_ARRAY_PUSH(*relative_targets, const char *) =
382 apr_pstrdup(result_pool, target);
383 }
384 }
385 else
386 {
387 /* If we have a single URL, then the session will be rooted at
388 it, so just send an empty string for the paths we are
389 interested in. */
390 APR_ARRAY_PUSH(*relative_targets, const char *) = "";
391 }
392
393 /* Remember that our targets are URLs. */
394 url_targets = TRUE;
395 }
396 else /* WC path target. */
397 {
398 const char *target;
399 const char *target_abspath;
400
401 url_targets = FALSE;
402 if (targets->nelts > 1)
403 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
404 _("When specifying working copy paths, only "
405 "one target may be given"));
406
407 /* An unspecified PEG_REVISION for a working copy path defaults
408 to svn_opt_revision_working. */
409 if (peg_revision->kind == svn_opt_revision_unspecified)
410 peg_revision->kind = svn_opt_revision_working;
411
412 /* Get URLs for each target */
413 target = APR_ARRAY_IDX(targets, 0, const char *);
414
415 SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, scratch_pool));
416 SVN_ERR(svn_wc__node_get_url(&url_or_path, ctx->wc_ctx, target_abspath,
417 scratch_pool, scratch_pool));
418 APR_ARRAY_PUSH(*relative_targets, const char *) = "";
419 }
420
421 /* If this is a revision type that requires access to the working copy,
422 * we use our initial target path to figure out where to root the RA
423 * session, otherwise we use our URL. */
424 if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
425 {
426 if (url_targets)
427 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
428 _("PREV, BASE, or COMMITTED revision "
429 "keywords are invalid for URL"));
430
431 else
432 SVN_ERR(svn_dirent_get_absolute(
433 ra_target, APR_ARRAY_IDX(targets, 0, const char *), result_pool));
434 }
435 else
436 {
437 *ra_target = apr_pstrdup(result_pool, url_or_path);
438 }
439
440 return SVN_NO_ERROR;
441 }
442
443 /* Keep track of oldest and youngest opt revs found.
444
445 If REV is younger than *YOUNGEST_REV, or *YOUNGEST_REV is
446 svn_opt_revision_unspecified, then set *YOUNGEST_REV equal to REV.
447
448 If REV is older than *OLDEST_REV, or *OLDEST_REV is
449 svn_opt_revision_unspecified, then set *OLDEST_REV equal to REV. */
450 static void
find_youngest_and_oldest_revs(svn_revnum_t * youngest_rev,svn_revnum_t * oldest_rev,svn_revnum_t rev)451 find_youngest_and_oldest_revs(svn_revnum_t *youngest_rev,
452 svn_revnum_t *oldest_rev,
453 svn_revnum_t rev)
454 {
455 /* Is REV younger than YOUNGEST_REV? */
456 if (! SVN_IS_VALID_REVNUM(*youngest_rev)
457 || rev > *youngest_rev)
458 *youngest_rev = rev;
459
460 if (! SVN_IS_VALID_REVNUM(*oldest_rev)
461 || rev < *oldest_rev)
462 *oldest_rev = rev;
463 }
464
465 typedef struct rev_range_t
466 {
467 svn_revnum_t range_start;
468 svn_revnum_t range_end;
469 } rev_range_t;
470
471 /* Convert array of svn_opt_revision_t ranges to an array of svn_revnum_t
472 ranges.
473
474 Given a log target URL_OR_ABSPATH@PEG_REV and an array of
475 svn_opt_revision_range_t's OPT_REV_RANGES, resolve the opt revs in
476 OPT_REV_RANGES to svn_revnum_t's and return these in *REVISION_RANGES, an
477 array of rev_range_t *.
478
479 Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions
480 found in *REVISION_RANGES.
481
482 If the repository needs to be contacted to resolve svn_opt_revision_date or
483 svn_opt_revision_head revisions, then the session used to do this is
484 RA_SESSION; it must be an open session to any URL in the right repository.
485 */
486 static svn_error_t*
convert_opt_rev_array_to_rev_range_array(apr_array_header_t ** revision_ranges,svn_revnum_t * youngest_rev,svn_revnum_t * oldest_rev,svn_ra_session_t * ra_session,const char * url_or_abspath,const apr_array_header_t * opt_rev_ranges,const svn_opt_revision_t * peg_rev,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)487 convert_opt_rev_array_to_rev_range_array(
488 apr_array_header_t **revision_ranges,
489 svn_revnum_t *youngest_rev,
490 svn_revnum_t *oldest_rev,
491 svn_ra_session_t *ra_session,
492 const char *url_or_abspath,
493 const apr_array_header_t *opt_rev_ranges,
494 const svn_opt_revision_t *peg_rev,
495 svn_client_ctx_t *ctx,
496 apr_pool_t *result_pool,
497 apr_pool_t *scratch_pool)
498 {
499 int i;
500 svn_revnum_t head_rev = SVN_INVALID_REVNUM;
501
502 /* Initialize the input/output parameters. */
503 *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM;
504
505 /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
506 and oldest revision range that spans all of OPT_REV_RANGES. */
507 *revision_ranges = apr_array_make(result_pool, opt_rev_ranges->nelts,
508 sizeof(rev_range_t *));
509
510 for (i = 0; i < opt_rev_ranges->nelts; i++)
511 {
512 svn_opt_revision_range_t *range;
513 rev_range_t *rev_range;
514 svn_boolean_t start_same_as_end = FALSE;
515
516 range = APR_ARRAY_IDX(opt_rev_ranges, i, svn_opt_revision_range_t *);
517
518 /* Right now RANGE can be any valid pair of svn_opt_revision_t's. We
519 will now convert all RANGEs in place to the corresponding
520 svn_opt_revision_number kind. */
521 if ((range->start.kind != svn_opt_revision_unspecified)
522 && (range->end.kind == svn_opt_revision_unspecified))
523 {
524 /* If the user specified exactly one revision, then start rev is
525 * set but end is not. We show the log message for just that
526 * revision by making end equal to start.
527 *
528 * Note that if the user requested a single dated revision, then
529 * this will cause the same date to be resolved twice. The
530 * extra code complexity to get around this slight inefficiency
531 * doesn't seem worth it, however. */
532 range->end = range->start;
533 }
534 else if (range->start.kind == svn_opt_revision_unspecified)
535 {
536 /* Default to any specified peg revision. Otherwise, if the
537 * first target is a URL, then we default to HEAD:0. Lastly,
538 * the default is BASE:0 since WC@HEAD may not exist. */
539 if (peg_rev->kind == svn_opt_revision_unspecified)
540 {
541 if (svn_path_is_url(url_or_abspath))
542 range->start.kind = svn_opt_revision_head;
543 else
544 range->start.kind = svn_opt_revision_base;
545 }
546 else
547 range->start = *peg_rev;
548
549 if (range->end.kind == svn_opt_revision_unspecified)
550 {
551 range->end.kind = svn_opt_revision_number;
552 range->end.value.number = 0;
553 }
554 }
555
556 if ((range->start.kind == svn_opt_revision_unspecified)
557 || (range->end.kind == svn_opt_revision_unspecified))
558 {
559 return svn_error_create
560 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
561 _("Missing required revision specification"));
562 }
563
564 /* Does RANGE describe a single svn_opt_revision_t? */
565 if (range->start.kind == range->end.kind)
566 {
567 if (range->start.kind == svn_opt_revision_number)
568 {
569 if (range->start.value.number == range->end.value.number)
570 start_same_as_end = TRUE;
571 }
572 else if (range->start.kind == svn_opt_revision_date)
573 {
574 if (range->start.value.date == range->end.value.date)
575 start_same_as_end = TRUE;
576 }
577 else
578 {
579 start_same_as_end = TRUE;
580 }
581 }
582
583 rev_range = apr_palloc(result_pool, sizeof(*rev_range));
584 SVN_ERR(svn_client__get_revision_number(
585 &rev_range->range_start, &head_rev,
586 ctx->wc_ctx, url_or_abspath, ra_session,
587 &range->start, scratch_pool));
588 if (start_same_as_end)
589 rev_range->range_end = rev_range->range_start;
590 else
591 SVN_ERR(svn_client__get_revision_number(
592 &rev_range->range_end, &head_rev,
593 ctx->wc_ctx, url_or_abspath, ra_session,
594 &range->end, scratch_pool));
595
596 /* Possibly update the oldest and youngest revisions requested. */
597 find_youngest_and_oldest_revs(youngest_rev,
598 oldest_rev,
599 rev_range->range_start);
600 find_youngest_and_oldest_revs(youngest_rev,
601 oldest_rev,
602 rev_range->range_end);
603 APR_ARRAY_PUSH(*revision_ranges, rev_range_t *) = rev_range;
604 }
605
606 return SVN_NO_ERROR;
607 }
608
609 static int
compare_rev_to_segment(const void * key_p,const void * element_p)610 compare_rev_to_segment(const void *key_p,
611 const void *element_p)
612 {
613 svn_revnum_t rev =
614 * (svn_revnum_t *)key_p;
615 const svn_location_segment_t *segment =
616 *((const svn_location_segment_t * const *) element_p);
617
618 if (rev < segment->range_start)
619 return -1;
620 else if (rev > segment->range_end)
621 return 1;
622 else
623 return 0;
624 }
625
626 /* Run svn_ra_get_log2 for PATHS, one or more paths relative to RA_SESSION's
627 common parent, for each revision in REVISION_RANGES, an array of
628 rev_range_t.
629
630 RA_SESSION is an open session pointing to ACTUAL_LOC.
631
632 LOG_SEGMENTS is an array of svn_location_segment_t * items representing the
633 history of PATHS from the oldest to youngest revisions found in
634 REVISION_RANGES.
635
636 The TARGETS, LIMIT, DISCOVER_CHANGED_PATHS, STRICT_NODE_HISTORY,
637 INCLUDE_MERGED_REVISIONS, REVPROPS, REAL_RECEIVER, and REAL_RECEIVER_BATON
638 parameters are all as per the svn_client_log5 API. */
639 static svn_error_t *
run_ra_get_log(apr_array_header_t * revision_ranges,apr_array_header_t * paths,apr_array_header_t * log_segments,svn_client__pathrev_t * actual_loc,svn_ra_session_t * ra_session,const apr_array_header_t * targets,int limit,svn_boolean_t discover_changed_paths,svn_boolean_t strict_node_history,svn_boolean_t include_merged_revisions,const apr_array_header_t * revprops,svn_log_entry_receiver_t real_receiver,void * real_receiver_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)640 run_ra_get_log(apr_array_header_t *revision_ranges,
641 apr_array_header_t *paths,
642 apr_array_header_t *log_segments,
643 svn_client__pathrev_t *actual_loc,
644 svn_ra_session_t *ra_session,
645 /* The following are as per svn_client_log5. */
646 const apr_array_header_t *targets,
647 int limit,
648 svn_boolean_t discover_changed_paths,
649 svn_boolean_t strict_node_history,
650 svn_boolean_t include_merged_revisions,
651 const apr_array_header_t *revprops,
652 svn_log_entry_receiver_t real_receiver,
653 void *real_receiver_baton,
654 svn_client_ctx_t *ctx,
655 apr_pool_t *scratch_pool)
656 {
657 int i;
658 pre_15_receiver_baton_t rb = {0};
659 apr_pool_t *iterpool;
660 svn_boolean_t has_log_revprops;
661
662 SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
663 SVN_RA_CAPABILITY_LOG_REVPROPS,
664 scratch_pool));
665
666 if (!has_log_revprops)
667 {
668 /* See above pre-1.5 notes. */
669 rb.ctx = ctx;
670
671 /* Create ra session on first use */
672 rb.ra_session_pool = scratch_pool;
673 rb.ra_session_url = actual_loc->url;
674 }
675
676 /* It's a bit complex to correctly handle the special revision words
677 * such as "BASE", "COMMITTED", and "PREV". For example, if the
678 * user runs
679 *
680 * $ svn log -rCOMMITTED foo.txt bar.c
681 *
682 * which committed rev should be used? The younger of the two? The
683 * first one? Should we just error?
684 *
685 * None of the above, I think. Rather, the committed rev of each
686 * target in turn should be used. This is what most users would
687 * expect, and is the most useful interpretation. Of course, this
688 * goes for the other dynamic (i.e., local) revision words too.
689 *
690 * Note that the code to do this is a bit more complex than a simple
691 * loop, because the user might run
692 *
693 * $ svn log -rCOMMITTED:42 foo.txt bar.c
694 *
695 * in which case we want to avoid recomputing the static revision on
696 * every iteration.
697 *
698 * ### FIXME: However, we can't yet handle multiple wc targets anyway.
699 *
700 * We used to iterate over each target in turn, getting the logs for
701 * the named range. This led to revisions being printed in strange
702 * order or being printed more than once. This is issue 1550.
703 *
704 * In r851673, jpieper blocked multiple wc targets in svn/log-cmd.c,
705 * meaning this block not only doesn't work right in that case, but isn't
706 * even testable that way (svn has no unit test suite; we can only test
707 * via the svn command). So, that check is now moved into this function
708 * (see above).
709 *
710 * kfogel ponders future enhancements in r844260:
711 * I think that's okay behavior, since the sense of the command is
712 * that one wants a particular range of logs for *this* file, then
713 * another range for *that* file, and so on. But we should
714 * probably put some sort of separator header between the log
715 * groups. Of course, libsvn_client can't just print stuff out --
716 * it has to take a callback from the client to do that. So we
717 * need to define that callback interface, then have the command
718 * line client pass one down here.
719 *
720 * epg wonders if the repository could send a unified stream of log
721 * entries if the paths and revisions were passed down.
722 */
723 iterpool = svn_pool_create(scratch_pool);
724 for (i = 0; i < revision_ranges->nelts; i++)
725 {
726 const char *old_session_url;
727 const char *path = APR_ARRAY_IDX(targets, 0, const char *);
728 const char *local_abspath_or_url;
729 rev_range_t *range;
730 limit_receiver_baton_t lb;
731 svn_log_entry_receiver_t passed_receiver;
732 void *passed_receiver_baton;
733 const apr_array_header_t *passed_receiver_revprops;
734 svn_location_segment_t **matching_segment;
735 svn_revnum_t younger_rev;
736
737 svn_pool_clear(iterpool);
738
739 if (!svn_path_is_url(path))
740 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path,
741 iterpool));
742 else
743 local_abspath_or_url = path;
744
745 range = APR_ARRAY_IDX(revision_ranges, i, rev_range_t *);
746
747 /* Issue #4355: Account for renames spanning requested
748 revision ranges. */
749 younger_rev = MAX(range->range_start, range->range_end);
750 matching_segment = bsearch(&younger_rev, log_segments->elts,
751 log_segments->nelts, log_segments->elt_size,
752 compare_rev_to_segment);
753 /* LOG_SEGMENTS is supposed to represent the history of PATHS from
754 the oldest to youngest revs in REVISION_RANGES. This function's
755 current sole caller svn_client_log5 *should* be providing
756 LOG_SEGMENTS that span the oldest to youngest revs in
757 REVISION_RANGES, even if one or more of the svn_location_segment_t's
758 returned have NULL path members indicating a gap in the history. So
759 MATCHING_SEGMENT should never be NULL, but clearly sometimes it is,
760 see http://svn.haxx.se/dev/archive-2013-06/0522.shtml
761 So to be safe we handle that case. */
762 if (matching_segment == NULL)
763 continue;
764
765 /* A segment with a NULL path means there is gap in the history.
766 We'll just proceed and let svn_ra_get_log2 fail with a useful
767 error...*/
768 if ((*matching_segment)->path != NULL)
769 {
770 /* ...but if there is history, then we must account for issue
771 #4355 and make sure our RA session is pointing at the correct
772 location. */
773 const char *segment_url = svn_path_url_add_component2(
774 actual_loc->repos_root_url, (*matching_segment)->path,
775 scratch_pool);
776 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
777 ra_session,
778 segment_url,
779 scratch_pool));
780 }
781
782 if (has_log_revprops)
783 {
784 passed_receiver = real_receiver;
785 passed_receiver_baton = real_receiver_baton;
786 passed_receiver_revprops = revprops;
787 }
788 else
789 {
790 rb.revprops = revprops;
791 rb.receiver = real_receiver;
792 rb.baton = real_receiver_baton;
793
794 passed_receiver = pre_15_receiver;
795 passed_receiver_baton = &rb;
796 passed_receiver_revprops = svn_compat_log_revprops_in(iterpool);
797 }
798
799 if (limit && revision_ranges->nelts > 1)
800 {
801 lb.limit = limit;
802 lb.receiver = passed_receiver;
803 lb.baton = passed_receiver_baton;
804
805 passed_receiver = limit_receiver;
806 passed_receiver_baton = &lb;
807 }
808
809 SVN_ERR(svn_ra_get_log2(ra_session,
810 paths,
811 range->range_start,
812 range->range_end,
813 limit,
814 discover_changed_paths,
815 strict_node_history,
816 include_merged_revisions,
817 passed_receiver_revprops,
818 passed_receiver,
819 passed_receiver_baton,
820 iterpool));
821
822 if (limit && revision_ranges->nelts > 1)
823 {
824 limit = lb.limit;
825 if (limit == 0)
826 {
827 return SVN_NO_ERROR;
828 }
829 }
830 }
831 svn_pool_destroy(iterpool);
832
833 return SVN_NO_ERROR;
834 }
835
836 /*** Public Interface. ***/
837
838 svn_error_t *
svn_client_log5(const apr_array_header_t * targets,const svn_opt_revision_t * peg_revision,const apr_array_header_t * opt_rev_ranges,int limit,svn_boolean_t discover_changed_paths,svn_boolean_t strict_node_history,svn_boolean_t include_merged_revisions,const apr_array_header_t * revprops,svn_log_entry_receiver_t real_receiver,void * real_receiver_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)839 svn_client_log5(const apr_array_header_t *targets,
840 const svn_opt_revision_t *peg_revision,
841 const apr_array_header_t *opt_rev_ranges,
842 int limit,
843 svn_boolean_t discover_changed_paths,
844 svn_boolean_t strict_node_history,
845 svn_boolean_t include_merged_revisions,
846 const apr_array_header_t *revprops,
847 svn_log_entry_receiver_t real_receiver,
848 void *real_receiver_baton,
849 svn_client_ctx_t *ctx,
850 apr_pool_t *pool)
851 {
852 svn_ra_session_t *ra_session;
853 const char *old_session_url;
854 const char *ra_target;
855 const char *path_or_url;
856 svn_opt_revision_t youngest_opt_rev;
857 svn_revnum_t youngest_rev;
858 svn_revnum_t oldest_rev;
859 svn_opt_revision_t peg_rev;
860 svn_client__pathrev_t *ra_session_loc;
861 svn_client__pathrev_t *actual_loc;
862 apr_array_header_t *log_segments;
863 apr_array_header_t *revision_ranges;
864 apr_array_header_t *relative_targets;
865
866 if (opt_rev_ranges->nelts == 0)
867 {
868 return svn_error_create
869 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
870 _("Missing required revision specification"));
871 }
872
873 /* Make a copy of PEG_REVISION, we may need to change it to a
874 default value. */
875 peg_rev = *peg_revision;
876
877 SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev,
878 targets, ctx, pool, pool));
879
880 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &ra_session_loc,
881 ra_target, NULL, &peg_rev, &peg_rev,
882 ctx, pool));
883
884 /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
885 and oldest revision range that spans all of OPT_REV_RANGES. */
886 SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges,
887 &youngest_rev,
888 &oldest_rev,
889 ra_session,
890 ra_target,
891 opt_rev_ranges, &peg_rev,
892 ctx, pool, pool));
893
894 /* For some peg revisions we must resolve revision and url via a local path
895 so use the original RA_TARGET. For others, use the potentially corrected
896 (redirected) ra session URL. */
897 if (peg_rev.kind == svn_opt_revision_previous ||
898 peg_rev.kind == svn_opt_revision_base ||
899 peg_rev.kind == svn_opt_revision_committed ||
900 peg_rev.kind == svn_opt_revision_working)
901 path_or_url = ra_target;
902 else
903 path_or_url = ra_session_loc->url;
904
905 /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */
906 youngest_opt_rev.kind = svn_opt_revision_number;
907 youngest_opt_rev.value.number = youngest_rev;
908 SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session,
909 path_or_url, &peg_rev,
910 &youngest_opt_rev, ctx, pool));
911 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
912 actual_loc->url, pool));
913
914 /* Save us an RA layer round trip if we are on the repository root and
915 know the result in advance, or if we don't need multiple ranges.
916 All the revision data has already been validated.
917 */
918 if (strcmp(actual_loc->url, actual_loc->repos_root_url) == 0
919 || opt_rev_ranges->nelts <= 1)
920 {
921 svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
922 log_segments = apr_array_make(pool, 1, sizeof(segment));
923
924 segment->range_start = oldest_rev;
925 segment->range_end = actual_loc->rev;
926 segment->path = svn_uri_skip_ancestor(actual_loc->repos_root_url,
927 actual_loc->url, pool);
928 APR_ARRAY_PUSH(log_segments, svn_location_segment_t *) = segment;
929 }
930 else
931 {
932 /* Get the svn_location_segment_t's representing the requested log
933 * ranges. */
934 SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session,
935 actual_loc->url,
936 actual_loc->rev, /* peg */
937 actual_loc->rev, /* start */
938 oldest_rev, /* end */
939 ctx, pool));
940 }
941
942
943 SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments,
944 actual_loc, ra_session, targets, limit,
945 discover_changed_paths, strict_node_history,
946 include_merged_revisions, revprops,
947 real_receiver, real_receiver_baton, ctx, pool));
948
949 return SVN_NO_ERROR;
950 }
951