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