compat.c revision 362181
1/*
2 * compat.c:  compatibility compliance logic
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#include <apr_pools.h>
25
26#include "svn_hash.h"
27#include "svn_error.h"
28#include "svn_pools.h"
29#include "svn_sorts.h"
30#include "svn_dirent_uri.h"
31#include "svn_path.h"
32#include "svn_ra.h"
33#include "svn_io.h"
34#include "svn_compat.h"
35#include "svn_props.h"
36
37#include "private/svn_fspath.h"
38#include "private/svn_sorts_private.h"
39#include "ra_loader.h"
40#include "svn_private_config.h"
41
42
43
44/* This is just like svn_sort_compare_revisions, save that it sorts
45   the revisions in *ascending* order. */
46static int
47compare_revisions(const void *a, const void *b)
48{
49  svn_revnum_t a_rev = *(const svn_revnum_t *)a;
50  svn_revnum_t b_rev = *(const svn_revnum_t *)b;
51  if (a_rev == b_rev)
52    return 0;
53  return a_rev < b_rev ? -1 : 1;
54}
55
56/* Given the CHANGED_PATHS and REVISION from an instance of a
57   svn_log_message_receiver_t function, determine at which location
58   PATH may be expected in the next log message, and set *PREV_PATH_P
59   to that value.  KIND is the node kind of PATH.  Set *ACTION_P to a
60   character describing the change that caused this revision (as
61   listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
62   revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
63   copied.  ACTION_P and COPYFROM_REV_P may be NULL, in which case
64   they are not used.  Perform all allocations in POOL.
65
66   This is useful for tracking the various changes in location a
67   particular resource has undergone when performing an RA->get_logs()
68   operation on that resource.
69*/
70static svn_error_t *
71prev_log_path(const char **prev_path_p,
72              char *action_p,
73              svn_revnum_t *copyfrom_rev_p,
74              apr_hash_t *changed_paths,
75              const char *path,
76              svn_node_kind_t kind,
77              svn_revnum_t revision,
78              apr_pool_t *pool)
79{
80  svn_log_changed_path_t *change;
81  const char *prev_path = NULL;
82
83  /* It's impossible to find the predecessor path of a NULL path. */
84  SVN_ERR_ASSERT(path);
85
86  /* Initialize our return values for the action and copyfrom_rev in
87     case we have an unhandled case later on. */
88  if (action_p)
89    *action_p = 'M';
90  if (copyfrom_rev_p)
91    *copyfrom_rev_p = SVN_INVALID_REVNUM;
92
93  if (changed_paths)
94    {
95      /* See if PATH was explicitly changed in this revision. */
96      change = svn_hash_gets(changed_paths, path);
97      if (change)
98        {
99          /* If PATH was not newly added in this revision, then it may or may
100             not have also been part of a moved subtree.  In this case, set a
101             default previous path, but still look through the parents of this
102             path for a possible copy event. */
103          if (change->action != 'A' && change->action != 'R')
104            {
105              prev_path = path;
106            }
107          else
108            {
109              /* PATH is new in this revision.  This means it cannot have been
110                 part of a copied subtree. */
111              if (change->copyfrom_path)
112                prev_path = apr_pstrdup(pool, change->copyfrom_path);
113              else
114                prev_path = NULL;
115
116              *prev_path_p = prev_path;
117              if (action_p)
118                *action_p = change->action;
119              if (copyfrom_rev_p)
120                *copyfrom_rev_p = change->copyfrom_rev;
121              return SVN_NO_ERROR;
122            }
123        }
124
125      if (apr_hash_count(changed_paths))
126        {
127          /* The path was not explicitly changed in this revision.  The
128             fact that we're hearing about this revision implies, then,
129             that the path was a child of some copied directory.  We need
130             to find that directory, and effectively "re-base" our path on
131             that directory's copyfrom_path. */
132          int i;
133          apr_array_header_t *paths;
134
135          /* Build a sorted list of the changed paths. */
136          paths = svn_sort__hash(changed_paths,
137                                 svn_sort_compare_items_as_paths, pool);
138
139          /* Now, walk the list of paths backwards, looking a parent of
140             our path that has copyfrom information. */
141          for (i = paths->nelts; i > 0; i--)
142            {
143              svn_sort__item_t item = APR_ARRAY_IDX(paths,
144                                                    i - 1, svn_sort__item_t);
145              const char *ch_path = item.key;
146              size_t len = strlen(ch_path);
147
148              /* See if our path is the child of this change path.  If
149                 not, keep looking.  */
150              if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/')))
151                continue;
152
153              /* Okay, our path *is* a child of this change path.  If
154                 this change was copied, we just need to apply the
155                 portion of our path that is relative to this change's
156                 path, to the change's copyfrom path.  Otherwise, this
157                 change isn't really interesting to us, and our search
158                 continues. */
159              change = item.value;
160              if (change->copyfrom_path)
161                {
162                  if (action_p)
163                    *action_p = change->action;
164                  if (copyfrom_rev_p)
165                    *copyfrom_rev_p = change->copyfrom_rev;
166                  prev_path = svn_fspath__join(change->copyfrom_path,
167                                               path + len + 1, pool);
168                  break;
169                }
170            }
171        }
172    }
173
174  /* If we didn't find what we expected to find, return an error.
175     (Because directories bubble-up, we get a bunch of logs we might
176     not want.  Be forgiving in that case.)  */
177  if (! prev_path)
178    {
179      if (kind == svn_node_dir)
180        prev_path = apr_pstrdup(pool, path);
181      else
182        return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
183                                 _("Missing changed-path information for "
184                                   "'%s' in revision %ld"),
185                                 svn_dirent_local_style(path, pool), revision);
186    }
187
188  *prev_path_p = prev_path;
189  return SVN_NO_ERROR;
190}
191
192
193/* Set *FS_PATH_P to the absolute filesystem path associated with the
194   URL built from SESSION's URL and REL_PATH (which is relative to
195   session's URL.  Use POOL for allocations. */
196static svn_error_t *
197get_fs_path(const char **fs_path_p,
198            svn_ra_session_t *session,
199            const char *rel_path,
200            apr_pool_t *pool)
201{
202  const char *url, *fs_path;
203
204  SVN_ERR(svn_ra_get_session_url(session, &url, pool));
205  SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool));
206  *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path,
207                                                         rel_path, pool),
208                                        pool);
209  return SVN_NO_ERROR;
210}
211
212
213
214/*** Fallback implementation of svn_ra_get_locations(). ***/
215
216
217/* ### This is to support 1.0 servers. */
218struct log_receiver_baton
219{
220  /* The kind of the path we're tracing. */
221  svn_node_kind_t kind;
222
223  /* The path at which we are trying to find our versioned resource in
224     the log output. */
225  const char *last_path;
226
227  /* Input revisions and output hash; the whole point of this little game. */
228  svn_revnum_t peg_revision;
229  apr_array_header_t *location_revisions;
230  const char *peg_path;
231  apr_hash_t *locations;
232
233  /* A pool from which to allocate stuff stored in this baton. */
234  apr_pool_t *pool;
235};
236
237
238/* Implements svn_log_entry_receiver_t; helper for slow_get_locations.
239   As input, takes log_receiver_baton (defined above) and attempts to
240   "fill in" locations in the baton over the course of many
241   iterations. */
242static svn_error_t *
243log_receiver(void *baton,
244             svn_log_entry_t *log_entry,
245             apr_pool_t *pool)
246{
247  struct log_receiver_baton *lrb = baton;
248  apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations);
249  const char *current_path = lrb->last_path;
250  const char *prev_path;
251
252  /* No paths were changed in this revision.  Nothing to do. */
253  if (! log_entry->changed_paths2)
254    return SVN_NO_ERROR;
255
256  /* If we've run off the end of the path's history, there's nothing
257     to do.  (This should never happen with a properly functioning
258     server, since we'd get no more log messages after the one where
259     path was created.  But a malfunctioning server shouldn't cause us
260     to trigger an assertion failure.) */
261  if (! current_path)
262    return SVN_NO_ERROR;
263
264  /* If we haven't found our peg path yet, and we are now looking at a
265     revision equal to or older than the peg revision, then our
266     "current" path is our peg path. */
267  if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision))
268    lrb->peg_path = apr_pstrdup(lrb->pool, current_path);
269
270  /* Determine the paths for any of the revisions for which we haven't
271     gotten paths already. */
272  while (lrb->location_revisions->nelts)
273    {
274      svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions,
275                                        lrb->location_revisions->nelts - 1,
276                                        svn_revnum_t);
277      if (log_entry->revision <= next)
278        {
279          apr_hash_set(lrb->locations,
280                       apr_pmemdup(hash_pool, &next, sizeof(next)),
281                       sizeof(next),
282                       apr_pstrdup(hash_pool, current_path));
283          apr_array_pop(lrb->location_revisions);
284        }
285      else
286        break;
287    }
288
289  /* Figure out at which repository path our object of interest lived
290     in the previous revision. */
291  SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2,
292                        current_path, lrb->kind, log_entry->revision, pool));
293
294  /* Squirrel away our "next place to look" path (suffer the strcmp
295     hit to save on allocations). */
296  if (! prev_path)
297    lrb->last_path = NULL;
298  else if (strcmp(prev_path, current_path) != 0)
299    lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
300
301  return SVN_NO_ERROR;
302}
303
304
305svn_error_t *
306svn_ra__locations_from_log(svn_ra_session_t *session,
307                           apr_hash_t **locations_p,
308                           const char *path,
309                           svn_revnum_t peg_revision,
310                           const apr_array_header_t *location_revisions,
311                           apr_pool_t *pool)
312{
313  apr_hash_t *locations = apr_hash_make(pool);
314  struct log_receiver_baton lrb = { 0 };
315  apr_array_header_t *targets;
316  svn_revnum_t youngest_requested, oldest_requested, youngest, oldest;
317  svn_node_kind_t kind;
318  const char *fs_path;
319  apr_array_header_t *sorted_location_revisions;
320
321  /* Fetch the absolute FS path associated with PATH. */
322  SVN_ERR(get_fs_path(&fs_path, session, path, pool));
323
324  /* Sanity check: verify that the peg-object exists in repos. */
325  SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
326  if (kind == svn_node_none)
327    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
328                             _("Path '%s' doesn't exist in revision %ld"),
329                             fs_path, peg_revision);
330
331  /* Easy out: no location revisions. */
332  if (! location_revisions->nelts)
333    {
334      *locations_p = locations;
335      return SVN_NO_ERROR;
336    }
337
338  /* Figure out the youngest and oldest revs (amongst the set of
339     requested revisions + the peg revision) so we can avoid
340     unnecessary log parsing. */
341  sorted_location_revisions = apr_array_copy(pool, location_revisions);
342  svn_sort__array(sorted_location_revisions, compare_revisions);
343  oldest_requested = APR_ARRAY_IDX(sorted_location_revisions, 0, svn_revnum_t);
344  youngest_requested = APR_ARRAY_IDX(sorted_location_revisions,
345                                     sorted_location_revisions->nelts - 1,
346                                     svn_revnum_t);
347  youngest = peg_revision;
348  youngest = (oldest_requested > youngest) ? oldest_requested : youngest;
349  youngest = (youngest_requested > youngest) ? youngest_requested : youngest;
350  oldest = peg_revision;
351  oldest = (oldest_requested < oldest) ? oldest_requested : oldest;
352  oldest = (youngest_requested < oldest) ? youngest_requested : oldest;
353
354  /* Populate most of our log receiver baton structure. */
355  lrb.kind = kind;
356  lrb.last_path = fs_path;
357  lrb.location_revisions = apr_array_copy(pool, sorted_location_revisions);
358  lrb.peg_revision = peg_revision;
359  lrb.peg_path = NULL;
360  lrb.locations = locations;
361  lrb.pool = pool;
362
363  /* Let the RA layer drive our log information handler, which will do
364     the work of finding the actual locations for our resource.
365     Notice that we always run on the youngest rev of the 3 inputs. */
366  targets = apr_array_make(pool, 1, sizeof(const char *));
367  APR_ARRAY_PUSH(targets, const char *) = path;
368  SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0,
369                          TRUE, FALSE, FALSE,
370                          apr_array_make(pool, 0, sizeof(const char *)),
371                          log_receiver, &lrb, pool));
372
373  /* If the received log information did not cover any of the
374     requested revisions, use the last known path.  (This normally
375     just means that FS_PATH was not modified between the requested
376     revision and OLDEST.  If the file was created at some point after
377     OLDEST, then lrb.last_path should be NULL.) */
378  if (! lrb.peg_path)
379    lrb.peg_path = lrb.last_path;
380  if (lrb.last_path)
381    {
382      int i;
383      for (i = 0; i < sorted_location_revisions->nelts; i++)
384        {
385          svn_revnum_t rev = APR_ARRAY_IDX(sorted_location_revisions, i,
386                                           svn_revnum_t);
387          if (! apr_hash_get(locations, &rev, sizeof(rev)))
388            apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)),
389                         sizeof(rev), apr_pstrdup(pool, lrb.last_path));
390        }
391    }
392
393  /* Check that we got the peg path. */
394  if (! lrb.peg_path)
395    return svn_error_createf
396      (APR_EGENERAL, NULL,
397       _("Unable to find repository location for '%s' in revision %ld"),
398       fs_path, peg_revision);
399
400  /* Sanity check: make sure that our calculated peg path is the same
401     as what we expected it to be. */
402  if (strcmp(fs_path, lrb.peg_path) != 0)
403    return svn_error_createf
404      (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
405       _("'%s' in revision %ld is an unrelated object"),
406       fs_path, youngest);
407
408  *locations_p = locations;
409  return SVN_NO_ERROR;
410}
411
412
413
414
415/*** Fallback implementation of svn_ra_get_location_segments(). ***/
416
417struct gls_log_receiver_baton {
418  /* The kind of the path we're tracing. */
419  svn_node_kind_t kind;
420
421  /* Are we finished (and just listening to log entries because our
422     caller won't shut up?). */
423  svn_boolean_t done;
424
425  /* The path at which we are trying to find our versioned resource in
426     the log output. */
427  const char *last_path;
428
429  /* Input data. */
430  svn_revnum_t start_rev;
431
432  /* Output intermediate state and callback/baton. */
433  svn_revnum_t range_end;
434  svn_location_segment_receiver_t receiver;
435  void *receiver_baton;
436
437  /* A pool from which to allocate stuff stored in this baton. */
438  apr_pool_t *pool;
439};
440
441/* Build a node location segment object from PATH, RANGE_START, and
442   RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */
443static svn_error_t *
444maybe_crop_and_send_segment(const char *path,
445                            svn_revnum_t start_rev,
446                            svn_revnum_t range_start,
447                            svn_revnum_t range_end,
448                            svn_location_segment_receiver_t receiver,
449                            void *receiver_baton,
450                            apr_pool_t *pool)
451{
452  svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
453  segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL;
454  segment->range_start = range_start;
455  segment->range_end = range_end;
456  if (segment->range_start <= start_rev)
457    {
458      if (segment->range_end > start_rev)
459        segment->range_end = start_rev;
460      return receiver(segment, receiver_baton, pool);
461    }
462  return SVN_NO_ERROR;
463}
464
465static svn_error_t *
466gls_log_receiver(void *baton,
467                 svn_log_entry_t *log_entry,
468                 apr_pool_t *pool)
469{
470  struct gls_log_receiver_baton *lrb = baton;
471  const char *current_path = lrb->last_path;
472  const char *prev_path;
473  svn_revnum_t copyfrom_rev;
474
475  /* If we're done, ignore this invocation. */
476  if (lrb->done)
477    return SVN_NO_ERROR;
478
479  /* Figure out at which repository path our object of interest lived
480     in the previous revision, and if its current location is the
481     result of copy since then. */
482  SVN_ERR(prev_log_path(&prev_path, NULL, &copyfrom_rev,
483                        log_entry->changed_paths2, current_path,
484                        lrb->kind, log_entry->revision, pool));
485
486  /* If we've run off the end of the path's history, we need to report
487     our final segment (and then, we're done). */
488  if (! prev_path)
489    {
490      lrb->done = TRUE;
491      return maybe_crop_and_send_segment(current_path, lrb->start_rev,
492                                         log_entry->revision, lrb->range_end,
493                                         lrb->receiver, lrb->receiver_baton,
494                                         pool);
495    }
496
497  /* If there was a copy operation of interest... */
498  if (SVN_IS_VALID_REVNUM(copyfrom_rev))
499    {
500      /* ...then report the segment between this revision and the
501         last-reported revision. */
502      SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev,
503                                          log_entry->revision, lrb->range_end,
504                                          lrb->receiver, lrb->receiver_baton,
505                                          pool));
506      lrb->range_end = log_entry->revision - 1;
507
508      /* And if there was a revision gap, we need to report that, too. */
509      if (log_entry->revision - copyfrom_rev > 1)
510        {
511          SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev,
512                                              copyfrom_rev + 1, lrb->range_end,
513                                              lrb->receiver,
514                                              lrb->receiver_baton, pool));
515          lrb->range_end = copyfrom_rev;
516        }
517
518      /* Update our state variables. */
519      lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
520    }
521
522  return SVN_NO_ERROR;
523}
524
525
526svn_error_t *
527svn_ra__location_segments_from_log(svn_ra_session_t *session,
528                                   const char *path,
529                                   svn_revnum_t peg_revision,
530                                   svn_revnum_t start_rev,
531                                   svn_revnum_t end_rev,
532                                   svn_location_segment_receiver_t receiver,
533                                   void *receiver_baton,
534                                   apr_pool_t *pool)
535{
536  struct gls_log_receiver_baton lrb = { 0 };
537  apr_array_header_t *targets;
538  svn_node_kind_t kind;
539  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
540  const char *fs_path;
541
542  /* Fetch the absolute FS path associated with PATH. */
543  SVN_ERR(get_fs_path(&fs_path, session, path, pool));
544
545  /* If PEG_REVISION is invalid, it means HEAD.  If START_REV is
546     invalid, it means HEAD.  If END_REV is SVN_INVALID_REVNUM, we'll
547     use 0. */
548  if (! SVN_IS_VALID_REVNUM(peg_revision))
549    {
550      SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool));
551      peg_revision = youngest_rev;
552    }
553  if (! SVN_IS_VALID_REVNUM(start_rev))
554    {
555      if (SVN_IS_VALID_REVNUM(youngest_rev))
556        start_rev = youngest_rev;
557      else
558        SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool));
559    }
560  if (! SVN_IS_VALID_REVNUM(end_rev))
561    {
562      end_rev = 0;
563    }
564
565  /* The API demands a certain ordering of our revision inputs. Enforce it. */
566  SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev));
567
568  /* Sanity check: verify that the peg-object exists in repos. */
569  SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
570  if (kind == svn_node_none)
571    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
572                             _("Path '%s' doesn't exist in revision %ld"),
573                             fs_path, start_rev);
574
575  /* Populate most of our log receiver baton structure. */
576  lrb.kind = kind;
577  lrb.last_path = fs_path;
578  lrb.done = FALSE;
579  lrb.start_rev = start_rev;
580  lrb.range_end = start_rev;
581  lrb.receiver = receiver;
582  lrb.receiver_baton = receiver_baton;
583  lrb.pool = pool;
584
585  /* Let the RA layer drive our log information handler, which will do
586     the work of finding the actual locations for our resource.
587     Notice that we always run on the youngest rev of the 3 inputs. */
588  targets = apr_array_make(pool, 1, sizeof(const char *));
589  APR_ARRAY_PUSH(targets, const char *) = path;
590  SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0,
591                          TRUE, FALSE, FALSE,
592                          apr_array_make(pool, 0, sizeof(const char *)),
593                          gls_log_receiver, &lrb, pool));
594
595  /* If we didn't finish, we need to do so with a final segment send. */
596  if (! lrb.done)
597    SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev,
598                                        end_rev, lrb.range_end,
599                                        receiver, receiver_baton, pool));
600
601  return SVN_NO_ERROR;
602}
603
604
605
606/*** Fallback implementation of svn_ra_get_file_revs(). ***/
607
608/* The metadata associated with a particular revision. */
609struct rev
610{
611  svn_revnum_t revision; /* the revision number */
612  const char *path;      /* the absolute repository path */
613  apr_hash_t *props;     /* the revprops for this revision */
614  struct rev *next;      /* the next revision */
615};
616
617/* File revs log message baton. */
618struct fr_log_message_baton {
619  const char *path;        /* The path to be processed */
620  struct rev *eldest;      /* The eldest revision processed */
621  char action;             /* The action associated with the eldest */
622  svn_revnum_t copyrev;    /* The revision the eldest was copied from */
623  apr_pool_t *pool;
624};
625
626/* Callback for log messages: implements svn_log_entry_receiver_t and
627   accumulates revision metadata into a chronologically ordered list stored in
628   the baton. */
629static svn_error_t *
630fr_log_message_receiver(void *baton,
631                        svn_log_entry_t *log_entry,
632                        apr_pool_t *pool)
633{
634  struct fr_log_message_baton *lmb = baton;
635  struct rev *rev;
636
637  rev = apr_palloc(lmb->pool, sizeof(*rev));
638  rev->revision = log_entry->revision;
639  rev->path = lmb->path;
640  rev->next = lmb->eldest;
641  lmb->eldest = rev;
642
643  /* Duplicate log_entry revprops into rev->props */
644  rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool);
645
646  return prev_log_path(&lmb->path, &lmb->action,
647                       &lmb->copyrev, log_entry->changed_paths2,
648                       lmb->path, svn_node_file, log_entry->revision,
649                       lmb->pool);
650}
651
652svn_error_t *
653svn_ra__file_revs_from_log(svn_ra_session_t *ra_session,
654                           const char *path,
655                           svn_revnum_t start,
656                           svn_revnum_t end,
657                           svn_file_rev_handler_t handler,
658                           void *handler_baton,
659                           apr_pool_t *pool)
660{
661  svn_node_kind_t kind;
662  const char *repos_url, *session_url, *fs_path;
663  apr_array_header_t *condensed_targets;
664  struct fr_log_message_baton lmb;
665  struct rev *rev;
666  apr_hash_t *last_props;
667  svn_stream_t *last_stream;
668  apr_pool_t *currpool, *lastpool;
669
670  /* Fetch the absolute FS path associated with PATH. */
671  SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool));
672
673  /* Check to make sure we're dealing with a file. */
674  SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool));
675  if (kind == svn_node_dir)
676    return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
677                             _("'%s' is not a file"), fs_path);
678
679  condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
680  APR_ARRAY_PUSH(condensed_targets, const char *) = path;
681
682  lmb.path = fs_path;
683  lmb.eldest = NULL;
684  lmb.pool = pool;
685
686  /* Accumulate revision metadata by walking the revisions
687     backwards; this allows us to follow moves/copies
688     correctly. */
689  SVN_ERR(svn_ra_get_log2(ra_session,
690                          condensed_targets,
691                          end, start, 0, /* no limit */
692                          TRUE, FALSE, FALSE,
693                          NULL, fr_log_message_receiver, &lmb,
694                          pool));
695
696  /* Reparent the session while we go back through the history. */
697  SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
698  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool));
699  SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool));
700
701  currpool = svn_pool_create(pool);
702  lastpool = svn_pool_create(pool);
703
704  /* We want the first txdelta to be against the empty file. */
705  last_props = apr_hash_make(lastpool);
706  last_stream = svn_stream_empty(lastpool);
707
708  /* Walk the revision list in chronological order, downloading each fulltext,
709     diffing it with its predecessor, and calling the file_revs handler for
710     each one.  Use two iteration pools rather than one, because the diff
711     routines need to look at a sliding window of revisions.  Two pools gives
712     us a ring buffer of sorts. */
713  for (rev = lmb.eldest; rev; rev = rev->next)
714    {
715      const char *temp_path;
716      apr_pool_t *tmppool;
717      apr_hash_t *props;
718      apr_file_t *file;
719      svn_stream_t *stream;
720      apr_array_header_t *prop_diffs;
721      svn_txdelta_stream_t *delta_stream;
722      svn_txdelta_window_handler_t delta_handler = NULL;
723      void *delta_baton = NULL;
724
725      svn_pool_clear(currpool);
726
727      /* Get the contents of the file from the repository, and put them in
728         a temporary local file. */
729      SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL,
730                                     svn_io_file_del_on_pool_cleanup,
731                                     currpool, currpool));
732      SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision,
733                              stream, NULL, &props, currpool));
734      SVN_ERR(svn_stream_close(stream));
735
736      /* Open up a stream to the local file. */
737      SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT,
738                               currpool));
739      stream = svn_stream_from_aprfile2(file, FALSE, currpool);
740
741      /* Calculate the property diff */
742      SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool));
743
744      /* Call the file_rev handler */
745      SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props,
746                      FALSE, /* merged revision */
747                      &delta_handler, &delta_baton, prop_diffs, lastpool));
748
749      /* Compute and send delta if client asked for it. */
750      if (delta_handler)
751        {
752          /* Get the content delta. Don't calculate checksums as we don't
753           * use them. */
754          svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool);
755
756          /* And send. */
757          SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
758                                            delta_baton, lastpool));
759        }
760
761      /* Switch the pools and data for the next iteration */
762      tmppool = currpool;
763      currpool = lastpool;
764      lastpool = tmppool;
765
766      SVN_ERR(svn_stream_close(last_stream));
767      last_stream = stream;
768      last_props = props;
769    }
770
771  SVN_ERR(svn_stream_close(last_stream));
772  svn_pool_destroy(currpool);
773  svn_pool_destroy(lastpool);
774
775  /* Reparent the session back to the original URL. */
776  return svn_ra_reparent(ra_session, session_url, pool);
777}
778
779
780/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/
781
782/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */
783typedef struct log_path_del_rev_t
784{
785  /* Absolute repository path. */
786  const char *path;
787
788  /* Revision PATH was first deleted or replaced. */
789  svn_revnum_t revision_deleted;
790} log_path_del_rev_t;
791
792/* A svn_log_entry_receiver_t callback for finding the revision
793   ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced.
794   Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED.
795 */
796static svn_error_t *
797log_path_del_receiver(void *baton,
798                      svn_log_entry_t *log_entry,
799                      apr_pool_t *pool)
800{
801  log_path_del_rev_t *b = baton;
802  apr_hash_index_t *hi;
803
804  /* No paths were changed in this revision.  Nothing to do. */
805  if (! log_entry->changed_paths2)
806    return SVN_NO_ERROR;
807
808  for (hi = apr_hash_first(pool, log_entry->changed_paths2);
809       hi != NULL;
810       hi = apr_hash_next(hi))
811    {
812      void *val;
813      char *path;
814      svn_log_changed_path_t *log_item;
815
816      apr_hash_this(hi, (void *) &path, NULL, &val);
817      log_item = val;
818      if (svn_path_compare_paths(b->path, path) == 0
819          && (log_item->action == 'D' || log_item->action == 'R'))
820        {
821          /* Found the first deletion or replacement, we are done. */
822          b->revision_deleted = log_entry->revision;
823          break;
824        }
825    }
826  return SVN_NO_ERROR;
827}
828
829svn_error_t *
830svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session,
831                                 const char *rel_deleted_path,
832                                 svn_revnum_t peg_revision,
833                                 svn_revnum_t end_revision,
834                                 svn_revnum_t *revision_deleted,
835                                 apr_pool_t *pool)
836{
837  const char *fs_path;
838  log_path_del_rev_t log_path_deleted_baton;
839
840  /* Fetch the absolute FS path associated with PATH. */
841  SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool));
842
843  if (!SVN_IS_VALID_REVNUM(peg_revision))
844    return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
845                             _("Invalid peg revision %ld"), peg_revision);
846  if (!SVN_IS_VALID_REVNUM(end_revision))
847    return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
848                             _("Invalid end revision %ld"), end_revision);
849  if (end_revision <= peg_revision)
850    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
851                            _("Peg revision must precede end revision"));
852
853  log_path_deleted_baton.path = fs_path;
854  log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM;
855
856  /* Examine the logs of SESSION's URL to find when DELETED_PATH was first
857     deleted or replaced. */
858  SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0,
859                          TRUE, TRUE, FALSE,
860                          apr_array_make(pool, 0, sizeof(char *)),
861                          log_path_del_receiver, &log_path_deleted_baton,
862                          pool));
863  *revision_deleted = log_path_deleted_baton.revision_deleted;
864  return SVN_NO_ERROR;
865}
866
867
868svn_error_t *
869svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
870                                 const char *path,
871                                 svn_revnum_t revision,
872                                 apr_array_header_t **inherited_props,
873                                 apr_pool_t *result_pool,
874                                 apr_pool_t *scratch_pool)
875{
876  const char *repos_root_url;
877  const char *session_url;
878  const char *parent_url;
879  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
880
881  *inherited_props =
882    apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
883
884  /* Walk to the root of the repository getting inherited
885     props for PATH. */
886  SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool));
887  SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
888  parent_url = session_url;
889
890  while (strcmp(repos_root_url, parent_url))
891    {
892      apr_hash_index_t *hi;
893      apr_hash_t *parent_props;
894      apr_hash_t *final_hash = apr_hash_make(result_pool);
895      svn_error_t *err;
896
897      svn_pool_clear(iterpool);
898      parent_url = svn_uri_dirname(parent_url, scratch_pool);
899      SVN_ERR(svn_ra_reparent(session, parent_url, iterpool));
900      err = session->vtable->get_dir(session, NULL, NULL,
901                                     &parent_props, "",
902                                     revision, SVN_DIRENT_ALL,
903                                     iterpool);
904
905      /* If the user doesn't have read access to a parent path then
906         skip, but allow them to inherit from further up. */
907      if (err)
908        {
909          if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)
910              || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))
911            {
912              svn_error_clear(err);
913              continue;
914            }
915          else
916            {
917              return svn_error_trace(err);
918            }
919        }
920
921      for (hi = apr_hash_first(scratch_pool, parent_props);
922           hi;
923           hi = apr_hash_next(hi))
924        {
925          const char *name = apr_hash_this_key(hi);
926          apr_ssize_t klen = apr_hash_this_key_len(hi);
927          svn_string_t *value = apr_hash_this_val(hi);
928
929          if (svn_property_kind2(name) == svn_prop_regular_kind)
930            {
931              name = apr_pstrdup(result_pool, name);
932              value = svn_string_dup(value, result_pool);
933              apr_hash_set(final_hash, name, klen, value);
934            }
935        }
936
937      if (apr_hash_count(final_hash))
938        {
939          svn_prop_inherited_item_t *new_iprop =
940            apr_palloc(result_pool, sizeof(*new_iprop));
941          new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url,
942                                                         parent_url,
943                                                         result_pool);
944          new_iprop->prop_hash = final_hash;
945          SVN_ERR(svn_sort__array_insert2(*inherited_props, &new_iprop, 0));
946        }
947    }
948
949  /* Reparent session back to original URL. */
950  SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool));
951
952  svn_pool_destroy(iterpool);
953  return SVN_NO_ERROR;
954}
955