]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra/compat.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra / compat.c
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 \f
43
44 /* This is just like svn_sort_compare_revisions, save that it sorts
45    the revisions in *ascending* order. */
46 static int
47 compare_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 */
70 static svn_error_t *
71 prev_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. */
196 static svn_error_t *
197 get_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 \f
214 /*** Fallback implementation of svn_ra_get_locations(). ***/
215
216
217 /* ### This is to support 1.0 servers. */
218 struct 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. */
242 static svn_error_t *
243 log_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
305 svn_error_t *
306 svn_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 \f
415 /*** Fallback implementation of svn_ra_get_location_segments(). ***/
416
417 struct 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. */
443 static svn_error_t *
444 maybe_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
465 static svn_error_t *
466 gls_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
526 svn_error_t *
527 svn_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 \f
606 /*** Fallback implementation of svn_ra_get_file_revs(). ***/
607
608 /* The metadata associated with a particular revision. */
609 struct 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. */
618 struct 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. */
629 static svn_error_t *
630 fr_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
652 svn_error_t *
653 svn_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 \f
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(). */
783 typedef 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  */
796 static svn_error_t *
797 log_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
829 svn_error_t *
830 svn_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
868 svn_error_t *
869 svn_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_sort__array_insert(*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 }