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