1251881Speter/*
2251881Speter * log.c:  return log messages
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#define APR_WANT_STRFUNC
25251881Speter#include <apr_want.h>
26251881Speter
27251881Speter#include <apr_strings.h>
28251881Speter#include <apr_pools.h>
29251881Speter
30251881Speter#include "svn_pools.h"
31251881Speter#include "svn_client.h"
32251881Speter#include "svn_compat.h"
33251881Speter#include "svn_error.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_hash.h"
36251881Speter#include "svn_path.h"
37251881Speter#include "svn_sorts.h"
38251881Speter#include "svn_props.h"
39251881Speter
40251881Speter#include "client.h"
41251881Speter
42251881Speter#include "svn_private_config.h"
43251881Speter#include "private/svn_wc_private.h"
44251881Speter
45251881Speter#include <assert.h>
46251881Speter
47251881Speter/*** Getting misc. information ***/
48251881Speter
49251881Speter/* The baton for use with copyfrom_info_receiver(). */
50251881Spetertypedef struct copyfrom_info_t
51251881Speter{
52251881Speter  svn_boolean_t is_first;
53251881Speter  const char *path;
54251881Speter  svn_revnum_t rev;
55251881Speter  apr_pool_t *pool;
56251881Speter} copyfrom_info_t;
57251881Speter
58251881Speter/* A location segment callback for obtaining the copy source of
59251881Speter   a node at a path and storing it in *BATON (a struct copyfrom_info_t *).
60251881Speter   Implements svn_location_segment_receiver_t. */
61251881Speterstatic svn_error_t *
62251881Spetercopyfrom_info_receiver(svn_location_segment_t *segment,
63251881Speter                       void *baton,
64251881Speter                       apr_pool_t *pool)
65251881Speter{
66251881Speter  copyfrom_info_t *copyfrom_info = baton;
67251881Speter
68251881Speter  /* If we've already identified the copy source, there's nothing more
69251881Speter     to do.
70251881Speter     ### FIXME:  We *should* be able to send */
71251881Speter  if (copyfrom_info->path)
72251881Speter    return SVN_NO_ERROR;
73251881Speter
74251881Speter  /* If this is the first segment, it's not of interest to us. Otherwise
75251881Speter     (so long as this segment doesn't represent a history gap), it holds
76251881Speter     our path's previous location (from which it was last copied). */
77251881Speter  if (copyfrom_info->is_first)
78251881Speter    {
79251881Speter      copyfrom_info->is_first = FALSE;
80251881Speter    }
81251881Speter  else if (segment->path)
82251881Speter    {
83251881Speter      /* The end of the second non-gap segment is the location copied from.  */
84251881Speter      copyfrom_info->path = apr_pstrdup(copyfrom_info->pool, segment->path);
85251881Speter      copyfrom_info->rev = segment->range_end;
86251881Speter
87251881Speter      /* ### FIXME: We *should* be able to return SVN_ERR_CEASE_INVOCATION
88251881Speter         ### here so we don't get called anymore. */
89251881Speter    }
90251881Speter
91251881Speter  return SVN_NO_ERROR;
92251881Speter}
93251881Speter
94251881Spetersvn_error_t *
95251881Spetersvn_client__get_copy_source(const char **original_repos_relpath,
96251881Speter                            svn_revnum_t *original_revision,
97251881Speter                            const char *path_or_url,
98251881Speter                            const svn_opt_revision_t *revision,
99251881Speter                            svn_client_ctx_t *ctx,
100251881Speter                            apr_pool_t *result_pool,
101251881Speter                            apr_pool_t *scratch_pool)
102251881Speter{
103251881Speter  svn_error_t *err;
104251881Speter  copyfrom_info_t copyfrom_info = { 0 };
105251881Speter  apr_pool_t *sesspool = svn_pool_create(scratch_pool);
106251881Speter  svn_ra_session_t *ra_session;
107251881Speter  svn_client__pathrev_t *at_loc;
108251881Speter
109251881Speter  copyfrom_info.is_first = TRUE;
110251881Speter  copyfrom_info.path = NULL;
111251881Speter  copyfrom_info.rev = SVN_INVALID_REVNUM;
112251881Speter  copyfrom_info.pool = result_pool;
113251881Speter
114251881Speter  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &at_loc,
115251881Speter                                            path_or_url, NULL,
116251881Speter                                            revision, revision,
117251881Speter                                            ctx, sesspool));
118251881Speter
119251881Speter  /* Find the copy source.  Walk the location segments to find the revision
120251881Speter     at which this node was created (copied or added). */
121251881Speter
122251881Speter  err = svn_ra_get_location_segments(ra_session, "", at_loc->rev, at_loc->rev,
123251881Speter                                     SVN_INVALID_REVNUM,
124251881Speter                                     copyfrom_info_receiver, &copyfrom_info,
125251881Speter                                     scratch_pool);
126251881Speter
127251881Speter  svn_pool_destroy(sesspool);
128251881Speter
129251881Speter  if (err)
130251881Speter    {
131251881Speter      if (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
132251881Speter          err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
133251881Speter        {
134251881Speter          /* A locally-added but uncommitted versioned resource won't
135251881Speter             exist in the repository. */
136251881Speter            svn_error_clear(err);
137251881Speter            err = SVN_NO_ERROR;
138251881Speter
139251881Speter            *original_repos_relpath = NULL;
140251881Speter            *original_revision = SVN_INVALID_REVNUM;
141251881Speter        }
142251881Speter      return svn_error_trace(err);
143251881Speter    }
144251881Speter
145251881Speter  *original_repos_relpath = copyfrom_info.path;
146251881Speter  *original_revision = copyfrom_info.rev;
147251881Speter  return SVN_NO_ERROR;
148251881Speter}
149251881Speter
150251881Speter
151251881Speter/* compatibility with pre-1.5 servers, which send only author/date/log
152251881Speter *revprops in log entries */
153251881Spetertypedef struct pre_15_receiver_baton_t
154251881Speter{
155251881Speter  svn_client_ctx_t *ctx;
156251881Speter  /* ra session for retrieving revprops from old servers */
157251881Speter  svn_ra_session_t *ra_session;
158251881Speter  /* caller's list of requested revprops, receiver, and baton */
159251881Speter  const char *ra_session_url;
160251881Speter  apr_pool_t *ra_session_pool;
161251881Speter  const apr_array_header_t *revprops;
162251881Speter  svn_log_entry_receiver_t receiver;
163251881Speter  void *baton;
164251881Speter} pre_15_receiver_baton_t;
165251881Speter
166251881Speterstatic svn_error_t *
167251881Speterpre_15_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
168251881Speter{
169251881Speter  pre_15_receiver_baton_t *rb = baton;
170251881Speter
171251881Speter  if (log_entry->revision == SVN_INVALID_REVNUM)
172251881Speter    return rb->receiver(rb->baton, log_entry, pool);
173251881Speter
174251881Speter  /* If only some revprops are requested, get them one at a time on the
175251881Speter     second ra connection.  If all are requested, get them all with
176251881Speter     svn_ra_rev_proplist.  This avoids getting unrequested revprops (which
177251881Speter     may be arbitrarily large), but means one round-trip per requested
178251881Speter     revprop.  epg isn't entirely sure which should be optimized for. */
179251881Speter  if (rb->revprops)
180251881Speter    {
181251881Speter      int i;
182251881Speter      svn_boolean_t want_author, want_date, want_log;
183251881Speter      want_author = want_date = want_log = FALSE;
184251881Speter      for (i = 0; i < rb->revprops->nelts; i++)
185251881Speter        {
186251881Speter          const char *name = APR_ARRAY_IDX(rb->revprops, i, const char *);
187251881Speter          svn_string_t *value;
188251881Speter
189251881Speter          /* If a standard revprop is requested, we know it is already in
190251881Speter             log_entry->revprops if available. */
191251881Speter          if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
192251881Speter            {
193251881Speter              want_author = TRUE;
194251881Speter              continue;
195251881Speter            }
196251881Speter          if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
197251881Speter            {
198251881Speter              want_date = TRUE;
199251881Speter              continue;
200251881Speter            }
201251881Speter          if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
202251881Speter            {
203251881Speter              want_log = TRUE;
204251881Speter              continue;
205251881Speter            }
206251881Speter
207251881Speter          if (rb->ra_session == NULL)
208251881Speter            SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
209251881Speter                                                rb->ra_session_url, NULL,
210251881Speter                                                rb->ctx, rb->ra_session_pool,
211251881Speter                                                pool));
212251881Speter
213251881Speter          SVN_ERR(svn_ra_rev_prop(rb->ra_session, log_entry->revision,
214251881Speter                                  name, &value, pool));
215251881Speter          if (log_entry->revprops == NULL)
216251881Speter            log_entry->revprops = apr_hash_make(pool);
217251881Speter          svn_hash_sets(log_entry->revprops, name, value);
218251881Speter        }
219251881Speter      if (log_entry->revprops)
220251881Speter        {
221251881Speter          /* Pre-1.5 servers send the standard revprops unconditionally;
222251881Speter             clear those the caller doesn't want. */
223251881Speter          if (!want_author)
224251881Speter            svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR, NULL);
225251881Speter          if (!want_date)
226251881Speter            svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE, NULL);
227251881Speter          if (!want_log)
228251881Speter            svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_LOG, NULL);
229251881Speter        }
230251881Speter    }
231251881Speter  else
232251881Speter    {
233251881Speter      if (rb->ra_session == NULL)
234251881Speter        SVN_ERR(svn_client_open_ra_session2(&rb->ra_session,
235251881Speter                                            rb->ra_session_url, NULL,
236251881Speter                                            rb->ctx, rb->ra_session_pool,
237251881Speter                                            pool));
238251881Speter
239251881Speter      SVN_ERR(svn_ra_rev_proplist(rb->ra_session, log_entry->revision,
240251881Speter                                  &log_entry->revprops, pool));
241251881Speter    }
242251881Speter
243251881Speter  return rb->receiver(rb->baton, log_entry, pool);
244251881Speter}
245251881Speter
246251881Speter/* limit receiver */
247251881Spetertypedef struct limit_receiver_baton_t
248251881Speter{
249251881Speter  int limit;
250251881Speter  svn_log_entry_receiver_t receiver;
251251881Speter  void *baton;
252251881Speter} limit_receiver_baton_t;
253251881Speter
254251881Speterstatic svn_error_t *
255251881Speterlimit_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool)
256251881Speter{
257251881Speter  limit_receiver_baton_t *rb = baton;
258251881Speter
259251881Speter  rb->limit--;
260251881Speter
261251881Speter  return rb->receiver(rb->baton, log_entry, pool);
262251881Speter}
263251881Speter
264251881Speter/* Resolve the URLs or WC path in TARGETS as per the svn_client_log5 API.
265251881Speter
266251881Speter   The limitations on TARGETS specified by svn_client_log5 are enforced here.
267251881Speter   So TARGETS can only contain a single WC path or a URL and zero or more
268251881Speter   relative paths -- anything else will raise an error.
269251881Speter
270251881Speter   PEG_REVISION, TARGETS, and CTX are as per svn_client_log5.
271251881Speter
272251881Speter   If TARGETS contains a single WC path then set *RA_TARGET to the absolute
273251881Speter   path of that single path if PEG_REVISION is dependent on the working copy
274251881Speter   (e.g. PREV).  Otherwise set *RA_TARGET to the corresponding URL for the
275251881Speter   single WC path.  Set *RELATIVE_TARGETS to an array with a single
276251881Speter   element "".
277251881Speter
278251881Speter   If TARGETS contains only a single URL, then set *RA_TARGET to a copy of
279251881Speter   that URL and *RELATIVE_TARGETS to an array with a single element "".
280251881Speter
281251881Speter   If TARGETS contains a single URL and one or more relative paths, then
282251881Speter   set *RA_TARGET to a copy of that URL and *RELATIVE_TARGETS to a copy of
283251881Speter   each relative path after the URL.
284251881Speter
285251881Speter   If *PEG_REVISION is svn_opt_revision_unspecified, then *PEG_REVISION is
286251881Speter   set to svn_opt_revision_head for URLs or svn_opt_revision_working for a
287251881Speter   WC path.
288251881Speter
289251881Speter   *RA_TARGET and *RELATIVE_TARGETS are allocated in RESULT_POOL. */
290251881Speterstatic svn_error_t *
291251881Speterresolve_log_targets(apr_array_header_t **relative_targets,
292251881Speter                    const char **ra_target,
293251881Speter                    svn_opt_revision_t *peg_revision,
294251881Speter                    const apr_array_header_t *targets,
295251881Speter                    svn_client_ctx_t *ctx,
296251881Speter                    apr_pool_t *result_pool,
297251881Speter                    apr_pool_t *scratch_pool)
298251881Speter{
299251881Speter  int i;
300251881Speter  svn_boolean_t url_targets;
301251881Speter
302251881Speter  /* Per svn_client_log5, TARGETS contains either a URL followed by zero or
303251881Speter     more relative paths, or one working copy path. */
304251881Speter  const char *url_or_path = APR_ARRAY_IDX(targets, 0, const char *);
305251881Speter
306251881Speter  /* svn_client_log5 requires at least one target. */
307251881Speter  if (targets->nelts == 0)
308251881Speter    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
309251881Speter                            _("No valid target found"));
310251881Speter
311251881Speter  /* Initialize the output array.  At a minimum, we need room for one
312251881Speter     (possibly empty) relpath.  Otherwise, we have to hold a relpath
313251881Speter     for every item in TARGETS except the first.  */
314251881Speter  *relative_targets = apr_array_make(result_pool,
315251881Speter                                     MAX(1, targets->nelts - 1),
316251881Speter                                     sizeof(const char *));
317251881Speter
318251881Speter  if (svn_path_is_url(url_or_path))
319251881Speter    {
320251881Speter      /* An unspecified PEG_REVISION for a URL path defaults
321251881Speter         to svn_opt_revision_head. */
322251881Speter      if (peg_revision->kind == svn_opt_revision_unspecified)
323251881Speter        peg_revision->kind = svn_opt_revision_head;
324251881Speter
325251881Speter      /* The logic here is this: If we get passed one argument, we assume
326251881Speter         it is the full URL to a file/dir we want log info for. If we get
327251881Speter         a URL plus some paths, then we assume that the URL is the base,
328251881Speter         and that the paths passed are relative to it.  */
329251881Speter      if (targets->nelts > 1)
330251881Speter        {
331251881Speter          /* We have some paths, let's use them. Start after the URL.  */
332251881Speter          for (i = 1; i < targets->nelts; i++)
333251881Speter            {
334251881Speter              const char *target;
335251881Speter
336251881Speter              target = APR_ARRAY_IDX(targets, i, const char *);
337251881Speter
338251881Speter              if (svn_path_is_url(target) || svn_dirent_is_absolute(target))
339251881Speter                return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
340251881Speter                                         _("'%s' is not a relative path"),
341251881Speter                                          target);
342251881Speter
343251881Speter              APR_ARRAY_PUSH(*relative_targets, const char *) =
344251881Speter                apr_pstrdup(result_pool, target);
345251881Speter            }
346251881Speter        }
347251881Speter      else
348251881Speter        {
349251881Speter          /* If we have a single URL, then the session will be rooted at
350251881Speter             it, so just send an empty string for the paths we are
351251881Speter             interested in. */
352251881Speter          APR_ARRAY_PUSH(*relative_targets, const char *) = "";
353251881Speter        }
354251881Speter
355251881Speter      /* Remember that our targets are URLs. */
356251881Speter      url_targets = TRUE;
357251881Speter    }
358251881Speter  else /* WC path target. */
359251881Speter    {
360251881Speter      const char *target;
361251881Speter      const char *target_abspath;
362251881Speter
363251881Speter      url_targets = FALSE;
364251881Speter      if (targets->nelts > 1)
365251881Speter        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
366251881Speter                                _("When specifying working copy paths, only "
367251881Speter                                  "one target may be given"));
368251881Speter
369251881Speter      /* An unspecified PEG_REVISION for a working copy path defaults
370251881Speter         to svn_opt_revision_working. */
371251881Speter      if (peg_revision->kind == svn_opt_revision_unspecified)
372251881Speter        peg_revision->kind = svn_opt_revision_working;
373251881Speter
374251881Speter      /* Get URLs for each target */
375251881Speter      target = APR_ARRAY_IDX(targets, 0, const char *);
376251881Speter
377251881Speter      SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, scratch_pool));
378251881Speter      SVN_ERR(svn_wc__node_get_url(&url_or_path, ctx->wc_ctx, target_abspath,
379251881Speter                                   scratch_pool, scratch_pool));
380251881Speter      APR_ARRAY_PUSH(*relative_targets, const char *) = "";
381251881Speter    }
382251881Speter
383251881Speter  /* If this is a revision type that requires access to the working copy,
384251881Speter   * we use our initial target path to figure out where to root the RA
385251881Speter   * session, otherwise we use our URL. */
386251881Speter  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
387251881Speter    {
388251881Speter      if (url_targets)
389251881Speter        return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
390251881Speter                                _("PREV, BASE, or COMMITTED revision "
391251881Speter                                  "keywords are invalid for URL"));
392251881Speter
393251881Speter      else
394251881Speter        SVN_ERR(svn_dirent_get_absolute(
395251881Speter          ra_target, APR_ARRAY_IDX(targets, 0, const char *), result_pool));
396251881Speter    }
397251881Speter  else
398251881Speter    {
399251881Speter      *ra_target = apr_pstrdup(result_pool, url_or_path);
400251881Speter    }
401251881Speter
402251881Speter  return SVN_NO_ERROR;
403251881Speter}
404251881Speter
405251881Speter/* Keep track of oldest and youngest opt revs found.
406251881Speter
407251881Speter   If REV is younger than *YOUNGEST_REV, or *YOUNGEST_REV is
408251881Speter   svn_opt_revision_unspecified, then set *YOUNGEST_REV equal to REV.
409251881Speter
410251881Speter   If REV is older than *OLDEST_REV, or *OLDEST_REV is
411251881Speter   svn_opt_revision_unspecified, then set *OLDEST_REV equal to REV. */
412251881Speterstatic void
413251881Speterfind_youngest_and_oldest_revs(svn_revnum_t *youngest_rev,
414251881Speter                              svn_revnum_t *oldest_rev,
415251881Speter                              svn_revnum_t rev)
416251881Speter{
417251881Speter  /* Is REV younger than YOUNGEST_REV? */
418251881Speter  if (! SVN_IS_VALID_REVNUM(*youngest_rev)
419251881Speter      || rev > *youngest_rev)
420251881Speter    *youngest_rev = rev;
421251881Speter
422251881Speter  if (! SVN_IS_VALID_REVNUM(*oldest_rev)
423251881Speter      || rev < *oldest_rev)
424251881Speter    *oldest_rev = rev;
425251881Speter}
426251881Speter
427251881Spetertypedef struct rev_range_t
428251881Speter{
429251881Speter  svn_revnum_t range_start;
430251881Speter  svn_revnum_t range_end;
431251881Speter} rev_range_t;
432251881Speter
433251881Speter/* Convert array of svn_opt_revision_t ranges to an array of svn_revnum_t
434251881Speter   ranges.
435251881Speter
436251881Speter   Given a log target URL_OR_ABSPATH@PEG_REV and an array of
437251881Speter   svn_opt_revision_range_t's OPT_REV_RANGES, resolve the opt revs in
438251881Speter   OPT_REV_RANGES to svn_revnum_t's and return these in *REVISION_RANGES, an
439251881Speter   array of rev_range_t *.
440251881Speter
441251881Speter   Set *YOUNGEST_REV and *OLDEST_REV to the youngest and oldest revisions
442251881Speter   found in *REVISION_RANGES.
443251881Speter
444251881Speter   If the repository needs to be contacted to resolve svn_opt_revision_date or
445251881Speter   svn_opt_revision_head revisions, then the session used to do this is
446251881Speter   RA_SESSION; it must be an open session to any URL in the right repository.
447251881Speter*/
448251881Speterstatic svn_error_t*
449251881Speterconvert_opt_rev_array_to_rev_range_array(
450251881Speter  apr_array_header_t **revision_ranges,
451251881Speter  svn_revnum_t *youngest_rev,
452251881Speter  svn_revnum_t *oldest_rev,
453251881Speter  svn_ra_session_t *ra_session,
454251881Speter  const char *url_or_abspath,
455251881Speter  const apr_array_header_t *opt_rev_ranges,
456251881Speter  const svn_opt_revision_t *peg_rev,
457251881Speter  svn_client_ctx_t *ctx,
458251881Speter  apr_pool_t *result_pool,
459251881Speter  apr_pool_t *scratch_pool)
460251881Speter{
461251881Speter  int i;
462251881Speter  svn_revnum_t head_rev = SVN_INVALID_REVNUM;
463251881Speter
464251881Speter  /* Initialize the input/output parameters. */
465251881Speter  *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM;
466251881Speter
467251881Speter  /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
468251881Speter     and oldest revision range that spans all of OPT_REV_RANGES. */
469251881Speter  *revision_ranges = apr_array_make(result_pool, opt_rev_ranges->nelts,
470251881Speter                                    sizeof(rev_range_t *));
471251881Speter
472251881Speter  for (i = 0; i < opt_rev_ranges->nelts; i++)
473251881Speter    {
474251881Speter      svn_opt_revision_range_t *range;
475251881Speter      rev_range_t *rev_range;
476251881Speter      svn_boolean_t start_same_as_end = FALSE;
477251881Speter
478251881Speter      range = APR_ARRAY_IDX(opt_rev_ranges, i, svn_opt_revision_range_t *);
479251881Speter
480251881Speter      /* Right now RANGE can be any valid pair of svn_opt_revision_t's.  We
481251881Speter         will now convert all RANGEs in place to the corresponding
482251881Speter         svn_opt_revision_number kind. */
483251881Speter      if ((range->start.kind != svn_opt_revision_unspecified)
484251881Speter          && (range->end.kind == svn_opt_revision_unspecified))
485251881Speter        {
486251881Speter          /* If the user specified exactly one revision, then start rev is
487251881Speter           * set but end is not.  We show the log message for just that
488251881Speter           * revision by making end equal to start.
489251881Speter           *
490251881Speter           * Note that if the user requested a single dated revision, then
491251881Speter           * this will cause the same date to be resolved twice.  The
492251881Speter           * extra code complexity to get around this slight inefficiency
493251881Speter           * doesn't seem worth it, however. */
494251881Speter          range->end = range->start;
495251881Speter        }
496251881Speter      else if (range->start.kind == svn_opt_revision_unspecified)
497251881Speter        {
498251881Speter          /* Default to any specified peg revision.  Otherwise, if the
499251881Speter           * first target is a URL, then we default to HEAD:0.  Lastly,
500251881Speter           * the default is BASE:0 since WC@HEAD may not exist. */
501251881Speter          if (peg_rev->kind == svn_opt_revision_unspecified)
502251881Speter            {
503251881Speter              if (svn_path_is_url(url_or_abspath))
504251881Speter                range->start.kind = svn_opt_revision_head;
505251881Speter              else
506251881Speter                range->start.kind = svn_opt_revision_base;
507251881Speter            }
508251881Speter          else
509251881Speter            range->start = *peg_rev;
510251881Speter
511251881Speter          if (range->end.kind == svn_opt_revision_unspecified)
512251881Speter            {
513251881Speter              range->end.kind = svn_opt_revision_number;
514251881Speter              range->end.value.number = 0;
515251881Speter            }
516251881Speter        }
517251881Speter
518251881Speter      if ((range->start.kind == svn_opt_revision_unspecified)
519251881Speter          || (range->end.kind == svn_opt_revision_unspecified))
520251881Speter        {
521251881Speter          return svn_error_create
522251881Speter            (SVN_ERR_CLIENT_BAD_REVISION, NULL,
523251881Speter             _("Missing required revision specification"));
524251881Speter        }
525251881Speter
526251881Speter      /* Does RANGE describe a single svn_opt_revision_t? */
527251881Speter      if (range->start.kind == range->end.kind)
528251881Speter        {
529251881Speter          if (range->start.kind == svn_opt_revision_number)
530251881Speter            {
531251881Speter              if (range->start.value.number == range->end.value.number)
532251881Speter                start_same_as_end = TRUE;
533251881Speter            }
534251881Speter          else if (range->start.kind == svn_opt_revision_date)
535251881Speter            {
536251881Speter              if (range->start.value.date == range->end.value.date)
537251881Speter                start_same_as_end = TRUE;
538251881Speter            }
539251881Speter          else
540251881Speter            {
541251881Speter              start_same_as_end = TRUE;
542251881Speter            }
543251881Speter        }
544251881Speter
545251881Speter      rev_range = apr_palloc(result_pool, sizeof(*rev_range));
546251881Speter      SVN_ERR(svn_client__get_revision_number(
547251881Speter                &rev_range->range_start, &head_rev,
548251881Speter                ctx->wc_ctx, url_or_abspath, ra_session,
549251881Speter                &range->start, scratch_pool));
550251881Speter      if (start_same_as_end)
551251881Speter        rev_range->range_end = rev_range->range_start;
552251881Speter      else
553251881Speter        SVN_ERR(svn_client__get_revision_number(
554251881Speter                  &rev_range->range_end, &head_rev,
555251881Speter                  ctx->wc_ctx, url_or_abspath, ra_session,
556251881Speter                  &range->end, scratch_pool));
557251881Speter
558251881Speter      /* Possibly update the oldest and youngest revisions requested. */
559251881Speter      find_youngest_and_oldest_revs(youngest_rev,
560251881Speter                                    oldest_rev,
561251881Speter                                    rev_range->range_start);
562251881Speter      find_youngest_and_oldest_revs(youngest_rev,
563251881Speter                                    oldest_rev,
564251881Speter                                    rev_range->range_end);
565251881Speter      APR_ARRAY_PUSH(*revision_ranges, rev_range_t *) = rev_range;
566251881Speter    }
567251881Speter
568251881Speter  return SVN_NO_ERROR;
569251881Speter}
570251881Speter
571251881Speterstatic int
572251881Spetercompare_rev_to_segment(const void *key_p,
573251881Speter                       const void *element_p)
574251881Speter{
575251881Speter  svn_revnum_t rev =
576251881Speter    * (svn_revnum_t *)key_p;
577251881Speter  const svn_location_segment_t *segment =
578251881Speter    *((const svn_location_segment_t * const *) element_p);
579251881Speter
580251881Speter  if (rev < segment->range_start)
581251881Speter    return -1;
582251881Speter  else if (rev > segment->range_end)
583251881Speter    return 1;
584251881Speter  else
585251881Speter    return 0;
586251881Speter}
587251881Speter
588251881Speter/* Run svn_ra_get_log2 for PATHS, one or more paths relative to RA_SESSION's
589251881Speter   common parent, for each revision in REVISION_RANGES, an array of
590251881Speter   rev_range_t.
591251881Speter
592251881Speter   RA_SESSION is an open session pointing to ACTUAL_LOC.
593251881Speter
594251881Speter   LOG_SEGMENTS is an array of svn_location_segment_t * items representing the
595251881Speter   history of PATHS from the oldest to youngest revisions found in
596251881Speter   REVISION_RANGES.
597251881Speter
598251881Speter   The TARGETS, LIMIT, DISCOVER_CHANGED_PATHS, STRICT_NODE_HISTORY,
599251881Speter   INCLUDE_MERGED_REVISIONS, REVPROPS, REAL_RECEIVER, and REAL_RECEIVER_BATON
600251881Speter   parameters are all as per the svn_client_log5 API. */
601251881Speterstatic svn_error_t *
602251881Speterrun_ra_get_log(apr_array_header_t *revision_ranges,
603251881Speter               apr_array_header_t *paths,
604251881Speter               apr_array_header_t *log_segments,
605251881Speter               svn_client__pathrev_t *actual_loc,
606251881Speter               svn_ra_session_t *ra_session,
607251881Speter               /* The following are as per svn_client_log5. */
608251881Speter               const apr_array_header_t *targets,
609251881Speter               int limit,
610251881Speter               svn_boolean_t discover_changed_paths,
611251881Speter               svn_boolean_t strict_node_history,
612251881Speter               svn_boolean_t include_merged_revisions,
613251881Speter               const apr_array_header_t *revprops,
614251881Speter               svn_log_entry_receiver_t real_receiver,
615251881Speter               void *real_receiver_baton,
616251881Speter               svn_client_ctx_t *ctx,
617251881Speter               apr_pool_t *scratch_pool)
618251881Speter{
619251881Speter  int i;
620251881Speter  pre_15_receiver_baton_t rb = {0};
621251881Speter  apr_pool_t *iterpool;
622251881Speter  svn_boolean_t has_log_revprops;
623251881Speter
624251881Speter  SVN_ERR(svn_ra_has_capability(ra_session, &has_log_revprops,
625251881Speter                                SVN_RA_CAPABILITY_LOG_REVPROPS,
626251881Speter                                scratch_pool));
627251881Speter
628251881Speter  if (!has_log_revprops)
629251881Speter    {
630251881Speter      /* See above pre-1.5 notes. */
631251881Speter      rb.ctx = ctx;
632251881Speter
633251881Speter      /* Create ra session on first use */
634251881Speter      rb.ra_session_pool = scratch_pool;
635251881Speter      rb.ra_session_url = actual_loc->url;
636251881Speter    }
637251881Speter
638251881Speter  /* It's a bit complex to correctly handle the special revision words
639251881Speter   * such as "BASE", "COMMITTED", and "PREV".  For example, if the
640251881Speter   * user runs
641251881Speter   *
642251881Speter   *   $ svn log -rCOMMITTED foo.txt bar.c
643251881Speter   *
644251881Speter   * which committed rev should be used?  The younger of the two?  The
645251881Speter   * first one?  Should we just error?
646251881Speter   *
647251881Speter   * None of the above, I think.  Rather, the committed rev of each
648251881Speter   * target in turn should be used.  This is what most users would
649251881Speter   * expect, and is the most useful interpretation.  Of course, this
650251881Speter   * goes for the other dynamic (i.e., local) revision words too.
651251881Speter   *
652251881Speter   * Note that the code to do this is a bit more complex than a simple
653251881Speter   * loop, because the user might run
654251881Speter   *
655251881Speter   *    $ svn log -rCOMMITTED:42 foo.txt bar.c
656251881Speter   *
657251881Speter   * in which case we want to avoid recomputing the static revision on
658251881Speter   * every iteration.
659251881Speter   *
660251881Speter   * ### FIXME: However, we can't yet handle multiple wc targets anyway.
661251881Speter   *
662251881Speter   * We used to iterate over each target in turn, getting the logs for
663251881Speter   * the named range.  This led to revisions being printed in strange
664251881Speter   * order or being printed more than once.  This is issue 1550.
665251881Speter   *
666251881Speter   * In r851673, jpieper blocked multiple wc targets in svn/log-cmd.c,
667251881Speter   * meaning this block not only doesn't work right in that case, but isn't
668251881Speter   * even testable that way (svn has no unit test suite; we can only test
669251881Speter   * via the svn command).  So, that check is now moved into this function
670251881Speter   * (see above).
671251881Speter   *
672251881Speter   * kfogel ponders future enhancements in r844260:
673251881Speter   * I think that's okay behavior, since the sense of the command is
674251881Speter   * that one wants a particular range of logs for *this* file, then
675251881Speter   * another range for *that* file, and so on.  But we should
676251881Speter   * probably put some sort of separator header between the log
677251881Speter   * groups.  Of course, libsvn_client can't just print stuff out --
678251881Speter   * it has to take a callback from the client to do that.  So we
679251881Speter   * need to define that callback interface, then have the command
680251881Speter   * line client pass one down here.
681251881Speter   *
682251881Speter   * epg wonders if the repository could send a unified stream of log
683251881Speter   * entries if the paths and revisions were passed down.
684251881Speter   */
685251881Speter  iterpool = svn_pool_create(scratch_pool);
686251881Speter  for (i = 0; i < revision_ranges->nelts; i++)
687251881Speter    {
688251881Speter      const char *old_session_url;
689251881Speter      const char *path = APR_ARRAY_IDX(targets, 0, const char *);
690251881Speter      const char *local_abspath_or_url;
691251881Speter      rev_range_t *range;
692251881Speter      limit_receiver_baton_t lb;
693251881Speter      svn_log_entry_receiver_t passed_receiver;
694251881Speter      void *passed_receiver_baton;
695251881Speter      const apr_array_header_t *passed_receiver_revprops;
696251881Speter      svn_location_segment_t **matching_segment;
697251881Speter      svn_revnum_t younger_rev;
698251881Speter
699251881Speter      svn_pool_clear(iterpool);
700251881Speter
701251881Speter      if (!svn_path_is_url(path))
702251881Speter        SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path,
703251881Speter                                        iterpool));
704251881Speter      else
705251881Speter        local_abspath_or_url = path;
706251881Speter
707251881Speter      range = APR_ARRAY_IDX(revision_ranges, i, rev_range_t *);
708251881Speter
709251881Speter      /* Issue #4355: Account for renames spanning requested
710251881Speter         revision ranges. */
711251881Speter      younger_rev = MAX(range->range_start, range->range_end);
712251881Speter      matching_segment = bsearch(&younger_rev, log_segments->elts,
713251881Speter                                 log_segments->nelts, log_segments->elt_size,
714251881Speter                                 compare_rev_to_segment);
715253734Speter      /* LOG_SEGMENTS is supposed to represent the history of PATHS from
716253734Speter         the oldest to youngest revs in REVISION_RANGES.  This function's
717253734Speter         current sole caller svn_client_log5 *should* be providing
718253734Speter         LOG_SEGMENTS that span the oldest to youngest revs in
719253734Speter         REVISION_RANGES, even if one or more of the svn_location_segment_t's
720253734Speter         returned have NULL path members indicating a gap in the history. So
721253734Speter         MATCHING_SEGMENT should never be NULL, but clearly sometimes it is,
722253734Speter         see http://svn.haxx.se/dev/archive-2013-06/0522.shtml
723253734Speter         So to be safe we handle that case. */
724253734Speter      if (matching_segment == NULL)
725253734Speter        continue;
726251881Speter
727251881Speter      /* A segment with a NULL path means there is gap in the history.
728251881Speter         We'll just proceed and let svn_ra_get_log2 fail with a useful
729251881Speter         error...*/
730251881Speter      if ((*matching_segment)->path != NULL)
731251881Speter        {
732251881Speter          /* ...but if there is history, then we must account for issue
733251881Speter             #4355 and make sure our RA session is pointing at the correct
734251881Speter             location. */
735251881Speter          const char *segment_url = svn_path_url_add_component2(
736251881Speter            actual_loc->repos_root_url, (*matching_segment)->path,
737251881Speter            scratch_pool);
738251881Speter          SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
739251881Speter                                                    ra_session,
740251881Speter                                                    segment_url,
741251881Speter                                                    scratch_pool));
742251881Speter        }
743251881Speter
744251881Speter      if (has_log_revprops)
745251881Speter        {
746251881Speter          passed_receiver = real_receiver;
747251881Speter          passed_receiver_baton = real_receiver_baton;
748251881Speter          passed_receiver_revprops = revprops;
749251881Speter        }
750251881Speter      else
751251881Speter        {
752251881Speter          rb.revprops = revprops;
753251881Speter          rb.receiver = real_receiver;
754251881Speter          rb.baton = real_receiver_baton;
755251881Speter
756251881Speter          passed_receiver = pre_15_receiver;
757251881Speter          passed_receiver_baton = &rb;
758251881Speter          passed_receiver_revprops = svn_compat_log_revprops_in(iterpool);
759251881Speter        }
760251881Speter
761251881Speter      if (limit && revision_ranges->nelts > 1)
762251881Speter        {
763251881Speter          lb.limit = limit;
764251881Speter          lb.receiver = passed_receiver;
765251881Speter          lb.baton = passed_receiver_baton;
766251881Speter
767251881Speter          passed_receiver = limit_receiver;
768251881Speter          passed_receiver_baton = &lb;
769251881Speter        }
770251881Speter
771251881Speter      SVN_ERR(svn_ra_get_log2(ra_session,
772251881Speter                              paths,
773251881Speter                              range->range_start,
774251881Speter                              range->range_end,
775251881Speter                              limit,
776251881Speter                              discover_changed_paths,
777251881Speter                              strict_node_history,
778251881Speter                              include_merged_revisions,
779251881Speter                              passed_receiver_revprops,
780251881Speter                              passed_receiver,
781251881Speter                              passed_receiver_baton,
782251881Speter                              iterpool));
783251881Speter
784251881Speter      if (limit && revision_ranges->nelts > 1)
785251881Speter        {
786251881Speter          limit = lb.limit;
787251881Speter          if (limit == 0)
788251881Speter            {
789251881Speter              return SVN_NO_ERROR;
790251881Speter            }
791251881Speter        }
792251881Speter    }
793251881Speter  svn_pool_destroy(iterpool);
794251881Speter
795251881Speter  return SVN_NO_ERROR;
796251881Speter}
797251881Speter
798251881Speter/*** Public Interface. ***/
799251881Speter
800251881Spetersvn_error_t *
801251881Spetersvn_client_log5(const apr_array_header_t *targets,
802251881Speter                const svn_opt_revision_t *peg_revision,
803251881Speter                const apr_array_header_t *opt_rev_ranges,
804251881Speter                int limit,
805251881Speter                svn_boolean_t discover_changed_paths,
806251881Speter                svn_boolean_t strict_node_history,
807251881Speter                svn_boolean_t include_merged_revisions,
808251881Speter                const apr_array_header_t *revprops,
809251881Speter                svn_log_entry_receiver_t real_receiver,
810251881Speter                void *real_receiver_baton,
811251881Speter                svn_client_ctx_t *ctx,
812251881Speter                apr_pool_t *pool)
813251881Speter{
814251881Speter  svn_ra_session_t *ra_session;
815251881Speter  const char *old_session_url;
816251881Speter  const char *ra_target;
817251881Speter  svn_opt_revision_t youngest_opt_rev;
818251881Speter  svn_revnum_t youngest_rev;
819251881Speter  svn_revnum_t oldest_rev;
820251881Speter  svn_opt_revision_t peg_rev;
821251881Speter  svn_client__pathrev_t *actual_loc;
822251881Speter  apr_array_header_t *log_segments;
823251881Speter  apr_array_header_t *revision_ranges;
824251881Speter  apr_array_header_t *relative_targets;
825251881Speter
826251881Speter  if (opt_rev_ranges->nelts == 0)
827251881Speter    {
828251881Speter      return svn_error_create
829251881Speter        (SVN_ERR_CLIENT_BAD_REVISION, NULL,
830251881Speter         _("Missing required revision specification"));
831251881Speter    }
832251881Speter
833251881Speter  /* Make a copy of PEG_REVISION, we may need to change it to a
834251881Speter     default value. */
835251881Speter  peg_rev = *peg_revision;
836251881Speter
837251881Speter  SVN_ERR(resolve_log_targets(&relative_targets, &ra_target, &peg_rev,
838251881Speter                              targets, ctx, pool, pool));
839251881Speter
840251881Speter  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &actual_loc,
841251881Speter                                            ra_target, NULL, &peg_rev, &peg_rev,
842251881Speter                                            ctx, pool));
843251881Speter
844251881Speter  /* Convert OPT_REV_RANGES to an array of rev_range_t and find the youngest
845251881Speter     and oldest revision range that spans all of OPT_REV_RANGES. */
846251881Speter  SVN_ERR(convert_opt_rev_array_to_rev_range_array(&revision_ranges,
847251881Speter                                                   &youngest_rev,
848251881Speter                                                   &oldest_rev,
849251881Speter                                                   ra_session,
850251881Speter                                                   ra_target,
851251881Speter                                                   opt_rev_ranges, &peg_rev,
852251881Speter                                                   ctx, pool,  pool));
853251881Speter
854251881Speter  /* Make ACTUAL_LOC and RA_SESSION point to the youngest operative rev. */
855251881Speter  youngest_opt_rev.kind = svn_opt_revision_number;
856251881Speter  youngest_opt_rev.value.number = youngest_rev;
857251881Speter  SVN_ERR(svn_client__resolve_rev_and_url(&actual_loc, ra_session,
858251881Speter                                          ra_target, &peg_rev,
859251881Speter                                          &youngest_opt_rev, ctx, pool));
860251881Speter  SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
861251881Speter                                            actual_loc->url, pool));
862251881Speter
863253734Speter  /* Save us an RA layer round trip if we are on the repository root and
864253734Speter     know the result in advance.  All the revision data has already been
865253734Speter     validated.
866253734Speter   */
867253734Speter  if (strcmp(actual_loc->url, actual_loc->repos_root_url) == 0)
868253734Speter    {
869253734Speter      svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
870253734Speter      log_segments = apr_array_make(pool, 1, sizeof(segment));
871251881Speter
872253734Speter      segment->range_start = oldest_rev;
873253734Speter      segment->range_end = actual_loc->rev;
874253734Speter      segment->path = "";
875253734Speter      APR_ARRAY_PUSH(log_segments, svn_location_segment_t *) = segment;
876253734Speter    }
877253734Speter  else
878253734Speter    {
879253734Speter      /* Get the svn_location_segment_t's representing the requested log
880253734Speter       * ranges. */
881253734Speter      SVN_ERR(svn_client__repos_location_segments(&log_segments, ra_session,
882253734Speter                                                  actual_loc->url,
883253734Speter                                                  actual_loc->rev, /* peg */
884253734Speter                                                  actual_loc->rev, /* start */
885253734Speter                                                  oldest_rev,      /* end */
886253734Speter                                                  ctx, pool));
887253734Speter    }
888253734Speter
889253734Speter
890251881Speter  SVN_ERR(run_ra_get_log(revision_ranges, relative_targets, log_segments,
891251881Speter                         actual_loc, ra_session, targets, limit,
892251881Speter                         discover_changed_paths, strict_node_history,
893251881Speter                         include_merged_revisions, revprops, real_receiver,
894251881Speter                         real_receiver_baton, ctx, pool));
895251881Speter
896251881Speter  return SVN_NO_ERROR;
897251881Speter}
898