1251881Speter/* rev_hunt.c --- routines to hunt down particular fs revisions and
2251881Speter *                their properties.
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
25251881Speter#include <string.h>
26251881Speter#include "svn_compat.h"
27251881Speter#include "svn_private_config.h"
28251881Speter#include "svn_hash.h"
29251881Speter#include "svn_pools.h"
30251881Speter#include "svn_error.h"
31251881Speter#include "svn_error_codes.h"
32251881Speter#include "svn_fs.h"
33251881Speter#include "svn_repos.h"
34251881Speter#include "svn_string.h"
35251881Speter#include "svn_time.h"
36251881Speter#include "svn_sorts.h"
37251881Speter#include "svn_props.h"
38251881Speter#include "svn_mergeinfo.h"
39251881Speter#include "repos.h"
40251881Speter#include "private/svn_fspath.h"
41251881Speter
42251881Speter
43251881Speter/* Note:  this binary search assumes that the datestamp properties on
44251881Speter   each revision are in chronological order.  That is if revision A >
45251881Speter   revision B, then A's datestamp is younger then B's datestamp.
46251881Speter
47251881Speter   If someone comes along and sets a bogus datestamp, this routine
48251881Speter   might not work right.
49251881Speter
50251881Speter   ### todo:  you know, we *could* have svn_fs_change_rev_prop() do
51251881Speter   some semantic checking when it's asked to change special reserved
52251881Speter   svn: properties.  It could prevent such a problem. */
53251881Speter
54251881Speter
55251881Speter/* helper for svn_repos_dated_revision().
56251881Speter
57251881Speter   Set *TM to the apr_time_t datestamp on revision REV in FS. */
58251881Speterstatic svn_error_t *
59251881Speterget_time(apr_time_t *tm,
60251881Speter         svn_fs_t *fs,
61251881Speter         svn_revnum_t rev,
62251881Speter         apr_pool_t *pool)
63251881Speter{
64251881Speter  svn_string_t *date_str;
65251881Speter
66251881Speter  SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
67251881Speter                               pool));
68251881Speter  if (! date_str)
69251881Speter    return svn_error_createf
70251881Speter      (SVN_ERR_FS_GENERAL, NULL,
71251881Speter       _("Failed to find time on revision %ld"), rev);
72251881Speter
73251881Speter  return svn_time_from_cstring(tm, date_str->data, pool);
74251881Speter}
75251881Speter
76251881Speter
77251881Spetersvn_error_t *
78251881Spetersvn_repos_dated_revision(svn_revnum_t *revision,
79251881Speter                         svn_repos_t *repos,
80251881Speter                         apr_time_t tm,
81251881Speter                         apr_pool_t *pool)
82251881Speter{
83251881Speter  svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
84251881Speter  apr_time_t this_time;
85251881Speter  svn_fs_t *fs = repos->fs;
86251881Speter
87251881Speter  /* Initialize top and bottom values of binary search. */
88251881Speter  SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
89251881Speter  rev_bot = 0;
90251881Speter  rev_top = rev_latest;
91251881Speter
92251881Speter  while (rev_bot <= rev_top)
93251881Speter    {
94251881Speter      rev_mid = (rev_top + rev_bot) / 2;
95251881Speter      SVN_ERR(get_time(&this_time, fs, rev_mid, pool));
96251881Speter
97251881Speter      if (this_time > tm)/* we've overshot */
98251881Speter        {
99251881Speter          apr_time_t previous_time;
100251881Speter
101251881Speter          if ((rev_mid - 1) < 0)
102251881Speter            {
103251881Speter              *revision = 0;
104251881Speter              break;
105251881Speter            }
106251881Speter
107251881Speter          /* see if time falls between rev_mid and rev_mid-1: */
108251881Speter          SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool));
109251881Speter          if (previous_time <= tm)
110251881Speter            {
111251881Speter              *revision = rev_mid - 1;
112251881Speter              break;
113251881Speter            }
114251881Speter
115251881Speter          rev_top = rev_mid - 1;
116251881Speter        }
117251881Speter
118251881Speter      else if (this_time < tm) /* we've undershot */
119251881Speter        {
120251881Speter          apr_time_t next_time;
121251881Speter
122251881Speter          if ((rev_mid + 1) > rev_latest)
123251881Speter            {
124251881Speter              *revision = rev_latest;
125251881Speter              break;
126251881Speter            }
127251881Speter
128251881Speter          /* see if time falls between rev_mid and rev_mid+1: */
129251881Speter          SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
130251881Speter          if (next_time > tm)
131251881Speter            {
132251881Speter              *revision = rev_mid;
133251881Speter              break;
134251881Speter            }
135251881Speter
136251881Speter          rev_bot = rev_mid + 1;
137251881Speter        }
138251881Speter
139251881Speter      else
140251881Speter        {
141251881Speter          *revision = rev_mid;  /* exact match! */
142251881Speter          break;
143251881Speter        }
144251881Speter    }
145251881Speter
146251881Speter  return SVN_NO_ERROR;
147251881Speter}
148251881Speter
149251881Speter
150251881Spetersvn_error_t *
151251881Spetersvn_repos_get_committed_info(svn_revnum_t *committed_rev,
152251881Speter                             const char **committed_date,
153251881Speter                             const char **last_author,
154251881Speter                             svn_fs_root_t *root,
155251881Speter                             const char *path,
156251881Speter                             apr_pool_t *pool)
157251881Speter{
158251881Speter  apr_hash_t *revprops;
159251881Speter
160251881Speter  svn_fs_t *fs = svn_fs_root_fs(root);
161251881Speter
162251881Speter  /* ### It might be simpler just to declare that revision
163251881Speter     properties have char * (i.e., UTF-8) values, not arbitrary
164251881Speter     binary values, hmmm. */
165251881Speter  svn_string_t *committed_date_s, *last_author_s;
166251881Speter
167251881Speter  /* Get the CR field out of the node's skel. */
168251881Speter  SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
169251881Speter
170251881Speter  /* Get the revision properties of this revision. */
171251881Speter  SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
172251881Speter
173251881Speter  /* Extract date and author from these revprops. */
174251881Speter  committed_date_s = apr_hash_get(revprops,
175251881Speter                                  SVN_PROP_REVISION_DATE,
176251881Speter                                  sizeof(SVN_PROP_REVISION_DATE)-1);
177251881Speter  last_author_s = apr_hash_get(revprops,
178251881Speter                               SVN_PROP_REVISION_AUTHOR,
179251881Speter                               sizeof(SVN_PROP_REVISION_AUTHOR)-1);
180251881Speter
181251881Speter  *committed_date = committed_date_s ? committed_date_s->data : NULL;
182251881Speter  *last_author = last_author_s ? last_author_s->data : NULL;
183251881Speter
184251881Speter  return SVN_NO_ERROR;
185251881Speter}
186251881Speter
187251881Spetersvn_error_t *
188251881Spetersvn_repos_history2(svn_fs_t *fs,
189251881Speter                   const char *path,
190251881Speter                   svn_repos_history_func_t history_func,
191251881Speter                   void *history_baton,
192251881Speter                   svn_repos_authz_func_t authz_read_func,
193251881Speter                   void *authz_read_baton,
194251881Speter                   svn_revnum_t start,
195251881Speter                   svn_revnum_t end,
196251881Speter                   svn_boolean_t cross_copies,
197251881Speter                   apr_pool_t *pool)
198251881Speter{
199251881Speter  svn_fs_history_t *history;
200251881Speter  apr_pool_t *oldpool = svn_pool_create(pool);
201251881Speter  apr_pool_t *newpool = svn_pool_create(pool);
202251881Speter  const char *history_path;
203251881Speter  svn_revnum_t history_rev;
204251881Speter  svn_fs_root_t *root;
205251881Speter
206251881Speter  /* Validate the revisions. */
207251881Speter  if (! SVN_IS_VALID_REVNUM(start))
208251881Speter    return svn_error_createf
209251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
210251881Speter       _("Invalid start revision %ld"), start);
211251881Speter  if (! SVN_IS_VALID_REVNUM(end))
212251881Speter    return svn_error_createf
213251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
214251881Speter       _("Invalid end revision %ld"), end);
215251881Speter
216251881Speter  /* Ensure that the input is ordered. */
217251881Speter  if (start > end)
218251881Speter    {
219251881Speter      svn_revnum_t tmprev = start;
220251881Speter      start = end;
221251881Speter      end = tmprev;
222251881Speter    }
223251881Speter
224251881Speter  /* Get a revision root for END, and an initial HISTORY baton.  */
225251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
226251881Speter
227251881Speter  if (authz_read_func)
228251881Speter    {
229251881Speter      svn_boolean_t readable;
230251881Speter      SVN_ERR(authz_read_func(&readable, root, path,
231251881Speter                              authz_read_baton, pool));
232251881Speter      if (! readable)
233251881Speter        return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
234251881Speter    }
235251881Speter
236251881Speter  SVN_ERR(svn_fs_node_history(&history, root, path, oldpool));
237251881Speter
238251881Speter  /* Now, we loop over the history items, calling svn_fs_history_prev(). */
239251881Speter  do
240251881Speter    {
241251881Speter      /* Note that we have to do some crazy pool work here.  We can't
242251881Speter         get rid of the old history until we use it to get the new, so
243251881Speter         we alternate back and forth between our subpools.  */
244251881Speter      apr_pool_t *tmppool;
245251881Speter      svn_error_t *err;
246251881Speter
247251881Speter      SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool));
248251881Speter
249251881Speter      /* Only continue if there is further history to deal with. */
250251881Speter      if (! history)
251251881Speter        break;
252251881Speter
253251881Speter      /* Fetch the location information for this history step. */
254251881Speter      SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
255251881Speter                                      history, newpool));
256251881Speter
257251881Speter      /* If this history item predates our START revision, quit
258251881Speter         here. */
259251881Speter      if (history_rev < start)
260251881Speter        break;
261251881Speter
262251881Speter      /* Is the history item readable?  If not, quit. */
263251881Speter      if (authz_read_func)
264251881Speter        {
265251881Speter          svn_boolean_t readable;
266251881Speter          svn_fs_root_t *history_root;
267251881Speter          SVN_ERR(svn_fs_revision_root(&history_root, fs,
268251881Speter                                       history_rev, newpool));
269251881Speter          SVN_ERR(authz_read_func(&readable, history_root, history_path,
270251881Speter                                  authz_read_baton, newpool));
271251881Speter          if (! readable)
272251881Speter            break;
273251881Speter        }
274251881Speter
275251881Speter      /* Call the user-provided callback function. */
276251881Speter      err = history_func(history_baton, history_path, history_rev, newpool);
277251881Speter      if (err)
278251881Speter        {
279251881Speter          if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
280251881Speter            {
281251881Speter              svn_error_clear(err);
282251881Speter              goto cleanup;
283251881Speter            }
284251881Speter          else
285251881Speter            {
286251881Speter              return svn_error_trace(err);
287251881Speter            }
288251881Speter        }
289251881Speter
290251881Speter      /* We're done with the old history item, so we can clear its
291251881Speter         pool, and then toggle our notion of "the old pool". */
292251881Speter      svn_pool_clear(oldpool);
293251881Speter      tmppool = oldpool;
294251881Speter      oldpool = newpool;
295251881Speter      newpool = tmppool;
296251881Speter    }
297251881Speter  while (history); /* shouldn't hit this */
298251881Speter
299251881Speter cleanup:
300251881Speter  svn_pool_destroy(oldpool);
301251881Speter  svn_pool_destroy(newpool);
302251881Speter  return SVN_NO_ERROR;
303251881Speter}
304251881Speter
305251881Speter
306251881Spetersvn_error_t *
307251881Spetersvn_repos_deleted_rev(svn_fs_t *fs,
308251881Speter                      const char *path,
309251881Speter                      svn_revnum_t start,
310251881Speter                      svn_revnum_t end,
311251881Speter                      svn_revnum_t *deleted,
312251881Speter                      apr_pool_t *pool)
313251881Speter{
314251881Speter  apr_pool_t *subpool;
315251881Speter  svn_fs_root_t *root, *copy_root;
316251881Speter  const char *copy_path;
317251881Speter  svn_revnum_t mid_rev;
318251881Speter  const svn_fs_id_t *start_node_id, *curr_node_id;
319251881Speter  svn_error_t *err;
320251881Speter
321251881Speter  /* Validate the revision range. */
322251881Speter  if (! SVN_IS_VALID_REVNUM(start))
323251881Speter    return svn_error_createf
324251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
325251881Speter       _("Invalid start revision %ld"), start);
326251881Speter  if (! SVN_IS_VALID_REVNUM(end))
327251881Speter    return svn_error_createf
328251881Speter      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
329251881Speter       _("Invalid end revision %ld"), end);
330251881Speter
331251881Speter  /* Ensure that the input is ordered. */
332251881Speter  if (start > end)
333251881Speter    {
334251881Speter      svn_revnum_t tmprev = start;
335251881Speter      start = end;
336251881Speter      end = tmprev;
337251881Speter    }
338251881Speter
339251881Speter  /* Ensure path exists in fs at start revision. */
340251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, start, pool));
341251881Speter  err = svn_fs_node_id(&start_node_id, root, path, pool);
342251881Speter  if (err)
343251881Speter    {
344251881Speter      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
345251881Speter        {
346251881Speter          /* Path must exist in fs at start rev. */
347251881Speter          *deleted = SVN_INVALID_REVNUM;
348251881Speter          svn_error_clear(err);
349251881Speter          return SVN_NO_ERROR;
350251881Speter        }
351251881Speter      return svn_error_trace(err);
352251881Speter    }
353251881Speter
354251881Speter  /* Ensure path was deleted at or before end revision. */
355251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
356251881Speter  err = svn_fs_node_id(&curr_node_id, root, path, pool);
357251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
358251881Speter    {
359251881Speter      svn_error_clear(err);
360251881Speter    }
361251881Speter  else if (err)
362251881Speter    {
363251881Speter      return svn_error_trace(err);
364251881Speter    }
365251881Speter  else
366251881Speter    {
367251881Speter      /* path exists in the end node and the end node is equivalent
368251881Speter         or otherwise equivalent to the start node.  This can mean
369251881Speter         a few things:
370251881Speter
371251881Speter           1) The end node *is* simply the start node, uncopied
372251881Speter              and unmodified in the start to end range.
373251881Speter
374251881Speter           2) The start node was modified, but never copied.
375251881Speter
376251881Speter           3) The start node was copied, but this copy occurred at
377251881Speter              start or some rev *previous* to start, this is
378251881Speter              effectively the same situation as 1 if the node was
379251881Speter              never modified or 2 if it was.
380251881Speter
381251881Speter         In the first three cases the path was not deleted in
382251881Speter         the specified range and we are done, but in the following
383251881Speter         cases the start node must have been deleted at least once:
384251881Speter
385251881Speter           4) The start node was deleted and replaced by a copy of
386251881Speter              itself at some rev between start and end.  This copy
387251881Speter              may itself have been replaced with copies of itself.
388251881Speter
389251881Speter           5) The start node was deleted and replaced by a node which
390251881Speter              it does not share any history with.
391251881Speter      */
392251881Speter      SVN_ERR(svn_fs_node_id(&curr_node_id, root, path, pool));
393251881Speter      if (svn_fs_compare_ids(start_node_id, curr_node_id) != -1)
394251881Speter        {
395251881Speter          SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
396251881Speter                                      path, pool));
397251881Speter          if (!copy_root ||
398251881Speter              (svn_fs_revision_root_revision(copy_root) <= start))
399251881Speter            {
400251881Speter              /* Case 1,2 or 3, nothing more to do. */
401251881Speter              *deleted = SVN_INVALID_REVNUM;
402251881Speter              return SVN_NO_ERROR;
403251881Speter            }
404251881Speter        }
405251881Speter    }
406251881Speter
407251881Speter  /* If we get here we know that path exists in rev start and was deleted
408251881Speter     at least once before rev end.  To find the revision path was first
409251881Speter     deleted we use a binary search.  The rules for the determining if
410251881Speter     the deletion comes before or after a given median revision are
411251881Speter     described by this matrix:
412251881Speter
413251881Speter                   |             Most recent copy event that
414251881Speter                   |               caused mid node to exist.
415251881Speter                   |-----------------------------------------------------
416251881Speter     Compare path  |                   |                |               |
417251881Speter     at start and  |   Copied at       |  Copied at     | Never copied  |
418251881Speter     mid nodes.    |   rev > start     |  rev <= start  |               |
419251881Speter                   |                   |                |               |
420251881Speter     -------------------------------------------------------------------|
421251881Speter     Mid node is   |  A) Start node    |                                |
422251881Speter     equivalent to |     replaced with |  E) Mid node == start node,    |
423251881Speter     start node    |     an unmodified |     look HIGHER.               |
424251881Speter                   |     copy of       |                                |
425251881Speter                   |     itself,       |                                |
426251881Speter                   |     look LOWER.   |                                |
427251881Speter     -------------------------------------------------------------------|
428251881Speter     Mid node is   |  B) Start node    |                                |
429251881Speter     otherwise     |     replaced with |  F) Mid node is a modified     |
430251881Speter     related to    |     a modified    |     version of start node,     |
431251881Speter     start node    |     copy of       |     look HIGHER.               |
432251881Speter                   |     itself,       |                                |
433251881Speter                   |     look LOWER.   |                                |
434251881Speter     -------------------------------------------------------------------|
435251881Speter     Mid node is   |                                                    |
436251881Speter     unrelated to  |  C) Start node replaced with unrelated mid node,   |
437251881Speter     start node    |     look LOWER.                                    |
438251881Speter                   |                                                    |
439251881Speter     -------------------------------------------------------------------|
440251881Speter     Path doesn't  |                                                    |
441251881Speter     exist at mid  |  D) Start node deleted before mid node,            |
442251881Speter     node          |     look LOWER                                     |
443251881Speter                   |                                                    |
444251881Speter     --------------------------------------------------------------------
445251881Speter  */
446251881Speter
447251881Speter  mid_rev = (start + end) / 2;
448251881Speter  subpool = svn_pool_create(pool);
449251881Speter
450251881Speter  while (1)
451251881Speter    {
452251881Speter      svn_pool_clear(subpool);
453251881Speter
454251881Speter      /* Get revision root and node id for mid_rev at that revision. */
455251881Speter      SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, subpool));
456251881Speter      err = svn_fs_node_id(&curr_node_id, root, path, subpool);
457251881Speter
458251881Speter      if (err)
459251881Speter        {
460251881Speter          if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
461251881Speter            {
462251881Speter              /* Case D: Look lower in the range. */
463251881Speter              svn_error_clear(err);
464251881Speter              end = mid_rev;
465251881Speter              mid_rev = (start + mid_rev) / 2;
466251881Speter            }
467251881Speter          else
468251881Speter            return svn_error_trace(err);
469251881Speter        }
470251881Speter      else
471251881Speter        {
472251881Speter          /* Determine the relationship between the start node
473251881Speter             and the current node. */
474251881Speter          int cmp = svn_fs_compare_ids(start_node_id, curr_node_id);
475251881Speter          SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
476251881Speter                                      path, subpool));
477251881Speter          if (cmp == -1 ||
478251881Speter              (copy_root &&
479251881Speter               (svn_fs_revision_root_revision(copy_root) > start)))
480251881Speter            {
481251881Speter              /* Cases A, B, C: Look at lower revs. */
482251881Speter              end = mid_rev;
483251881Speter              mid_rev = (start + mid_rev) / 2;
484251881Speter            }
485251881Speter          else if (end - mid_rev == 1)
486251881Speter            {
487251881Speter              /* Found the node path was deleted. */
488251881Speter              *deleted = end;
489251881Speter              break;
490251881Speter            }
491251881Speter          else
492251881Speter            {
493251881Speter              /* Cases E, F: Look at higher revs. */
494251881Speter              start = mid_rev;
495251881Speter              mid_rev = (start + end) / 2;
496251881Speter            }
497251881Speter        }
498251881Speter    }
499251881Speter
500251881Speter  svn_pool_destroy(subpool);
501251881Speter  return SVN_NO_ERROR;
502251881Speter}
503251881Speter
504251881Speter
505251881Speter/* Helper func:  return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
506251881Speter   unreadable. */
507251881Speterstatic svn_error_t *
508251881Spetercheck_readability(svn_fs_root_t *root,
509251881Speter                  const char *path,
510251881Speter                  svn_repos_authz_func_t authz_read_func,
511251881Speter                  void *authz_read_baton,
512251881Speter                  apr_pool_t *pool)
513251881Speter{
514251881Speter  svn_boolean_t readable;
515251881Speter  SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
516251881Speter  if (! readable)
517251881Speter    return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
518251881Speter                            _("Unreadable path encountered; access denied"));
519251881Speter  return SVN_NO_ERROR;
520251881Speter}
521251881Speter
522251881Speter
523251881Speter/* The purpose of this function is to discover if fs_path@future_rev
524251881Speter * is derived from fs_path@peg_rev.  The return is placed in *is_ancestor. */
525251881Speter
526251881Speterstatic svn_error_t *
527251881Spetercheck_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
528251881Speter                           svn_fs_t *fs,
529251881Speter                           const char *fs_path,
530251881Speter                           svn_revnum_t peg_revision,
531251881Speter                           svn_revnum_t future_revision,
532251881Speter                           apr_pool_t *pool)
533251881Speter{
534251881Speter  svn_fs_root_t *root;
535251881Speter  svn_fs_history_t *history;
536251881Speter  const char *path = NULL;
537251881Speter  svn_revnum_t revision;
538251881Speter  apr_pool_t *lastpool, *currpool;
539251881Speter
540251881Speter  lastpool = svn_pool_create(pool);
541251881Speter  currpool = svn_pool_create(pool);
542251881Speter
543251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
544251881Speter
545251881Speter  SVN_ERR(svn_fs_node_history(&history, root, fs_path, lastpool));
546251881Speter
547251881Speter  /* Since paths that are different according to strcmp may still be
548251881Speter     equivalent (due to number of consecutive slashes and the fact that
549251881Speter     "" is the same as "/"), we get the "canonical" path in the first
550251881Speter     iteration below so that the comparison after the loop will work
551251881Speter     correctly. */
552251881Speter  fs_path = NULL;
553251881Speter
554251881Speter  while (1)
555251881Speter    {
556251881Speter      apr_pool_t *tmppool;
557251881Speter
558251881Speter      SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool));
559251881Speter
560251881Speter      if (!history)
561251881Speter        break;
562251881Speter
563251881Speter      SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
564251881Speter
565251881Speter      if (!fs_path)
566251881Speter        fs_path = apr_pstrdup(pool, path);
567251881Speter
568251881Speter      if (revision <= peg_revision)
569251881Speter        break;
570251881Speter
571251881Speter      /* Clear old pool and flip. */
572251881Speter      svn_pool_clear(lastpool);
573251881Speter      tmppool = lastpool;
574251881Speter      lastpool = currpool;
575251881Speter      currpool = tmppool;
576251881Speter    }
577251881Speter
578251881Speter  /* We must have had at least one iteration above where we
579251881Speter     reassigned fs_path. Else, the path wouldn't have existed at
580251881Speter     future_revision and svn_fs_history would have thrown. */
581251881Speter  SVN_ERR_ASSERT(fs_path != NULL);
582251881Speter
583251881Speter  *is_ancestor = (history && strcmp(path, fs_path) == 0);
584251881Speter
585251881Speter  return SVN_NO_ERROR;
586251881Speter}
587251881Speter
588251881Speter
589251881Spetersvn_error_t *
590251881Spetersvn_repos__prev_location(svn_revnum_t *appeared_rev,
591251881Speter                         const char **prev_path,
592251881Speter                         svn_revnum_t *prev_rev,
593251881Speter                         svn_fs_t *fs,
594251881Speter                         svn_revnum_t revision,
595251881Speter                         const char *path,
596251881Speter                         apr_pool_t *pool)
597251881Speter{
598251881Speter  svn_fs_root_t *root, *copy_root;
599251881Speter  const char *copy_path, *copy_src_path, *remainder;
600251881Speter  svn_revnum_t copy_src_rev;
601251881Speter
602251881Speter  /* Initialize return variables. */
603251881Speter  if (appeared_rev)
604251881Speter    *appeared_rev = SVN_INVALID_REVNUM;
605251881Speter  if (prev_rev)
606251881Speter    *prev_rev = SVN_INVALID_REVNUM;
607251881Speter  if (prev_path)
608251881Speter    *prev_path = NULL;
609251881Speter
610251881Speter  /* Ask about the most recent copy which affected PATH@REVISION.  If
611251881Speter     there was no such copy, we're done.  */
612251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
613251881Speter  SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root, path, pool));
614251881Speter  if (! copy_root)
615251881Speter    return SVN_NO_ERROR;
616251881Speter
617251881Speter  /* Ultimately, it's not the path of the closest copy's source that
618251881Speter     we care about -- it's our own path's location in the copy source
619251881Speter     revision.  So we'll tack the relative path that expresses the
620251881Speter     difference between the copy destination and our path in the copy
621251881Speter     revision onto the copy source path to determine this information.
622251881Speter
623251881Speter     In other words, if our path is "/branches/my-branch/foo/bar", and
624251881Speter     we know that the closest relevant copy was a copy of "/trunk" to
625251881Speter     "/branches/my-branch", then that relative path under the copy
626251881Speter     destination is "/foo/bar".  Tacking that onto the copy source
627251881Speter     path tells us that our path was located at "/trunk/foo/bar"
628251881Speter     before the copy.
629251881Speter  */
630251881Speter  SVN_ERR(svn_fs_copied_from(&copy_src_rev, &copy_src_path,
631251881Speter                             copy_root, copy_path, pool));
632251881Speter  remainder = svn_fspath__skip_ancestor(copy_path, path);
633251881Speter  if (prev_path)
634251881Speter    *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
635251881Speter  if (appeared_rev)
636251881Speter    *appeared_rev = svn_fs_revision_root_revision(copy_root);
637251881Speter  if (prev_rev)
638251881Speter    *prev_rev = copy_src_rev;
639251881Speter  return SVN_NO_ERROR;
640251881Speter}
641251881Speter
642251881Speter
643251881Spetersvn_error_t *
644251881Spetersvn_repos_trace_node_locations(svn_fs_t *fs,
645251881Speter                               apr_hash_t **locations,
646251881Speter                               const char *fs_path,
647251881Speter                               svn_revnum_t peg_revision,
648251881Speter                               const apr_array_header_t *location_revisions_orig,
649251881Speter                               svn_repos_authz_func_t authz_read_func,
650251881Speter                               void *authz_read_baton,
651251881Speter                               apr_pool_t *pool)
652251881Speter{
653251881Speter  apr_array_header_t *location_revisions;
654251881Speter  svn_revnum_t *revision_ptr, *revision_ptr_end;
655251881Speter  svn_fs_root_t *root;
656251881Speter  const char *path;
657251881Speter  svn_revnum_t revision;
658251881Speter  svn_boolean_t is_ancestor;
659251881Speter  apr_pool_t *lastpool, *currpool;
660251881Speter  const svn_fs_id_t *id;
661251881Speter
662251881Speter  SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
663251881Speter
664251881Speter  /* Ensure that FS_PATH is absolute, because our path-math below will
665251881Speter     depend on that being the case.  */
666251881Speter  if (*fs_path != '/')
667251881Speter    fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);
668251881Speter
669251881Speter  /* Another sanity check. */
670251881Speter  if (authz_read_func)
671251881Speter    {
672251881Speter      svn_fs_root_t *peg_root;
673251881Speter      SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
674251881Speter      SVN_ERR(check_readability(peg_root, fs_path,
675251881Speter                                authz_read_func, authz_read_baton, pool));
676251881Speter    }
677251881Speter
678251881Speter  *locations = apr_hash_make(pool);
679251881Speter
680251881Speter  /* We flip between two pools in the second loop below. */
681251881Speter  lastpool = svn_pool_create(pool);
682251881Speter  currpool = svn_pool_create(pool);
683251881Speter
684251881Speter  /* First - let's sort the array of the revisions from the greatest revision
685251881Speter   * downward, so it will be easier to search on. */
686251881Speter  location_revisions = apr_array_copy(pool, location_revisions_orig);
687251881Speter  qsort(location_revisions->elts, location_revisions->nelts,
688251881Speter        sizeof(*revision_ptr), svn_sort_compare_revisions);
689251881Speter
690251881Speter  revision_ptr = (svn_revnum_t *)location_revisions->elts;
691251881Speter  revision_ptr_end = revision_ptr + location_revisions->nelts;
692251881Speter
693251881Speter  /* Ignore revisions R that are younger than the peg_revisions where
694251881Speter     path@peg_revision is not an ancestor of path@R. */
695251881Speter  is_ancestor = FALSE;
696251881Speter  while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
697251881Speter    {
698251881Speter      svn_pool_clear(currpool);
699251881Speter      SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path,
700251881Speter                                         peg_revision, *revision_ptr,
701251881Speter                                         currpool));
702251881Speter      if (is_ancestor)
703251881Speter        break;
704251881Speter      ++revision_ptr;
705251881Speter    }
706251881Speter
707251881Speter  revision = is_ancestor ? *revision_ptr : peg_revision;
708251881Speter  path = fs_path;
709251881Speter  if (authz_read_func)
710251881Speter    {
711251881Speter      SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
712251881Speter      SVN_ERR(check_readability(root, fs_path, authz_read_func,
713251881Speter                                authz_read_baton, pool));
714251881Speter    }
715251881Speter
716251881Speter  while (revision_ptr < revision_ptr_end)
717251881Speter    {
718251881Speter      apr_pool_t *tmppool;
719251881Speter      svn_revnum_t appeared_rev, prev_rev;
720251881Speter      const char *prev_path;
721251881Speter
722251881Speter      /* Find the target of the innermost copy relevant to path@revision.
723251881Speter         The copy may be of path itself, or of a parent directory. */
724251881Speter      SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
725251881Speter                                       fs, revision, path, currpool));
726251881Speter      if (! prev_path)
727251881Speter        break;
728251881Speter
729251881Speter      if (authz_read_func)
730251881Speter        {
731251881Speter          svn_boolean_t readable;
732251881Speter          svn_fs_root_t *tmp_root;
733251881Speter
734251881Speter          SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool));
735251881Speter          SVN_ERR(authz_read_func(&readable, tmp_root, path,
736251881Speter                                  authz_read_baton, currpool));
737251881Speter          if (! readable)
738251881Speter            {
739251881Speter              svn_pool_destroy(lastpool);
740251881Speter              svn_pool_destroy(currpool);
741251881Speter
742251881Speter              return SVN_NO_ERROR;
743251881Speter            }
744251881Speter        }
745251881Speter
746251881Speter      /* Assign the current path to all younger revisions until we reach
747251881Speter         the copy target rev. */
748251881Speter      while ((revision_ptr < revision_ptr_end)
749251881Speter             && (*revision_ptr >= appeared_rev))
750251881Speter        {
751251881Speter          /* *revision_ptr is allocated out of pool, so we can point
752251881Speter             to in the hash table. */
753251881Speter          apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
754251881Speter                       apr_pstrdup(pool, path));
755251881Speter          revision_ptr++;
756251881Speter        }
757251881Speter
758251881Speter      /* Ignore all revs between the copy target rev and the copy
759251881Speter         source rev (non-inclusive). */
760251881Speter      while ((revision_ptr < revision_ptr_end)
761251881Speter             && (*revision_ptr > prev_rev))
762251881Speter        revision_ptr++;
763251881Speter
764251881Speter      /* State update. */
765251881Speter      path = prev_path;
766251881Speter      revision = prev_rev;
767251881Speter
768251881Speter      /* Clear last pool and switch. */
769251881Speter      svn_pool_clear(lastpool);
770251881Speter      tmppool = lastpool;
771251881Speter      lastpool = currpool;
772251881Speter      currpool = tmppool;
773251881Speter    }
774251881Speter
775251881Speter  /* There are no copies relevant to path@revision.  So any remaining
776251881Speter     revisions either predate the creation of path@revision or have
777251881Speter     the node existing at the same path.  We will look up path@lrev
778251881Speter     for each remaining location-revision and make sure it is related
779251881Speter     to path@revision. */
780251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
781251881Speter  SVN_ERR(svn_fs_node_id(&id, root, path, pool));
782251881Speter  while (revision_ptr < revision_ptr_end)
783251881Speter    {
784251881Speter      svn_node_kind_t kind;
785251881Speter      const svn_fs_id_t *lrev_id;
786251881Speter
787251881Speter      svn_pool_clear(currpool);
788251881Speter      SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool));
789251881Speter      SVN_ERR(svn_fs_check_path(&kind, root, path, currpool));
790251881Speter      if (kind == svn_node_none)
791251881Speter        break;
792251881Speter      SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
793251881Speter      if (! svn_fs_check_related(id, lrev_id))
794251881Speter        break;
795251881Speter
796251881Speter      /* The node exists at the same path; record that and advance. */
797251881Speter      apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
798251881Speter                   apr_pstrdup(pool, path));
799251881Speter      revision_ptr++;
800251881Speter    }
801251881Speter
802251881Speter  /* Ignore any remaining location-revisions; they predate the
803251881Speter     creation of path@revision. */
804251881Speter
805251881Speter  svn_pool_destroy(lastpool);
806251881Speter  svn_pool_destroy(currpool);
807251881Speter
808251881Speter  return SVN_NO_ERROR;
809251881Speter}
810251881Speter
811251881Speter
812251881Speter/* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
813251881Speter   its revision range fits between END_REV and START_REV, possibly
814251881Speter   cropping the range so that it fits *entirely* in that range. */
815251881Speterstatic svn_error_t *
816251881Spetermaybe_crop_and_send_segment(svn_location_segment_t *segment,
817251881Speter                            svn_revnum_t start_rev,
818251881Speter                            svn_revnum_t end_rev,
819251881Speter                            svn_location_segment_receiver_t receiver,
820251881Speter                            void *receiver_baton,
821251881Speter                            apr_pool_t *pool)
822251881Speter{
823251881Speter  /* We only want to transmit this segment if some portion of it
824251881Speter     is between our END_REV and START_REV. */
825251881Speter  if (! ((segment->range_start > start_rev)
826251881Speter         || (segment->range_end < end_rev)))
827251881Speter    {
828251881Speter      /* Correct our segment range when the range straddles one of
829251881Speter         our requested revision boundaries. */
830251881Speter      if (segment->range_start < end_rev)
831251881Speter        segment->range_start = end_rev;
832251881Speter      if (segment->range_end > start_rev)
833251881Speter        segment->range_end = start_rev;
834251881Speter      SVN_ERR(receiver(segment, receiver_baton, pool));
835251881Speter    }
836251881Speter  return SVN_NO_ERROR;
837251881Speter}
838251881Speter
839251881Speter
840251881Spetersvn_error_t *
841251881Spetersvn_repos_node_location_segments(svn_repos_t *repos,
842251881Speter                                 const char *path,
843251881Speter                                 svn_revnum_t peg_revision,
844251881Speter                                 svn_revnum_t start_rev,
845251881Speter                                 svn_revnum_t end_rev,
846251881Speter                                 svn_location_segment_receiver_t receiver,
847251881Speter                                 void *receiver_baton,
848251881Speter                                 svn_repos_authz_func_t authz_read_func,
849251881Speter                                 void *authz_read_baton,
850251881Speter                                 apr_pool_t *pool)
851251881Speter{
852251881Speter  svn_fs_t *fs = svn_repos_fs(repos);
853251881Speter  svn_stringbuf_t *current_path;
854251881Speter  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
855251881Speter  apr_pool_t *subpool;
856251881Speter
857251881Speter  /* No PEG_REVISION?  We'll use HEAD. */
858251881Speter  if (! SVN_IS_VALID_REVNUM(peg_revision))
859251881Speter    {
860251881Speter      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
861251881Speter      peg_revision = youngest_rev;
862251881Speter    }
863251881Speter
864251881Speter  /* No START_REV?  We'll use HEAD (which we may have already fetched). */
865251881Speter  if (! SVN_IS_VALID_REVNUM(start_rev))
866251881Speter    {
867251881Speter      if (SVN_IS_VALID_REVNUM(youngest_rev))
868251881Speter        start_rev = youngest_rev;
869251881Speter      else
870251881Speter        SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
871251881Speter    }
872251881Speter
873251881Speter  /* No END_REV?  We'll use 0. */
874251881Speter  end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
875251881Speter
876251881Speter  /* Are the revision properly ordered?  They better be -- the API
877251881Speter     demands it. */
878251881Speter  SVN_ERR_ASSERT(end_rev <= start_rev);
879251881Speter  SVN_ERR_ASSERT(start_rev <= peg_revision);
880251881Speter
881251881Speter  /* Ensure that PATH is absolute, because our path-math will depend
882251881Speter     on that being the case.  */
883251881Speter  if (*path != '/')
884251881Speter    path = apr_pstrcat(pool, "/", path, (char *)NULL);
885251881Speter
886251881Speter  /* Auth check. */
887251881Speter  if (authz_read_func)
888251881Speter    {
889251881Speter      svn_fs_root_t *peg_root;
890251881Speter      SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
891251881Speter      SVN_ERR(check_readability(peg_root, path,
892251881Speter                                authz_read_func, authz_read_baton, pool));
893251881Speter    }
894251881Speter
895251881Speter  /* Okay, let's get searching! */
896251881Speter  subpool = svn_pool_create(pool);
897251881Speter  current_rev = peg_revision;
898251881Speter  current_path = svn_stringbuf_create(path, pool);
899251881Speter  while (current_rev >= end_rev)
900251881Speter    {
901251881Speter      svn_revnum_t appeared_rev, prev_rev;
902251881Speter      const char *cur_path, *prev_path;
903251881Speter      svn_location_segment_t *segment;
904251881Speter
905251881Speter      svn_pool_clear(subpool);
906251881Speter
907251881Speter      cur_path = apr_pstrmemdup(subpool, current_path->data,
908251881Speter                                current_path->len);
909251881Speter      segment = apr_pcalloc(subpool, sizeof(*segment));
910251881Speter      segment->range_end = current_rev;
911251881Speter      segment->range_start = end_rev;
912251881Speter      /* segment path should be absolute without leading '/'. */
913251881Speter      segment->path = cur_path + 1;
914251881Speter
915251881Speter      SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
916251881Speter                                       fs, current_rev, cur_path, subpool));
917251881Speter
918251881Speter      /* If there are no previous locations for this thing (meaning,
919251881Speter         it originated at the current path), then we simply need to
920251881Speter         find its revision of origin to populate our final segment.
921251881Speter         Otherwise, the APPEARED_REV is the start of current segment's
922251881Speter         range. */
923251881Speter      if (! prev_path)
924251881Speter        {
925251881Speter          svn_fs_root_t *revroot;
926251881Speter          SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
927251881Speter          SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
928251881Speter                                         cur_path, subpool));
929251881Speter          if (segment->range_start < end_rev)
930251881Speter            segment->range_start = end_rev;
931251881Speter          current_rev = SVN_INVALID_REVNUM;
932251881Speter        }
933251881Speter      else
934251881Speter        {
935251881Speter          segment->range_start = appeared_rev;
936251881Speter          svn_stringbuf_set(current_path, prev_path);
937251881Speter          current_rev = prev_rev;
938251881Speter        }
939251881Speter
940251881Speter      /* Report our segment, providing it passes authz muster. */
941251881Speter      if (authz_read_func)
942251881Speter        {
943251881Speter          svn_boolean_t readable;
944251881Speter          svn_fs_root_t *cur_rev_root;
945251881Speter
946251881Speter          /* authz_read_func requires path to have a leading slash. */
947251881Speter          const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
948251881Speter                                             (char *)NULL);
949251881Speter
950251881Speter          SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
951251881Speter                                       segment->range_end, subpool));
952251881Speter          SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
953251881Speter                                  authz_read_baton, subpool));
954251881Speter          if (! readable)
955251881Speter            return SVN_NO_ERROR;
956251881Speter        }
957251881Speter
958251881Speter      /* Transmit the segment (if it's within the scope of our concern). */
959251881Speter      SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
960251881Speter                                          receiver, receiver_baton, subpool));
961251881Speter
962251881Speter      /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
963251881Speter         (and didn't ever reach END_REV).  */
964251881Speter      if (! SVN_IS_VALID_REVNUM(current_rev))
965251881Speter        break;
966251881Speter
967251881Speter      /* If there's a gap in the history, we need to report as much
968251881Speter         (if the gap is within the scope of our concern). */
969251881Speter      if (segment->range_start - current_rev > 1)
970251881Speter        {
971251881Speter          svn_location_segment_t *gap_segment;
972251881Speter          gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
973251881Speter          gap_segment->range_end = segment->range_start - 1;
974251881Speter          gap_segment->range_start = current_rev + 1;
975251881Speter          gap_segment->path = NULL;
976251881Speter          SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
977251881Speter                                              receiver, receiver_baton,
978251881Speter                                              subpool));
979251881Speter        }
980251881Speter    }
981251881Speter  svn_pool_destroy(subpool);
982251881Speter  return SVN_NO_ERROR;
983251881Speter}
984251881Speter
985251881Speter/* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
986251881Speterstatic svn_error_t *
987251881Speterget_path_mergeinfo(apr_hash_t **mergeinfo,
988251881Speter                   svn_fs_t *fs,
989251881Speter                   const char *path,
990251881Speter                   svn_revnum_t revnum,
991251881Speter                   apr_pool_t *result_pool,
992251881Speter                   apr_pool_t *scratch_pool)
993251881Speter{
994251881Speter  svn_mergeinfo_catalog_t tmp_catalog;
995251881Speter  svn_fs_root_t *root;
996251881Speter  apr_array_header_t *paths = apr_array_make(scratch_pool, 1,
997251881Speter                                             sizeof(const char *));
998251881Speter
999251881Speter  APR_ARRAY_PUSH(paths, const char *) = path;
1000251881Speter
1001251881Speter  SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool));
1002251881Speter  /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1003251881Speter     because we will filter out unreadable revisions in
1004251881Speter     find_interesting_revision(), above */
1005251881Speter  SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
1006251881Speter                                svn_mergeinfo_inherited, FALSE, TRUE,
1007251881Speter                                result_pool, scratch_pool));
1008251881Speter
1009251881Speter  *mergeinfo = svn_hash_gets(tmp_catalog, path);
1010251881Speter  if (!*mergeinfo)
1011251881Speter    *mergeinfo = apr_hash_make(result_pool);
1012251881Speter
1013251881Speter  return SVN_NO_ERROR;
1014251881Speter}
1015251881Speter
1016251881Speterstatic APR_INLINE svn_boolean_t
1017251881Speteris_path_in_hash(apr_hash_t *duplicate_path_revs,
1018251881Speter                const char *path,
1019251881Speter                svn_revnum_t revision,
1020251881Speter                apr_pool_t *pool)
1021251881Speter{
1022251881Speter  const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
1023251881Speter  void *ptr;
1024251881Speter
1025251881Speter  ptr = svn_hash_gets(duplicate_path_revs, key);
1026251881Speter  return ptr != NULL;
1027251881Speter}
1028251881Speter
1029251881Speterstruct path_revision
1030251881Speter{
1031251881Speter  svn_revnum_t revnum;
1032251881Speter  const char *path;
1033251881Speter
1034251881Speter  /* Does this path_rev have merges to also be included?  */
1035251881Speter  apr_hash_t *merged_mergeinfo;
1036251881Speter
1037251881Speter  /* Is this a merged revision? */
1038251881Speter  svn_boolean_t merged;
1039251881Speter};
1040251881Speter
1041251881Speter/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM.  Store
1042251881Speter   the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL.  The
1043251881Speter   returned *MERGED_MERGEINFO will be NULL if there are no changes. */
1044251881Speterstatic svn_error_t *
1045251881Speterget_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1046251881Speter                     svn_repos_t *repos,
1047251881Speter                     struct path_revision *old_path_rev,
1048251881Speter                     apr_pool_t *result_pool,
1049251881Speter                     apr_pool_t *scratch_pool)
1050251881Speter{
1051251881Speter  apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1052251881Speter  svn_error_t *err;
1053251881Speter  svn_fs_root_t *root;
1054251881Speter  apr_hash_t *changed_paths;
1055251881Speter  const char *path = old_path_rev->path;
1056251881Speter
1057251881Speter  /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1058251881Speter     if there is a property change. */
1059251881Speter  SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1060251881Speter                               scratch_pool));
1061251881Speter  SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
1062251881Speter  while (1)
1063251881Speter    {
1064251881Speter      svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
1065251881Speter      if (changed_path && changed_path->prop_mod)
1066251881Speter        break;
1067251881Speter      if (svn_fspath__is_root(path, strlen(path)))
1068251881Speter        {
1069251881Speter          *merged_mergeinfo = NULL;
1070251881Speter          return SVN_NO_ERROR;
1071251881Speter        }
1072251881Speter      path = svn_fspath__dirname(path, scratch_pool);
1073251881Speter    }
1074251881Speter
1075251881Speter  /* First, find the mergeinfo difference for old_path_rev->revnum, and
1076251881Speter     old_path_rev->revnum - 1. */
1077251881Speter  err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path,
1078251881Speter                           old_path_rev->revnum, scratch_pool,
1079251881Speter                           scratch_pool);
1080251881Speter  if (err)
1081251881Speter    {
1082251881Speter      if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1083251881Speter        {
1084251881Speter          /* Issue #3896: If invalid mergeinfo is encountered the
1085251881Speter             best we can do is ignore it and act is if there are
1086251881Speter             no mergeinfo differences. */
1087251881Speter          svn_error_clear(err);
1088251881Speter          *merged_mergeinfo = NULL;
1089251881Speter          return SVN_NO_ERROR;
1090251881Speter        }
1091251881Speter      else
1092251881Speter        {
1093251881Speter          return svn_error_trace(err);
1094251881Speter        }
1095251881Speter    }
1096251881Speter
1097251881Speter  err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path,
1098251881Speter                           old_path_rev->revnum - 1, scratch_pool,
1099251881Speter                           scratch_pool);
1100251881Speter  if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1101251881Speter              || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1102251881Speter    {
1103251881Speter      /* If the path doesn't exist in the previous revision or it does exist
1104251881Speter         but has invalid mergeinfo (Issue #3896), assume no merges. */
1105251881Speter      svn_error_clear(err);
1106251881Speter      *merged_mergeinfo = NULL;
1107251881Speter      return SVN_NO_ERROR;
1108251881Speter    }
1109251881Speter  else
1110251881Speter    SVN_ERR(err);
1111251881Speter
1112251881Speter  /* Then calculate and merge the differences. */
1113251881Speter  SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1114251881Speter                              curr_mergeinfo, FALSE, result_pool,
1115251881Speter                              scratch_pool));
1116251881Speter  SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1117251881Speter
1118251881Speter  /* Store the result. */
1119251881Speter  if (apr_hash_count(changed))
1120251881Speter    *merged_mergeinfo = changed;
1121251881Speter  else
1122251881Speter    *merged_mergeinfo = NULL;
1123251881Speter
1124251881Speter  return SVN_NO_ERROR;
1125251881Speter}
1126251881Speter
1127251881Speterstatic svn_error_t *
1128251881Speterfind_interesting_revisions(apr_array_header_t *path_revisions,
1129251881Speter                           svn_repos_t *repos,
1130251881Speter                           const char *path,
1131251881Speter                           svn_revnum_t start,
1132251881Speter                           svn_revnum_t end,
1133251881Speter                           svn_boolean_t include_merged_revisions,
1134251881Speter                           svn_boolean_t mark_as_merged,
1135251881Speter                           apr_hash_t *duplicate_path_revs,
1136251881Speter                           svn_repos_authz_func_t authz_read_func,
1137251881Speter                           void *authz_read_baton,
1138251881Speter                           apr_pool_t *result_pool,
1139251881Speter                           apr_pool_t *scratch_pool)
1140251881Speter{
1141251881Speter  apr_pool_t *iterpool, *last_pool;
1142251881Speter  svn_fs_history_t *history;
1143251881Speter  svn_fs_root_t *root;
1144251881Speter  svn_node_kind_t kind;
1145251881Speter
1146251881Speter  /* We switch between two pools while looping, since we need information from
1147251881Speter     the last iteration to be available. */
1148251881Speter  iterpool = svn_pool_create(scratch_pool);
1149251881Speter  last_pool = svn_pool_create(scratch_pool);
1150251881Speter
1151251881Speter  /* The path had better be a file in this revision. */
1152251881Speter  SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1153251881Speter  SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1154251881Speter  if (kind != svn_node_file)
1155251881Speter    return svn_error_createf
1156251881Speter      (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1157251881Speter       path, end);
1158251881Speter
1159251881Speter  /* Open a history object. */
1160251881Speter  SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1161251881Speter  while (1)
1162251881Speter    {
1163251881Speter      struct path_revision *path_rev;
1164251881Speter      svn_revnum_t tmp_revnum;
1165251881Speter      const char *tmp_path;
1166251881Speter      apr_pool_t *tmp_pool;
1167251881Speter
1168251881Speter      svn_pool_clear(iterpool);
1169251881Speter
1170251881Speter      /* Fetch the history object to walk through. */
1171251881Speter      SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1172251881Speter      if (!history)
1173251881Speter        break;
1174251881Speter      SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1175251881Speter                                      history, iterpool));
1176251881Speter
1177251881Speter      /* Check to see if we already saw this path (and it's ancestors) */
1178251881Speter      if (include_merged_revisions
1179251881Speter          && is_path_in_hash(duplicate_path_revs, tmp_path,
1180251881Speter                             tmp_revnum, iterpool))
1181251881Speter         break;
1182251881Speter
1183251881Speter      /* Check authorization. */
1184251881Speter      if (authz_read_func)
1185251881Speter        {
1186251881Speter          svn_boolean_t readable;
1187251881Speter          svn_fs_root_t *tmp_root;
1188251881Speter
1189251881Speter          SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1190251881Speter                                       iterpool));
1191251881Speter          SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1192251881Speter                                  authz_read_baton, iterpool));
1193251881Speter          if (! readable)
1194251881Speter            break;
1195251881Speter        }
1196251881Speter
1197251881Speter      /* We didn't break, so we must really want this path-rev. */
1198251881Speter      path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1199251881Speter      path_rev->path = apr_pstrdup(result_pool, tmp_path);
1200251881Speter      path_rev->revnum = tmp_revnum;
1201251881Speter      path_rev->merged = mark_as_merged;
1202251881Speter      APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1203251881Speter
1204251881Speter      if (include_merged_revisions)
1205251881Speter        SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1206251881Speter                                     path_rev, result_pool, iterpool));
1207251881Speter      else
1208251881Speter        path_rev->merged_mergeinfo = NULL;
1209251881Speter
1210251881Speter      /* Add the path/rev pair to the hash, so we can filter out future
1211251881Speter         occurrences of it.  We only care about this if including merged
1212251881Speter         revisions, 'cause that's the only time we can have duplicates. */
1213251881Speter      svn_hash_sets(duplicate_path_revs,
1214251881Speter                    apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1215251881Speter                                 path_rev->revnum),
1216251881Speter                    (void *)0xdeadbeef);
1217251881Speter
1218251881Speter      if (path_rev->revnum <= start)
1219251881Speter        break;
1220251881Speter
1221251881Speter      /* Swap pools. */
1222251881Speter      tmp_pool = iterpool;
1223251881Speter      iterpool = last_pool;
1224251881Speter      last_pool = tmp_pool;
1225251881Speter    }
1226251881Speter
1227251881Speter  svn_pool_destroy(iterpool);
1228251881Speter  svn_pool_destroy(last_pool);
1229251881Speter
1230251881Speter  return SVN_NO_ERROR;
1231251881Speter}
1232251881Speter
1233251881Speter/* Comparison function to sort path/revisions in increasing order */
1234251881Speterstatic int
1235251881Spetercompare_path_revisions(const void *a, const void *b)
1236251881Speter{
1237251881Speter  struct path_revision *a_pr = *(struct path_revision *const *)a;
1238251881Speter  struct path_revision *b_pr = *(struct path_revision *const *)b;
1239251881Speter
1240251881Speter  if (a_pr->revnum == b_pr->revnum)
1241251881Speter    return 0;
1242251881Speter
1243251881Speter  return a_pr->revnum < b_pr->revnum ? 1 : -1;
1244251881Speter}
1245251881Speter
1246251881Speterstatic svn_error_t *
1247251881Speterfind_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1248251881Speter                      svn_revnum_t start,
1249251881Speter                      const apr_array_header_t *mainline_path_revisions,
1250251881Speter                      svn_repos_t *repos,
1251251881Speter                      apr_hash_t *duplicate_path_revs,
1252251881Speter                      svn_repos_authz_func_t authz_read_func,
1253251881Speter                      void *authz_read_baton,
1254251881Speter                      apr_pool_t *result_pool,
1255251881Speter                      apr_pool_t *scratch_pool)
1256251881Speter{
1257251881Speter  const apr_array_header_t *old;
1258251881Speter  apr_array_header_t *new_merged_path_revs;
1259251881Speter  apr_pool_t *iterpool, *last_pool;
1260251881Speter  apr_array_header_t *merged_path_revisions =
1261251881Speter    apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1262251881Speter
1263251881Speter  old = mainline_path_revisions;
1264251881Speter  iterpool = svn_pool_create(scratch_pool);
1265251881Speter  last_pool = svn_pool_create(scratch_pool);
1266251881Speter
1267251881Speter  do
1268251881Speter    {
1269251881Speter      int i;
1270251881Speter      apr_pool_t *temp_pool;
1271251881Speter
1272251881Speter      svn_pool_clear(iterpool);
1273251881Speter      new_merged_path_revs = apr_array_make(iterpool, 0,
1274251881Speter                                            sizeof(struct path_revision *));
1275251881Speter
1276251881Speter      /* Iterate over OLD, checking for non-empty mergeinfo.  If found, gather
1277251881Speter         path_revisions for any merged revisions, and store those in NEW. */
1278251881Speter      for (i = 0; i < old->nelts; i++)
1279251881Speter        {
1280251881Speter          apr_pool_t *iterpool2;
1281251881Speter          apr_hash_index_t *hi;
1282251881Speter          struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1283251881Speter                                                       struct path_revision *);
1284251881Speter          if (!old_pr->merged_mergeinfo)
1285251881Speter            continue;
1286251881Speter
1287251881Speter          iterpool2 = svn_pool_create(iterpool);
1288251881Speter
1289251881Speter          /* Determine and trace the merge sources. */
1290251881Speter          for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1291251881Speter               hi = apr_hash_next(hi))
1292251881Speter            {
1293251881Speter              apr_pool_t *iterpool3;
1294251881Speter              svn_rangelist_t *rangelist;
1295251881Speter              const char *path;
1296251881Speter              int j;
1297251881Speter
1298251881Speter              svn_pool_clear(iterpool2);
1299251881Speter              iterpool3 = svn_pool_create(iterpool2);
1300251881Speter
1301251881Speter              apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
1302251881Speter
1303251881Speter              for (j = 0; j < rangelist->nelts; j++)
1304251881Speter                {
1305251881Speter                  svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1306251881Speter                                                           svn_merge_range_t *);
1307251881Speter                  svn_node_kind_t kind;
1308251881Speter                  svn_fs_root_t *root;
1309251881Speter
1310251881Speter                  if (range->end < start)
1311251881Speter                    continue;
1312251881Speter
1313251881Speter                  svn_pool_clear(iterpool3);
1314251881Speter
1315251881Speter                  SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1316251881Speter                                               iterpool3));
1317251881Speter                  SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1318251881Speter                  if (kind != svn_node_file)
1319251881Speter                    continue;
1320251881Speter
1321251881Speter                  /* Search and find revisions to add to the NEW list. */
1322251881Speter                  SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1323251881Speter                                                     repos, path,
1324251881Speter                                                     range->start, range->end,
1325251881Speter                                                     TRUE, TRUE,
1326251881Speter                                                     duplicate_path_revs,
1327251881Speter                                                     authz_read_func,
1328251881Speter                                                     authz_read_baton,
1329251881Speter                                                     result_pool, iterpool3));
1330251881Speter                }
1331251881Speter              svn_pool_destroy(iterpool3);
1332251881Speter            }
1333251881Speter          svn_pool_destroy(iterpool2);
1334251881Speter        }
1335251881Speter
1336251881Speter      /* Append the newly found path revisions with the old ones. */
1337251881Speter      merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1338251881Speter                                               new_merged_path_revs);
1339251881Speter
1340251881Speter      /* Swap data structures */
1341251881Speter      old = new_merged_path_revs;
1342251881Speter      temp_pool = last_pool;
1343251881Speter      last_pool = iterpool;
1344251881Speter      iterpool = temp_pool;
1345251881Speter    }
1346251881Speter  while (new_merged_path_revs->nelts > 0);
1347251881Speter
1348251881Speter  /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1349251881Speter  qsort(merged_path_revisions->elts, merged_path_revisions->nelts,
1350251881Speter        sizeof(struct path_revision *), compare_path_revisions);
1351251881Speter
1352251881Speter  /* Copy to the output array. */
1353251881Speter  *merged_path_revisions_out = apr_array_copy(result_pool,
1354251881Speter                                              merged_path_revisions);
1355251881Speter
1356251881Speter  svn_pool_destroy(iterpool);
1357251881Speter  svn_pool_destroy(last_pool);
1358251881Speter
1359251881Speter  return SVN_NO_ERROR;
1360251881Speter}
1361251881Speter
1362251881Speterstruct send_baton
1363251881Speter{
1364251881Speter  apr_pool_t *iterpool;
1365251881Speter  apr_pool_t *last_pool;
1366251881Speter  apr_hash_t *last_props;
1367251881Speter  const char *last_path;
1368251881Speter  svn_fs_root_t *last_root;
1369251881Speter};
1370251881Speter
1371251881Speter/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1372251881Speter   SB. */
1373251881Speterstatic svn_error_t *
1374251881Spetersend_path_revision(struct path_revision *path_rev,
1375251881Speter                   svn_repos_t *repos,
1376251881Speter                   struct send_baton *sb,
1377251881Speter                   svn_file_rev_handler_t handler,
1378251881Speter                   void *handler_baton)
1379251881Speter{
1380251881Speter  apr_hash_t *rev_props;
1381251881Speter  apr_hash_t *props;
1382251881Speter  apr_array_header_t *prop_diffs;
1383251881Speter  svn_fs_root_t *root;
1384251881Speter  svn_txdelta_stream_t *delta_stream;
1385251881Speter  svn_txdelta_window_handler_t delta_handler = NULL;
1386251881Speter  void *delta_baton = NULL;
1387251881Speter  apr_pool_t *tmp_pool;  /* For swapping */
1388251881Speter  svn_boolean_t contents_changed;
1389251881Speter
1390251881Speter  svn_pool_clear(sb->iterpool);
1391251881Speter
1392251881Speter  /* Get the revision properties. */
1393251881Speter  SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
1394251881Speter                                   path_rev->revnum, sb->iterpool));
1395251881Speter
1396251881Speter  /* Open the revision root. */
1397251881Speter  SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1398251881Speter                               sb->iterpool));
1399251881Speter
1400251881Speter  /* Get the file's properties for this revision and compute the diffs. */
1401251881Speter  SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1402251881Speter                                   sb->iterpool));
1403251881Speter  SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1404251881Speter                         sb->iterpool));
1405251881Speter
1406251881Speter  /* Check if the contents changed. */
1407251881Speter  /* Special case: In the first revision, we always provide a delta. */
1408251881Speter  if (sb->last_root)
1409251881Speter    SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
1410251881Speter                                    sb->last_path, root, path_rev->path,
1411251881Speter                                    sb->iterpool));
1412251881Speter  else
1413251881Speter    contents_changed = TRUE;
1414251881Speter
1415251881Speter  /* We have all we need, give to the handler. */
1416251881Speter  SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1417251881Speter                  rev_props, path_rev->merged,
1418251881Speter                  contents_changed ? &delta_handler : NULL,
1419251881Speter                  contents_changed ? &delta_baton : NULL,
1420251881Speter                  prop_diffs, sb->iterpool));
1421251881Speter
1422251881Speter  /* Compute and send delta if client asked for it.
1423251881Speter     Note that this was initialized to NULL, so if !contents_changed,
1424251881Speter     no deltas will be computed. */
1425251881Speter  if (delta_handler)
1426251881Speter    {
1427251881Speter      /* Get the content delta. */
1428251881Speter      SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1429251881Speter                                           sb->last_root, sb->last_path,
1430251881Speter                                           root, path_rev->path,
1431251881Speter                                           sb->iterpool));
1432251881Speter      /* And send. */
1433251881Speter      SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1434251881Speter                                        delta_handler, delta_baton,
1435251881Speter                                        sb->iterpool));
1436251881Speter    }
1437251881Speter
1438251881Speter  /* Remember root, path and props for next iteration. */
1439251881Speter  sb->last_root = root;
1440251881Speter  sb->last_path = path_rev->path;
1441251881Speter  sb->last_props = props;
1442251881Speter
1443251881Speter  /* Swap the pools. */
1444251881Speter  tmp_pool = sb->iterpool;
1445251881Speter  sb->iterpool = sb->last_pool;
1446251881Speter  sb->last_pool = tmp_pool;
1447251881Speter
1448251881Speter  return SVN_NO_ERROR;
1449251881Speter}
1450251881Speter
1451251881Speter/* Similar to svn_repos_get_file_revs2() but returns paths while walking
1452251881Speter   history instead of after collecting all history.
1453251881Speter
1454251881Speter   This allows implementing clients to immediately start processing and
1455251881Speter   stop when they got the information they need. (E.g. all or a specific set
1456251881Speter   of lines were modified) */
1457251881Speterstatic svn_error_t *
1458251881Speterget_file_revs_backwards(svn_repos_t *repos,
1459251881Speter                        const char *path,
1460251881Speter                        svn_revnum_t start,
1461251881Speter                        svn_revnum_t end,
1462251881Speter                        svn_repos_authz_func_t authz_read_func,
1463251881Speter                        void *authz_read_baton,
1464251881Speter                        svn_file_rev_handler_t handler,
1465251881Speter                        void *handler_baton,
1466251881Speter                        apr_pool_t *scratch_pool)
1467251881Speter{
1468251881Speter  apr_pool_t *iterpool, *last_pool;
1469251881Speter  svn_fs_history_t *history;
1470251881Speter  svn_fs_root_t *root;
1471251881Speter  svn_node_kind_t kind;
1472251881Speter  struct send_baton sb;
1473251881Speter
1474251881Speter  /* We switch between two pools while looping and so does the path-rev
1475251881Speter     handler for actually reported revisions. We do this as we
1476251881Speter     need just information from last iteration to be available. */
1477251881Speter
1478251881Speter  iterpool = svn_pool_create(scratch_pool);
1479251881Speter  last_pool = svn_pool_create(scratch_pool);
1480251881Speter  sb.iterpool = svn_pool_create(scratch_pool);
1481251881Speter  sb.last_pool = svn_pool_create(scratch_pool);
1482251881Speter
1483251881Speter  /* We want the first txdelta to be against the empty file. */
1484251881Speter  sb.last_root = NULL;
1485251881Speter  sb.last_path = NULL;
1486251881Speter
1487251881Speter  /* Create an empty hash table for the first property diff. */
1488251881Speter  sb.last_props = apr_hash_make(sb.last_pool);
1489251881Speter
1490251881Speter  /* The path had better be a file in this revision. */
1491251881Speter  SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1492251881Speter  SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1493251881Speter  if (kind != svn_node_file)
1494251881Speter    return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1495251881Speter                             NULL, _("'%s' is not a file in revision %ld"),
1496251881Speter                             path, end);
1497251881Speter
1498251881Speter  /* Open a history object. */
1499251881Speter  SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1500251881Speter  while (1)
1501251881Speter    {
1502251881Speter      struct path_revision *path_rev;
1503251881Speter      svn_revnum_t tmp_revnum;
1504251881Speter      const char *tmp_path;
1505251881Speter
1506251881Speter      svn_pool_clear(iterpool);
1507251881Speter
1508251881Speter      /* Fetch the history object to walk through. */
1509251881Speter      SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1510251881Speter      if (!history)
1511251881Speter        break;
1512251881Speter      SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1513251881Speter                                      history, iterpool));
1514251881Speter
1515251881Speter      /* Check authorization. */
1516251881Speter      if (authz_read_func)
1517251881Speter        {
1518251881Speter          svn_boolean_t readable;
1519251881Speter          svn_fs_root_t *tmp_root;
1520251881Speter
1521251881Speter          SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1522251881Speter                                       iterpool));
1523251881Speter          SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1524251881Speter                                  authz_read_baton, iterpool));
1525251881Speter          if (! readable)
1526251881Speter            break;
1527251881Speter        }
1528251881Speter
1529251881Speter      /* We didn't break, so we must really want this path-rev. */
1530251881Speter      path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1531251881Speter      path_rev->path = tmp_path;
1532251881Speter      path_rev->revnum = tmp_revnum;
1533251881Speter      path_rev->merged = FALSE;
1534251881Speter
1535251881Speter      SVN_ERR(send_path_revision(path_rev, repos, &sb,
1536251881Speter                                 handler, handler_baton));
1537251881Speter
1538251881Speter      if (path_rev->revnum <= start)
1539251881Speter        break;
1540251881Speter
1541251881Speter      /* Swap pools. */
1542251881Speter      {
1543251881Speter        apr_pool_t *tmp_pool = iterpool;
1544251881Speter        iterpool = last_pool;
1545251881Speter        last_pool = tmp_pool;
1546251881Speter      }
1547251881Speter    }
1548251881Speter
1549251881Speter  svn_pool_destroy(iterpool);
1550251881Speter  svn_pool_destroy(last_pool);
1551251881Speter  svn_pool_destroy(sb.last_pool);
1552251881Speter  svn_pool_destroy(sb.iterpool);
1553251881Speter
1554251881Speter  return SVN_NO_ERROR;
1555251881Speter
1556251881Speter}
1557251881Speter
1558251881Speter
1559251881Speter/* We don't yet support sending revisions in reverse order; the caller wait
1560251881Speter * until we've traced back through the entire history, and then accept
1561251881Speter * them from oldest to youngest.  Someday this may change, but in the meantime,
1562251881Speter * the general algorithm is thus:
1563251881Speter *
1564251881Speter *  1) Trace back through the history of an object, adding each revision
1565251881Speter *     found to the MAINLINE_PATH_REVISIONS array, marking any which were
1566251881Speter *     merges.
1567251881Speter *  2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1568251881Speter *     merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1569251881Speter *     DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1570251881Speter *     times.
1571251881Speter *  3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1572251881Speter *     oldest to youngest, interleaving as appropriate.  This is implemented
1573251881Speter *     similar to an insertion sort, but instead of inserting into another
1574251881Speter *     array, we just call the appropriate handler.
1575251881Speter *
1576251881Speter * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1577251881Speter *          this would return an error (path not found) or just the first
1578251881Speter *          revision before end.
1579251881Speter */
1580251881Spetersvn_error_t *
1581251881Spetersvn_repos_get_file_revs2(svn_repos_t *repos,
1582251881Speter                         const char *path,
1583251881Speter                         svn_revnum_t start,
1584251881Speter                         svn_revnum_t end,
1585251881Speter                         svn_boolean_t include_merged_revisions,
1586251881Speter                         svn_repos_authz_func_t authz_read_func,
1587251881Speter                         void *authz_read_baton,
1588251881Speter                         svn_file_rev_handler_t handler,
1589251881Speter                         void *handler_baton,
1590251881Speter                         apr_pool_t *scratch_pool)
1591251881Speter{
1592251881Speter  apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1593251881Speter  apr_hash_t *duplicate_path_revs;
1594251881Speter  struct send_baton sb;
1595251881Speter  int mainline_pos, merged_pos;
1596251881Speter
1597251881Speter  if (end < start)
1598251881Speter    {
1599251881Speter      if (include_merged_revisions)
1600251881Speter        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1601251881Speter
1602251881Speter      return svn_error_trace(
1603251881Speter                      get_file_revs_backwards(repos, path,
1604251881Speter                                              end, start,
1605251881Speter                                              authz_read_func,
1606251881Speter                                              authz_read_baton,
1607251881Speter                                              handler,
1608251881Speter                                              handler_baton,
1609251881Speter                                              scratch_pool));
1610251881Speter    }
1611251881Speter
1612251881Speter  /* We switch between two pools while looping, since we need information from
1613251881Speter     the last iteration to be available. */
1614251881Speter  sb.iterpool = svn_pool_create(scratch_pool);
1615251881Speter  sb.last_pool = svn_pool_create(scratch_pool);
1616251881Speter
1617251881Speter  /* We want the first txdelta to be against the empty file. */
1618251881Speter  sb.last_root = NULL;
1619251881Speter  sb.last_path = NULL;
1620251881Speter
1621251881Speter  /* Create an empty hash table for the first property diff. */
1622251881Speter  sb.last_props = apr_hash_make(sb.last_pool);
1623251881Speter
1624251881Speter
1625251881Speter  /* Get the revisions we are interested in. */
1626251881Speter  duplicate_path_revs = apr_hash_make(scratch_pool);
1627251881Speter  mainline_path_revisions = apr_array_make(scratch_pool, 100,
1628251881Speter                                           sizeof(struct path_revision *));
1629251881Speter  SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1630251881Speter                                     start, end, include_merged_revisions,
1631251881Speter                                     FALSE, duplicate_path_revs,
1632251881Speter                                     authz_read_func, authz_read_baton,
1633251881Speter                                     scratch_pool, sb.iterpool));
1634251881Speter
1635251881Speter  /* If we are including merged revisions, go get those, too. */
1636251881Speter  if (include_merged_revisions)
1637251881Speter    SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1638251881Speter                                  mainline_path_revisions, repos,
1639251881Speter                                  duplicate_path_revs, authz_read_func,
1640251881Speter                                  authz_read_baton,
1641251881Speter                                  scratch_pool, sb.iterpool));
1642251881Speter  else
1643251881Speter    merged_path_revisions = apr_array_make(scratch_pool, 0,
1644251881Speter                                           sizeof(struct path_revision *));
1645251881Speter
1646251881Speter  /* We must have at least one revision to get. */
1647251881Speter  SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1648251881Speter
1649251881Speter  /* Walk through both mainline and merged revisions, and send them in
1650251881Speter     reverse chronological order, interleaving as appropriate. */
1651251881Speter  mainline_pos = mainline_path_revisions->nelts - 1;
1652251881Speter  merged_pos = merged_path_revisions->nelts - 1;
1653251881Speter  while (mainline_pos >= 0 && merged_pos >= 0)
1654251881Speter    {
1655251881Speter      struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1656251881Speter                                                    mainline_pos,
1657251881Speter                                                    struct path_revision *);
1658251881Speter      struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1659251881Speter                                                      merged_pos,
1660251881Speter                                                      struct path_revision *);
1661251881Speter
1662251881Speter      if (main_pr->revnum <= merged_pr->revnum)
1663251881Speter        {
1664251881Speter          SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1665251881Speter                                     handler_baton));
1666251881Speter          mainline_pos -= 1;
1667251881Speter        }
1668251881Speter      else
1669251881Speter        {
1670251881Speter          SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1671251881Speter                                     handler_baton));
1672251881Speter          merged_pos -= 1;
1673251881Speter        }
1674251881Speter    }
1675251881Speter
1676251881Speter  /* Send any remaining revisions from the mainline list. */
1677251881Speter  for (; mainline_pos >= 0; mainline_pos -= 1)
1678251881Speter    {
1679251881Speter      struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1680251881Speter                                                    mainline_pos,
1681251881Speter                                                    struct path_revision *);
1682251881Speter      SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1683251881Speter    }
1684251881Speter
1685251881Speter  /* Ditto for the merged list. */
1686251881Speter  for (; merged_pos >= 0; merged_pos -= 1)
1687251881Speter    {
1688251881Speter      struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1689251881Speter                                                      merged_pos,
1690251881Speter                                                      struct path_revision *);
1691251881Speter      SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1692251881Speter                                 handler_baton));
1693251881Speter    }
1694251881Speter
1695251881Speter  svn_pool_destroy(sb.last_pool);
1696251881Speter  svn_pool_destroy(sb.iterpool);
1697251881Speter
1698251881Speter  return SVN_NO_ERROR;
1699251881Speter}
1700