1251881Speter/*
2251881Speter * compat.c:  compatibility compliance logic
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter#include <apr_pools.h>
25251881Speter
26251881Speter#include "svn_hash.h"
27251881Speter#include "svn_error.h"
28251881Speter#include "svn_pools.h"
29251881Speter#include "svn_sorts.h"
30251881Speter#include "svn_dirent_uri.h"
31251881Speter#include "svn_path.h"
32251881Speter#include "svn_ra.h"
33251881Speter#include "svn_io.h"
34251881Speter#include "svn_compat.h"
35251881Speter#include "svn_props.h"
36251881Speter
37251881Speter#include "private/svn_fspath.h"
38299742Sdim#include "private/svn_sorts_private.h"
39251881Speter#include "ra_loader.h"
40251881Speter#include "svn_private_config.h"
41251881Speter
42251881Speter
43251881Speter
44251881Speter/* This is just like svn_sort_compare_revisions, save that it sorts
45251881Speter   the revisions in *ascending* order. */
46251881Speterstatic int
47251881Spetercompare_revisions(const void *a, const void *b)
48251881Speter{
49251881Speter  svn_revnum_t a_rev = *(const svn_revnum_t *)a;
50251881Speter  svn_revnum_t b_rev = *(const svn_revnum_t *)b;
51251881Speter  if (a_rev == b_rev)
52251881Speter    return 0;
53251881Speter  return a_rev < b_rev ? -1 : 1;
54251881Speter}
55251881Speter
56251881Speter/* Given the CHANGED_PATHS and REVISION from an instance of a
57251881Speter   svn_log_message_receiver_t function, determine at which location
58251881Speter   PATH may be expected in the next log message, and set *PREV_PATH_P
59251881Speter   to that value.  KIND is the node kind of PATH.  Set *ACTION_P to a
60251881Speter   character describing the change that caused this revision (as
61251881Speter   listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
62251881Speter   revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
63251881Speter   copied.  ACTION_P and COPYFROM_REV_P may be NULL, in which case
64251881Speter   they are not used.  Perform all allocations in POOL.
65251881Speter
66251881Speter   This is useful for tracking the various changes in location a
67251881Speter   particular resource has undergone when performing an RA->get_logs()
68251881Speter   operation on that resource.
69251881Speter*/
70251881Speterstatic svn_error_t *
71251881Speterprev_log_path(const char **prev_path_p,
72251881Speter              char *action_p,
73251881Speter              svn_revnum_t *copyfrom_rev_p,
74251881Speter              apr_hash_t *changed_paths,
75251881Speter              const char *path,
76251881Speter              svn_node_kind_t kind,
77251881Speter              svn_revnum_t revision,
78251881Speter              apr_pool_t *pool)
79251881Speter{
80251881Speter  svn_log_changed_path_t *change;
81251881Speter  const char *prev_path = NULL;
82251881Speter
83251881Speter  /* It's impossible to find the predecessor path of a NULL path. */
84251881Speter  SVN_ERR_ASSERT(path);
85251881Speter
86251881Speter  /* Initialize our return values for the action and copyfrom_rev in
87251881Speter     case we have an unhandled case later on. */
88251881Speter  if (action_p)
89251881Speter    *action_p = 'M';
90251881Speter  if (copyfrom_rev_p)
91251881Speter    *copyfrom_rev_p = SVN_INVALID_REVNUM;
92251881Speter
93251881Speter  if (changed_paths)
94251881Speter    {
95251881Speter      /* See if PATH was explicitly changed in this revision. */
96251881Speter      change = svn_hash_gets(changed_paths, path);
97251881Speter      if (change)
98251881Speter        {
99251881Speter          /* If PATH was not newly added in this revision, then it may or may
100251881Speter             not have also been part of a moved subtree.  In this case, set a
101251881Speter             default previous path, but still look through the parents of this
102251881Speter             path for a possible copy event. */
103251881Speter          if (change->action != 'A' && change->action != 'R')
104251881Speter            {
105251881Speter              prev_path = path;
106251881Speter            }
107251881Speter          else
108251881Speter            {
109251881Speter              /* PATH is new in this revision.  This means it cannot have been
110251881Speter                 part of a copied subtree. */
111251881Speter              if (change->copyfrom_path)
112251881Speter                prev_path = apr_pstrdup(pool, change->copyfrom_path);
113251881Speter              else
114251881Speter                prev_path = NULL;
115251881Speter
116251881Speter              *prev_path_p = prev_path;
117251881Speter              if (action_p)
118251881Speter                *action_p = change->action;
119251881Speter              if (copyfrom_rev_p)
120251881Speter                *copyfrom_rev_p = change->copyfrom_rev;
121251881Speter              return SVN_NO_ERROR;
122251881Speter            }
123251881Speter        }
124251881Speter
125251881Speter      if (apr_hash_count(changed_paths))
126251881Speter        {
127251881Speter          /* The path was not explicitly changed in this revision.  The
128251881Speter             fact that we're hearing about this revision implies, then,
129251881Speter             that the path was a child of some copied directory.  We need
130251881Speter             to find that directory, and effectively "re-base" our path on
131251881Speter             that directory's copyfrom_path. */
132251881Speter          int i;
133251881Speter          apr_array_header_t *paths;
134251881Speter
135251881Speter          /* Build a sorted list of the changed paths. */
136251881Speter          paths = svn_sort__hash(changed_paths,
137251881Speter                                 svn_sort_compare_items_as_paths, pool);
138251881Speter
139251881Speter          /* Now, walk the list of paths backwards, looking a parent of
140251881Speter             our path that has copyfrom information. */
141251881Speter          for (i = paths->nelts; i > 0; i--)
142251881Speter            {
143251881Speter              svn_sort__item_t item = APR_ARRAY_IDX(paths,
144251881Speter                                                    i - 1, svn_sort__item_t);
145251881Speter              const char *ch_path = item.key;
146251881Speter              size_t len = strlen(ch_path);
147251881Speter
148251881Speter              /* See if our path is the child of this change path.  If
149251881Speter                 not, keep looking.  */
150251881Speter              if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/')))
151251881Speter                continue;
152251881Speter
153251881Speter              /* Okay, our path *is* a child of this change path.  If
154251881Speter                 this change was copied, we just need to apply the
155251881Speter                 portion of our path that is relative to this change's
156251881Speter                 path, to the change's copyfrom path.  Otherwise, this
157251881Speter                 change isn't really interesting to us, and our search
158251881Speter                 continues. */
159251881Speter              change = item.value;
160251881Speter              if (change->copyfrom_path)
161251881Speter                {
162251881Speter                  if (action_p)
163251881Speter                    *action_p = change->action;
164251881Speter                  if (copyfrom_rev_p)
165251881Speter                    *copyfrom_rev_p = change->copyfrom_rev;
166251881Speter                  prev_path = svn_fspath__join(change->copyfrom_path,
167251881Speter                                               path + len + 1, pool);
168251881Speter                  break;
169251881Speter                }
170251881Speter            }
171251881Speter        }
172251881Speter    }
173251881Speter
174251881Speter  /* If we didn't find what we expected to find, return an error.
175251881Speter     (Because directories bubble-up, we get a bunch of logs we might
176251881Speter     not want.  Be forgiving in that case.)  */
177251881Speter  if (! prev_path)
178251881Speter    {
179251881Speter      if (kind == svn_node_dir)
180251881Speter        prev_path = apr_pstrdup(pool, path);
181251881Speter      else
182251881Speter        return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
183251881Speter                                 _("Missing changed-path information for "
184251881Speter                                   "'%s' in revision %ld"),
185251881Speter                                 svn_dirent_local_style(path, pool), revision);
186251881Speter    }
187251881Speter
188251881Speter  *prev_path_p = prev_path;
189251881Speter  return SVN_NO_ERROR;
190251881Speter}
191251881Speter
192251881Speter
193251881Speter/* Set *FS_PATH_P to the absolute filesystem path associated with the
194251881Speter   URL built from SESSION's URL and REL_PATH (which is relative to
195251881Speter   session's URL.  Use POOL for allocations. */
196251881Speterstatic svn_error_t *
197251881Speterget_fs_path(const char **fs_path_p,
198251881Speter            svn_ra_session_t *session,
199251881Speter            const char *rel_path,
200251881Speter            apr_pool_t *pool)
201251881Speter{
202251881Speter  const char *url, *fs_path;
203251881Speter
204251881Speter  SVN_ERR(svn_ra_get_session_url(session, &url, pool));
205251881Speter  SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool));
206251881Speter  *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path,
207251881Speter                                                         rel_path, pool),
208251881Speter                                        pool);
209251881Speter  return SVN_NO_ERROR;
210251881Speter}
211251881Speter
212251881Speter
213251881Speter
214251881Speter/*** Fallback implementation of svn_ra_get_locations(). ***/
215251881Speter
216251881Speter
217251881Speter/* ### This is to support 1.0 servers. */
218251881Speterstruct log_receiver_baton
219251881Speter{
220251881Speter  /* The kind of the path we're tracing. */
221251881Speter  svn_node_kind_t kind;
222251881Speter
223251881Speter  /* The path at which we are trying to find our versioned resource in
224251881Speter     the log output. */
225251881Speter  const char *last_path;
226251881Speter
227251881Speter  /* Input revisions and output hash; the whole point of this little game. */
228251881Speter  svn_revnum_t peg_revision;
229251881Speter  apr_array_header_t *location_revisions;
230251881Speter  const char *peg_path;
231251881Speter  apr_hash_t *locations;
232251881Speter
233251881Speter  /* A pool from which to allocate stuff stored in this baton. */
234251881Speter  apr_pool_t *pool;
235251881Speter};
236251881Speter
237251881Speter
238251881Speter/* Implements svn_log_entry_receiver_t; helper for slow_get_locations.
239251881Speter   As input, takes log_receiver_baton (defined above) and attempts to
240251881Speter   "fill in" locations in the baton over the course of many
241251881Speter   iterations. */
242251881Speterstatic svn_error_t *
243251881Speterlog_receiver(void *baton,
244251881Speter             svn_log_entry_t *log_entry,
245251881Speter             apr_pool_t *pool)
246251881Speter{
247251881Speter  struct log_receiver_baton *lrb = baton;
248251881Speter  apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations);
249251881Speter  const char *current_path = lrb->last_path;
250251881Speter  const char *prev_path;
251251881Speter
252251881Speter  /* No paths were changed in this revision.  Nothing to do. */
253251881Speter  if (! log_entry->changed_paths2)
254251881Speter    return SVN_NO_ERROR;
255251881Speter
256251881Speter  /* If we've run off the end of the path's history, there's nothing
257251881Speter     to do.  (This should never happen with a properly functioning
258251881Speter     server, since we'd get no more log messages after the one where
259251881Speter     path was created.  But a malfunctioning server shouldn't cause us
260251881Speter     to trigger an assertion failure.) */
261251881Speter  if (! current_path)
262251881Speter    return SVN_NO_ERROR;
263251881Speter
264251881Speter  /* If we haven't found our peg path yet, and we are now looking at a
265251881Speter     revision equal to or older than the peg revision, then our
266251881Speter     "current" path is our peg path. */
267251881Speter  if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision))
268251881Speter    lrb->peg_path = apr_pstrdup(lrb->pool, current_path);
269251881Speter
270251881Speter  /* Determine the paths for any of the revisions for which we haven't
271251881Speter     gotten paths already. */
272251881Speter  while (lrb->location_revisions->nelts)
273251881Speter    {
274251881Speter      svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions,
275251881Speter                                        lrb->location_revisions->nelts - 1,
276251881Speter                                        svn_revnum_t);
277251881Speter      if (log_entry->revision <= next)
278251881Speter        {
279251881Speter          apr_hash_set(lrb->locations,
280251881Speter                       apr_pmemdup(hash_pool, &next, sizeof(next)),
281251881Speter                       sizeof(next),
282251881Speter                       apr_pstrdup(hash_pool, current_path));
283251881Speter          apr_array_pop(lrb->location_revisions);
284251881Speter        }
285251881Speter      else
286251881Speter        break;
287251881Speter    }
288251881Speter
289251881Speter  /* Figure out at which repository path our object of interest lived
290251881Speter     in the previous revision. */
291251881Speter  SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2,
292251881Speter                        current_path, lrb->kind, log_entry->revision, pool));
293251881Speter
294251881Speter  /* Squirrel away our "next place to look" path (suffer the strcmp
295251881Speter     hit to save on allocations). */
296251881Speter  if (! prev_path)
297251881Speter    lrb->last_path = NULL;
298251881Speter  else if (strcmp(prev_path, current_path) != 0)
299251881Speter    lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
300251881Speter
301251881Speter  return SVN_NO_ERROR;
302251881Speter}
303251881Speter
304251881Speter
305251881Spetersvn_error_t *
306251881Spetersvn_ra__locations_from_log(svn_ra_session_t *session,
307251881Speter                           apr_hash_t **locations_p,
308251881Speter                           const char *path,
309251881Speter                           svn_revnum_t peg_revision,
310251881Speter                           const apr_array_header_t *location_revisions,
311251881Speter                           apr_pool_t *pool)
312251881Speter{
313251881Speter  apr_hash_t *locations = apr_hash_make(pool);
314251881Speter  struct log_receiver_baton lrb = { 0 };
315251881Speter  apr_array_header_t *targets;
316251881Speter  svn_revnum_t youngest_requested, oldest_requested, youngest, oldest;
317251881Speter  svn_node_kind_t kind;
318251881Speter  const char *fs_path;
319299742Sdim  apr_array_header_t *sorted_location_revisions;
320251881Speter
321251881Speter  /* Fetch the absolute FS path associated with PATH. */
322251881Speter  SVN_ERR(get_fs_path(&fs_path, session, path, pool));
323251881Speter
324251881Speter  /* Sanity check: verify that the peg-object exists in repos. */
325251881Speter  SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
326251881Speter  if (kind == svn_node_none)
327251881Speter    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
328251881Speter                             _("Path '%s' doesn't exist in revision %ld"),
329251881Speter                             fs_path, peg_revision);
330251881Speter
331251881Speter  /* Easy out: no location revisions. */
332251881Speter  if (! location_revisions->nelts)
333251881Speter    {
334251881Speter      *locations_p = locations;
335251881Speter      return SVN_NO_ERROR;
336251881Speter    }
337251881Speter
338251881Speter  /* Figure out the youngest and oldest revs (amongst the set of
339251881Speter     requested revisions + the peg revision) so we can avoid
340251881Speter     unnecessary log parsing. */
341299742Sdim  sorted_location_revisions = apr_array_copy(pool, location_revisions);
342299742Sdim  svn_sort__array(sorted_location_revisions, compare_revisions);
343299742Sdim  oldest_requested = APR_ARRAY_IDX(sorted_location_revisions, 0, svn_revnum_t);
344299742Sdim  youngest_requested = APR_ARRAY_IDX(sorted_location_revisions,
345299742Sdim                                     sorted_location_revisions->nelts - 1,
346251881Speter                                     svn_revnum_t);
347251881Speter  youngest = peg_revision;
348251881Speter  youngest = (oldest_requested > youngest) ? oldest_requested : youngest;
349251881Speter  youngest = (youngest_requested > youngest) ? youngest_requested : youngest;
350251881Speter  oldest = peg_revision;
351251881Speter  oldest = (oldest_requested < oldest) ? oldest_requested : oldest;
352251881Speter  oldest = (youngest_requested < oldest) ? youngest_requested : oldest;
353251881Speter
354251881Speter  /* Populate most of our log receiver baton structure. */
355251881Speter  lrb.kind = kind;
356251881Speter  lrb.last_path = fs_path;
357299742Sdim  lrb.location_revisions = apr_array_copy(pool, sorted_location_revisions);
358251881Speter  lrb.peg_revision = peg_revision;
359251881Speter  lrb.peg_path = NULL;
360251881Speter  lrb.locations = locations;
361251881Speter  lrb.pool = pool;
362251881Speter
363251881Speter  /* Let the RA layer drive our log information handler, which will do
364251881Speter     the work of finding the actual locations for our resource.
365251881Speter     Notice that we always run on the youngest rev of the 3 inputs. */
366251881Speter  targets = apr_array_make(pool, 1, sizeof(const char *));
367251881Speter  APR_ARRAY_PUSH(targets, const char *) = path;
368251881Speter  SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0,
369251881Speter                          TRUE, FALSE, FALSE,
370251881Speter                          apr_array_make(pool, 0, sizeof(const char *)),
371251881Speter                          log_receiver, &lrb, pool));
372251881Speter
373251881Speter  /* If the received log information did not cover any of the
374251881Speter     requested revisions, use the last known path.  (This normally
375251881Speter     just means that FS_PATH was not modified between the requested
376251881Speter     revision and OLDEST.  If the file was created at some point after
377251881Speter     OLDEST, then lrb.last_path should be NULL.) */
378251881Speter  if (! lrb.peg_path)
379251881Speter    lrb.peg_path = lrb.last_path;
380251881Speter  if (lrb.last_path)
381251881Speter    {
382251881Speter      int i;
383299742Sdim      for (i = 0; i < sorted_location_revisions->nelts; i++)
384251881Speter        {
385299742Sdim          svn_revnum_t rev = APR_ARRAY_IDX(sorted_location_revisions, i,
386251881Speter                                           svn_revnum_t);
387251881Speter          if (! apr_hash_get(locations, &rev, sizeof(rev)))
388251881Speter            apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)),
389251881Speter                         sizeof(rev), apr_pstrdup(pool, lrb.last_path));
390251881Speter        }
391251881Speter    }
392251881Speter
393251881Speter  /* Check that we got the peg path. */
394251881Speter  if (! lrb.peg_path)
395251881Speter    return svn_error_createf
396251881Speter      (APR_EGENERAL, NULL,
397251881Speter       _("Unable to find repository location for '%s' in revision %ld"),
398251881Speter       fs_path, peg_revision);
399251881Speter
400251881Speter  /* Sanity check: make sure that our calculated peg path is the same
401251881Speter     as what we expected it to be. */
402251881Speter  if (strcmp(fs_path, lrb.peg_path) != 0)
403251881Speter    return svn_error_createf
404251881Speter      (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
405251881Speter       _("'%s' in revision %ld is an unrelated object"),
406251881Speter       fs_path, youngest);
407251881Speter
408251881Speter  *locations_p = locations;
409251881Speter  return SVN_NO_ERROR;
410251881Speter}
411251881Speter
412251881Speter
413251881Speter
414251881Speter
415251881Speter/*** Fallback implementation of svn_ra_get_location_segments(). ***/
416251881Speter
417251881Speterstruct gls_log_receiver_baton {
418251881Speter  /* The kind of the path we're tracing. */
419251881Speter  svn_node_kind_t kind;
420251881Speter
421251881Speter  /* Are we finished (and just listening to log entries because our
422251881Speter     caller won't shut up?). */
423251881Speter  svn_boolean_t done;
424251881Speter
425251881Speter  /* The path at which we are trying to find our versioned resource in
426251881Speter     the log output. */
427251881Speter  const char *last_path;
428251881Speter
429251881Speter  /* Input data. */
430251881Speter  svn_revnum_t start_rev;
431251881Speter
432251881Speter  /* Output intermediate state and callback/baton. */
433251881Speter  svn_revnum_t range_end;
434251881Speter  svn_location_segment_receiver_t receiver;
435251881Speter  void *receiver_baton;
436251881Speter
437251881Speter  /* A pool from which to allocate stuff stored in this baton. */
438251881Speter  apr_pool_t *pool;
439251881Speter};
440251881Speter
441251881Speter/* Build a node location segment object from PATH, RANGE_START, and
442251881Speter   RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */
443251881Speterstatic svn_error_t *
444251881Spetermaybe_crop_and_send_segment(const char *path,
445251881Speter                            svn_revnum_t start_rev,
446251881Speter                            svn_revnum_t range_start,
447251881Speter                            svn_revnum_t range_end,
448251881Speter                            svn_location_segment_receiver_t receiver,
449251881Speter                            void *receiver_baton,
450251881Speter                            apr_pool_t *pool)
451251881Speter{
452251881Speter  svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
453251881Speter  segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL;
454251881Speter  segment->range_start = range_start;
455251881Speter  segment->range_end = range_end;
456251881Speter  if (segment->range_start <= start_rev)
457251881Speter    {
458251881Speter      if (segment->range_end > start_rev)
459251881Speter        segment->range_end = start_rev;
460251881Speter      return receiver(segment, receiver_baton, pool);
461251881Speter    }
462251881Speter  return SVN_NO_ERROR;
463251881Speter}
464251881Speter
465251881Speterstatic svn_error_t *
466251881Spetergls_log_receiver(void *baton,
467251881Speter                 svn_log_entry_t *log_entry,
468251881Speter                 apr_pool_t *pool)
469251881Speter{
470251881Speter  struct gls_log_receiver_baton *lrb = baton;
471251881Speter  const char *current_path = lrb->last_path;
472251881Speter  const char *prev_path;
473251881Speter  svn_revnum_t copyfrom_rev;
474251881Speter
475251881Speter  /* If we're done, ignore this invocation. */
476251881Speter  if (lrb->done)
477251881Speter    return SVN_NO_ERROR;
478251881Speter
479251881Speter  /* Figure out at which repository path our object of interest lived
480251881Speter     in the previous revision, and if its current location is the
481251881Speter     result of copy since then. */
482251881Speter  SVN_ERR(prev_log_path(&prev_path, NULL, &copyfrom_rev,
483251881Speter                        log_entry->changed_paths2, current_path,
484251881Speter                        lrb->kind, log_entry->revision, pool));
485251881Speter
486251881Speter  /* If we've run off the end of the path's history, we need to report
487251881Speter     our final segment (and then, we're done). */
488251881Speter  if (! prev_path)
489251881Speter    {
490251881Speter      lrb->done = TRUE;
491251881Speter      return maybe_crop_and_send_segment(current_path, lrb->start_rev,
492251881Speter                                         log_entry->revision, lrb->range_end,
493251881Speter                                         lrb->receiver, lrb->receiver_baton,
494251881Speter                                         pool);
495251881Speter    }
496251881Speter
497251881Speter  /* If there was a copy operation of interest... */
498251881Speter  if (SVN_IS_VALID_REVNUM(copyfrom_rev))
499251881Speter    {
500251881Speter      /* ...then report the segment between this revision and the
501251881Speter         last-reported revision. */
502251881Speter      SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev,
503251881Speter                                          log_entry->revision, lrb->range_end,
504251881Speter                                          lrb->receiver, lrb->receiver_baton,
505251881Speter                                          pool));
506251881Speter      lrb->range_end = log_entry->revision - 1;
507251881Speter
508251881Speter      /* And if there was a revision gap, we need to report that, too. */
509251881Speter      if (log_entry->revision - copyfrom_rev > 1)
510251881Speter        {
511251881Speter          SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev,
512251881Speter                                              copyfrom_rev + 1, lrb->range_end,
513251881Speter                                              lrb->receiver,
514251881Speter                                              lrb->receiver_baton, pool));
515251881Speter          lrb->range_end = copyfrom_rev;
516251881Speter        }
517251881Speter
518251881Speter      /* Update our state variables. */
519251881Speter      lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
520251881Speter    }
521251881Speter
522251881Speter  return SVN_NO_ERROR;
523251881Speter}
524251881Speter
525251881Speter
526251881Spetersvn_error_t *
527251881Spetersvn_ra__location_segments_from_log(svn_ra_session_t *session,
528251881Speter                                   const char *path,
529251881Speter                                   svn_revnum_t peg_revision,
530251881Speter                                   svn_revnum_t start_rev,
531251881Speter                                   svn_revnum_t end_rev,
532251881Speter                                   svn_location_segment_receiver_t receiver,
533251881Speter                                   void *receiver_baton,
534251881Speter                                   apr_pool_t *pool)
535251881Speter{
536251881Speter  struct gls_log_receiver_baton lrb = { 0 };
537251881Speter  apr_array_header_t *targets;
538251881Speter  svn_node_kind_t kind;
539251881Speter  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
540251881Speter  const char *fs_path;
541251881Speter
542251881Speter  /* Fetch the absolute FS path associated with PATH. */
543251881Speter  SVN_ERR(get_fs_path(&fs_path, session, path, pool));
544251881Speter
545251881Speter  /* If PEG_REVISION is invalid, it means HEAD.  If START_REV is
546251881Speter     invalid, it means HEAD.  If END_REV is SVN_INVALID_REVNUM, we'll
547251881Speter     use 0. */
548251881Speter  if (! SVN_IS_VALID_REVNUM(peg_revision))
549251881Speter    {
550251881Speter      SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool));
551251881Speter      peg_revision = youngest_rev;
552251881Speter    }
553251881Speter  if (! SVN_IS_VALID_REVNUM(start_rev))
554251881Speter    {
555251881Speter      if (SVN_IS_VALID_REVNUM(youngest_rev))
556251881Speter        start_rev = youngest_rev;
557251881Speter      else
558251881Speter        SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool));
559251881Speter    }
560251881Speter  if (! SVN_IS_VALID_REVNUM(end_rev))
561251881Speter    {
562251881Speter      end_rev = 0;
563251881Speter    }
564251881Speter
565251881Speter  /* The API demands a certain ordering of our revision inputs. Enforce it. */
566251881Speter  SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev));
567251881Speter
568251881Speter  /* Sanity check: verify that the peg-object exists in repos. */
569251881Speter  SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
570251881Speter  if (kind == svn_node_none)
571251881Speter    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
572251881Speter                             _("Path '%s' doesn't exist in revision %ld"),
573251881Speter                             fs_path, start_rev);
574251881Speter
575251881Speter  /* Populate most of our log receiver baton structure. */
576251881Speter  lrb.kind = kind;
577251881Speter  lrb.last_path = fs_path;
578251881Speter  lrb.done = FALSE;
579251881Speter  lrb.start_rev = start_rev;
580251881Speter  lrb.range_end = start_rev;
581251881Speter  lrb.receiver = receiver;
582251881Speter  lrb.receiver_baton = receiver_baton;
583251881Speter  lrb.pool = pool;
584251881Speter
585251881Speter  /* Let the RA layer drive our log information handler, which will do
586251881Speter     the work of finding the actual locations for our resource.
587251881Speter     Notice that we always run on the youngest rev of the 3 inputs. */
588251881Speter  targets = apr_array_make(pool, 1, sizeof(const char *));
589251881Speter  APR_ARRAY_PUSH(targets, const char *) = path;
590251881Speter  SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0,
591251881Speter                          TRUE, FALSE, FALSE,
592251881Speter                          apr_array_make(pool, 0, sizeof(const char *)),
593251881Speter                          gls_log_receiver, &lrb, pool));
594251881Speter
595251881Speter  /* If we didn't finish, we need to do so with a final segment send. */
596251881Speter  if (! lrb.done)
597251881Speter    SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev,
598251881Speter                                        end_rev, lrb.range_end,
599251881Speter                                        receiver, receiver_baton, pool));
600251881Speter
601251881Speter  return SVN_NO_ERROR;
602251881Speter}
603251881Speter
604251881Speter
605251881Speter
606251881Speter/*** Fallback implementation of svn_ra_get_file_revs(). ***/
607251881Speter
608251881Speter/* The metadata associated with a particular revision. */
609251881Speterstruct rev
610251881Speter{
611251881Speter  svn_revnum_t revision; /* the revision number */
612251881Speter  const char *path;      /* the absolute repository path */
613251881Speter  apr_hash_t *props;     /* the revprops for this revision */
614251881Speter  struct rev *next;      /* the next revision */
615251881Speter};
616251881Speter
617251881Speter/* File revs log message baton. */
618251881Speterstruct fr_log_message_baton {
619251881Speter  const char *path;        /* The path to be processed */
620251881Speter  struct rev *eldest;      /* The eldest revision processed */
621251881Speter  char action;             /* The action associated with the eldest */
622251881Speter  svn_revnum_t copyrev;    /* The revision the eldest was copied from */
623251881Speter  apr_pool_t *pool;
624251881Speter};
625251881Speter
626251881Speter/* Callback for log messages: implements svn_log_entry_receiver_t and
627251881Speter   accumulates revision metadata into a chronologically ordered list stored in
628251881Speter   the baton. */
629251881Speterstatic svn_error_t *
630251881Speterfr_log_message_receiver(void *baton,
631251881Speter                        svn_log_entry_t *log_entry,
632251881Speter                        apr_pool_t *pool)
633251881Speter{
634251881Speter  struct fr_log_message_baton *lmb = baton;
635251881Speter  struct rev *rev;
636251881Speter
637251881Speter  rev = apr_palloc(lmb->pool, sizeof(*rev));
638251881Speter  rev->revision = log_entry->revision;
639251881Speter  rev->path = lmb->path;
640251881Speter  rev->next = lmb->eldest;
641251881Speter  lmb->eldest = rev;
642251881Speter
643251881Speter  /* Duplicate log_entry revprops into rev->props */
644251881Speter  rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool);
645251881Speter
646251881Speter  return prev_log_path(&lmb->path, &lmb->action,
647251881Speter                       &lmb->copyrev, log_entry->changed_paths2,
648251881Speter                       lmb->path, svn_node_file, log_entry->revision,
649251881Speter                       lmb->pool);
650251881Speter}
651251881Speter
652251881Spetersvn_error_t *
653251881Spetersvn_ra__file_revs_from_log(svn_ra_session_t *ra_session,
654251881Speter                           const char *path,
655251881Speter                           svn_revnum_t start,
656251881Speter                           svn_revnum_t end,
657251881Speter                           svn_file_rev_handler_t handler,
658251881Speter                           void *handler_baton,
659251881Speter                           apr_pool_t *pool)
660251881Speter{
661251881Speter  svn_node_kind_t kind;
662251881Speter  const char *repos_url, *session_url, *fs_path;
663251881Speter  apr_array_header_t *condensed_targets;
664251881Speter  struct fr_log_message_baton lmb;
665251881Speter  struct rev *rev;
666251881Speter  apr_hash_t *last_props;
667251881Speter  svn_stream_t *last_stream;
668251881Speter  apr_pool_t *currpool, *lastpool;
669251881Speter
670251881Speter  /* Fetch the absolute FS path associated with PATH. */
671251881Speter  SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool));
672251881Speter
673251881Speter  /* Check to make sure we're dealing with a file. */
674251881Speter  SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool));
675251881Speter  if (kind == svn_node_dir)
676251881Speter    return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
677251881Speter                             _("'%s' is not a file"), fs_path);
678251881Speter
679251881Speter  condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
680251881Speter  APR_ARRAY_PUSH(condensed_targets, const char *) = path;
681251881Speter
682251881Speter  lmb.path = fs_path;
683251881Speter  lmb.eldest = NULL;
684251881Speter  lmb.pool = pool;
685251881Speter
686251881Speter  /* Accumulate revision metadata by walking the revisions
687251881Speter     backwards; this allows us to follow moves/copies
688251881Speter     correctly. */
689251881Speter  SVN_ERR(svn_ra_get_log2(ra_session,
690251881Speter                          condensed_targets,
691251881Speter                          end, start, 0, /* no limit */
692251881Speter                          TRUE, FALSE, FALSE,
693251881Speter                          NULL, fr_log_message_receiver, &lmb,
694251881Speter                          pool));
695251881Speter
696251881Speter  /* Reparent the session while we go back through the history. */
697251881Speter  SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
698251881Speter  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool));
699251881Speter  SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool));
700251881Speter
701251881Speter  currpool = svn_pool_create(pool);
702251881Speter  lastpool = svn_pool_create(pool);
703251881Speter
704251881Speter  /* We want the first txdelta to be against the empty file. */
705251881Speter  last_props = apr_hash_make(lastpool);
706251881Speter  last_stream = svn_stream_empty(lastpool);
707251881Speter
708251881Speter  /* Walk the revision list in chronological order, downloading each fulltext,
709251881Speter     diffing it with its predecessor, and calling the file_revs handler for
710251881Speter     each one.  Use two iteration pools rather than one, because the diff
711251881Speter     routines need to look at a sliding window of revisions.  Two pools gives
712251881Speter     us a ring buffer of sorts. */
713251881Speter  for (rev = lmb.eldest; rev; rev = rev->next)
714251881Speter    {
715251881Speter      const char *temp_path;
716251881Speter      apr_pool_t *tmppool;
717251881Speter      apr_hash_t *props;
718251881Speter      apr_file_t *file;
719251881Speter      svn_stream_t *stream;
720251881Speter      apr_array_header_t *prop_diffs;
721251881Speter      svn_txdelta_stream_t *delta_stream;
722251881Speter      svn_txdelta_window_handler_t delta_handler = NULL;
723251881Speter      void *delta_baton = NULL;
724251881Speter
725251881Speter      svn_pool_clear(currpool);
726251881Speter
727251881Speter      /* Get the contents of the file from the repository, and put them in
728251881Speter         a temporary local file. */
729251881Speter      SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL,
730251881Speter                                     svn_io_file_del_on_pool_cleanup,
731251881Speter                                     currpool, currpool));
732251881Speter      SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision,
733251881Speter                              stream, NULL, &props, currpool));
734251881Speter      SVN_ERR(svn_stream_close(stream));
735251881Speter
736251881Speter      /* Open up a stream to the local file. */
737251881Speter      SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT,
738251881Speter                               currpool));
739251881Speter      stream = svn_stream_from_aprfile2(file, FALSE, currpool);
740251881Speter
741251881Speter      /* Calculate the property diff */
742251881Speter      SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool));
743251881Speter
744251881Speter      /* Call the file_rev handler */
745251881Speter      SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props,
746251881Speter                      FALSE, /* merged revision */
747251881Speter                      &delta_handler, &delta_baton, prop_diffs, lastpool));
748251881Speter
749251881Speter      /* Compute and send delta if client asked for it. */
750251881Speter      if (delta_handler)
751251881Speter        {
752251881Speter          /* Get the content delta. Don't calculate checksums as we don't
753251881Speter           * use them. */
754251881Speter          svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool);
755251881Speter
756251881Speter          /* And send. */
757251881Speter          SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
758251881Speter                                            delta_baton, lastpool));
759251881Speter        }
760251881Speter
761251881Speter      /* Switch the pools and data for the next iteration */
762251881Speter      tmppool = currpool;
763251881Speter      currpool = lastpool;
764251881Speter      lastpool = tmppool;
765251881Speter
766251881Speter      SVN_ERR(svn_stream_close(last_stream));
767251881Speter      last_stream = stream;
768251881Speter      last_props = props;
769251881Speter    }
770251881Speter
771251881Speter  SVN_ERR(svn_stream_close(last_stream));
772251881Speter  svn_pool_destroy(currpool);
773251881Speter  svn_pool_destroy(lastpool);
774251881Speter
775251881Speter  /* Reparent the session back to the original URL. */
776251881Speter  return svn_ra_reparent(ra_session, session_url, pool);
777251881Speter}
778251881Speter
779251881Speter
780251881Speter/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/
781251881Speter
782251881Speter/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */
783251881Spetertypedef struct log_path_del_rev_t
784251881Speter{
785251881Speter  /* Absolute repository path. */
786251881Speter  const char *path;
787251881Speter
788251881Speter  /* Revision PATH was first deleted or replaced. */
789251881Speter  svn_revnum_t revision_deleted;
790251881Speter} log_path_del_rev_t;
791251881Speter
792251881Speter/* A svn_log_entry_receiver_t callback for finding the revision
793251881Speter   ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced.
794251881Speter   Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED.
795251881Speter */
796251881Speterstatic svn_error_t *
797251881Speterlog_path_del_receiver(void *baton,
798251881Speter                      svn_log_entry_t *log_entry,
799251881Speter                      apr_pool_t *pool)
800251881Speter{
801251881Speter  log_path_del_rev_t *b = baton;
802251881Speter  apr_hash_index_t *hi;
803251881Speter
804251881Speter  /* No paths were changed in this revision.  Nothing to do. */
805251881Speter  if (! log_entry->changed_paths2)
806251881Speter    return SVN_NO_ERROR;
807251881Speter
808251881Speter  for (hi = apr_hash_first(pool, log_entry->changed_paths2);
809251881Speter       hi != NULL;
810251881Speter       hi = apr_hash_next(hi))
811251881Speter    {
812251881Speter      void *val;
813251881Speter      char *path;
814251881Speter      svn_log_changed_path_t *log_item;
815251881Speter
816251881Speter      apr_hash_this(hi, (void *) &path, NULL, &val);
817251881Speter      log_item = val;
818251881Speter      if (svn_path_compare_paths(b->path, path) == 0
819251881Speter          && (log_item->action == 'D' || log_item->action == 'R'))
820251881Speter        {
821251881Speter          /* Found the first deletion or replacement, we are done. */
822251881Speter          b->revision_deleted = log_entry->revision;
823251881Speter          break;
824251881Speter        }
825251881Speter    }
826251881Speter  return SVN_NO_ERROR;
827251881Speter}
828251881Speter
829251881Spetersvn_error_t *
830251881Spetersvn_ra__get_deleted_rev_from_log(svn_ra_session_t *session,
831251881Speter                                 const char *rel_deleted_path,
832251881Speter                                 svn_revnum_t peg_revision,
833251881Speter                                 svn_revnum_t end_revision,
834251881Speter                                 svn_revnum_t *revision_deleted,
835251881Speter                                 apr_pool_t *pool)
836251881Speter{
837251881Speter  const char *fs_path;
838251881Speter  log_path_del_rev_t log_path_deleted_baton;
839251881Speter
840251881Speter  /* Fetch the absolute FS path associated with PATH. */
841251881Speter  SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool));
842251881Speter
843251881Speter  if (!SVN_IS_VALID_REVNUM(peg_revision))
844251881Speter    return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
845251881Speter                             _("Invalid peg revision %ld"), peg_revision);
846251881Speter  if (!SVN_IS_VALID_REVNUM(end_revision))
847251881Speter    return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
848251881Speter                             _("Invalid end revision %ld"), end_revision);
849251881Speter  if (end_revision <= peg_revision)
850251881Speter    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
851251881Speter                            _("Peg revision must precede end revision"));
852251881Speter
853251881Speter  log_path_deleted_baton.path = fs_path;
854251881Speter  log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM;
855251881Speter
856251881Speter  /* Examine the logs of SESSION's URL to find when DELETED_PATH was first
857251881Speter     deleted or replaced. */
858251881Speter  SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0,
859251881Speter                          TRUE, TRUE, FALSE,
860251881Speter                          apr_array_make(pool, 0, sizeof(char *)),
861251881Speter                          log_path_del_receiver, &log_path_deleted_baton,
862251881Speter                          pool));
863251881Speter  *revision_deleted = log_path_deleted_baton.revision_deleted;
864251881Speter  return SVN_NO_ERROR;
865251881Speter}
866251881Speter
867251881Speter
868251881Spetersvn_error_t *
869251881Spetersvn_ra__get_inherited_props_walk(svn_ra_session_t *session,
870251881Speter                                 const char *path,
871251881Speter                                 svn_revnum_t revision,
872251881Speter                                 apr_array_header_t **inherited_props,
873251881Speter                                 apr_pool_t *result_pool,
874251881Speter                                 apr_pool_t *scratch_pool)
875251881Speter{
876251881Speter  const char *repos_root_url;
877251881Speter  const char *session_url;
878251881Speter  const char *parent_url;
879251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
880251881Speter
881251881Speter  *inherited_props =
882251881Speter    apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
883251881Speter
884251881Speter  /* Walk to the root of the repository getting inherited
885251881Speter     props for PATH. */
886251881Speter  SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool));
887251881Speter  SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
888251881Speter  parent_url = session_url;
889251881Speter
890251881Speter  while (strcmp(repos_root_url, parent_url))
891251881Speter    {
892251881Speter      apr_hash_index_t *hi;
893251881Speter      apr_hash_t *parent_props;
894251881Speter      apr_hash_t *final_hash = apr_hash_make(result_pool);
895251881Speter      svn_error_t *err;
896251881Speter
897251881Speter      svn_pool_clear(iterpool);
898251881Speter      parent_url = svn_uri_dirname(parent_url, scratch_pool);
899251881Speter      SVN_ERR(svn_ra_reparent(session, parent_url, iterpool));
900251881Speter      err = session->vtable->get_dir(session, NULL, NULL,
901251881Speter                                     &parent_props, "",
902251881Speter                                     revision, SVN_DIRENT_ALL,
903251881Speter                                     iterpool);
904251881Speter
905251881Speter      /* If the user doesn't have read access to a parent path then
906251881Speter         skip, but allow them to inherit from further up. */
907251881Speter      if (err)
908251881Speter        {
909251881Speter          if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)
910251881Speter              || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))
911251881Speter            {
912251881Speter              svn_error_clear(err);
913251881Speter              continue;
914251881Speter            }
915251881Speter          else
916251881Speter            {
917251881Speter              return svn_error_trace(err);
918251881Speter            }
919251881Speter        }
920251881Speter
921251881Speter      for (hi = apr_hash_first(scratch_pool, parent_props);
922251881Speter           hi;
923251881Speter           hi = apr_hash_next(hi))
924251881Speter        {
925299742Sdim          const char *name = apr_hash_this_key(hi);
926299742Sdim          apr_ssize_t klen = apr_hash_this_key_len(hi);
927299742Sdim          svn_string_t *value = apr_hash_this_val(hi);
928251881Speter
929251881Speter          if (svn_property_kind2(name) == svn_prop_regular_kind)
930251881Speter            {
931251881Speter              name = apr_pstrdup(result_pool, name);
932251881Speter              value = svn_string_dup(value, result_pool);
933251881Speter              apr_hash_set(final_hash, name, klen, value);
934251881Speter            }
935251881Speter        }
936251881Speter
937251881Speter      if (apr_hash_count(final_hash))
938251881Speter        {
939251881Speter          svn_prop_inherited_item_t *new_iprop =
940251881Speter            apr_palloc(result_pool, sizeof(*new_iprop));
941251881Speter          new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url,
942251881Speter                                                         parent_url,
943251881Speter                                                         result_pool);
944251881Speter          new_iprop->prop_hash = final_hash;
945299742Sdim          svn_sort__array_insert(*inherited_props, &new_iprop, 0);
946251881Speter        }
947251881Speter    }
948251881Speter
949251881Speter  /* Reparent session back to original URL. */
950251881Speter  SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool));
951251881Speter
952251881Speter  svn_pool_destroy(iterpool);
953251881Speter  return SVN_NO_ERROR;
954251881Speter}
955