log.c revision 289166
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(). */
50typedef 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. */
61static svn_error_t *
62copyfrom_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
94svn_error_t *
95svn_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 */
153typedef 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
166static svn_error_t *
167pre_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 */
247typedef 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
254static svn_error_t *
255limit_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. */
290static svn_error_t *
291resolve_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. */
412static void
413find_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
427typedef 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*/
448static svn_error_t*
449convert_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
571static int
572compare_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. */
601static svn_error_t *
602run_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
800svn_error_t *
801svn_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  const char *path_or_url;
818  svn_opt_revision_t youngest_opt_rev;
819  svn_revnum_t youngest_rev;
820  svn_revnum_t oldest_rev;
821  svn_opt_revision_t peg_rev;
822  svn_client__pathrev_t *ra_session_loc;
823  svn_client__pathrev_t *actual_loc;
824  apr_array_header_t *log_segments;
825  apr_array_header_t *revision_ranges;
826  apr_array_header_t *relative_targets;
827
828  if (opt_rev_ranges->nelts == 0)
829    {
830      return svn_error_create
831        (SVN_ERR_CLIENT_BAD_REVISION, NULL,
832         _("Missing required revision specification"));
833    }
834
835  /* Make a copy of PEG_REVISION, we may need to change it to a
836     default value. */
837  peg_rev = *peg_revision;
838
839  SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev,
840                              targets, ctx, pool, pool));
841
842  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &ra_session_loc,
843                                            ra_target, NULL, &peg_rev, &peg_rev,
844                                            ctx, pool));
845
846  /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
847     and oldest revision range that spans all of OPT_REV_RANGES. */
848  SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges,
849                                                   &youngest_rev,
850                                                   &oldest_rev,
851                                                   ra_session,
852                                                   ra_target,
853                                                   opt_rev_ranges, &peg_rev,
854                                                   ctx, pool,  pool));
855
856  /* For some peg revisions we must resolve revision and url via a local path
857     so use the original RA_TARGET. For others, use the potentially corrected
858     (redirected) ra session URL. */
859  if (peg_rev.kind == svn_opt_revision_previous ||
860      peg_rev.kind == svn_opt_revision_base ||
861      peg_rev.kind == svn_opt_revision_committed ||
862      peg_rev.kind == svn_opt_revision_working)
863    path_or_url = ra_target;
864  else
865    path_or_url = ra_session_loc->url;
866
867  /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */
868  youngest_opt_rev.kind = svn_opt_revision_number;
869  youngest_opt_rev.value.number = youngest_rev;
870  SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session,
871                                          path_or_url, &peg_rev,
872                                          &youngest_opt_rev, ctx, pool));
873  SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
874                                            actual_loc->url, pool));
875
876  /* Save us an RA layer round trip if we are on the repository root and
877     know the result in advance, or if we don't need multiple ranges.
878     All the revision data has already been validated.
879   */
880  if (strcmp(actual_loc->url, actual_loc->repos_root_url) == 0
881      || opt_rev_ranges->nelts <= 1)
882    {
883      svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
884      log_segments = apr_array_make(pool, 1, sizeof(segment));
885
886      segment->range_start = oldest_rev;
887      segment->range_end = actual_loc->rev;
888      segment->path = svn_uri_skip_ancestor(actual_loc->repos_root_url,
889                                            actual_loc->url, pool);
890      APR_ARRAY_PUSH(log_segments, svn_location_segment_t *) = segment;
891    }
892  else
893    {
894      /* Get the svn_location_segment_t's representing the requested log
895       * ranges. */
896      SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session,
897                                                  actual_loc->url,
898                                                  actual_loc->rev, /* peg */
899                                                  actual_loc->rev, /* start */
900                                                  oldest_rev,      /* end */
901                                                  ctx, pool));
902    }
903
904
905  SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments,
906                         actual_loc, ra_session, targets, limit,
907                         discover_changed_paths, strict_node_history,
908                         include_merged_revisions, revprops, real_receiver,
909                         real_receiver_baton, ctx, pool));
910
911  return SVN_NO_ERROR;
912}
913