]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/log.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_client / log.c
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 \f
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 *
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 *
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, &copyfrom_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 *
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 *
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 *
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
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*
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
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 *
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 *
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 }