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