rev_hunt.c revision 289166
1/* rev_hunt.c --- routines to hunt down particular fs revisions and
2 *                their properties.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25#include <string.h>
26#include "svn_compat.h"
27#include "svn_private_config.h"
28#include "svn_hash.h"
29#include "svn_pools.h"
30#include "svn_error.h"
31#include "svn_error_codes.h"
32#include "svn_fs.h"
33#include "svn_repos.h"
34#include "svn_string.h"
35#include "svn_time.h"
36#include "svn_sorts.h"
37#include "svn_props.h"
38#include "svn_mergeinfo.h"
39#include "repos.h"
40#include "private/svn_fspath.h"
41
42
43/* Note:  this binary search assumes that the datestamp properties on
44   each revision are in chronological order.  That is if revision A >
45   revision B, then A's datestamp is younger then B's datestamp.
46
47   If someone comes along and sets a bogus datestamp, this routine
48   might not work right.
49
50   ### todo:  you know, we *could* have svn_fs_change_rev_prop() do
51   some semantic checking when it's asked to change special reserved
52   svn: properties.  It could prevent such a problem. */
53
54
55/* helper for svn_repos_dated_revision().
56
57   Set *TM to the apr_time_t datestamp on revision REV in FS. */
58static svn_error_t *
59get_time(apr_time_t *tm,
60         svn_fs_t *fs,
61         svn_revnum_t rev,
62         apr_pool_t *pool)
63{
64  svn_string_t *date_str;
65
66  SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
67                               pool));
68  if (! date_str)
69    return svn_error_createf
70      (SVN_ERR_FS_GENERAL, NULL,
71       _("Failed to find time on revision %ld"), rev);
72
73  return svn_time_from_cstring(tm, date_str->data, pool);
74}
75
76
77svn_error_t *
78svn_repos_dated_revision(svn_revnum_t *revision,
79                         svn_repos_t *repos,
80                         apr_time_t tm,
81                         apr_pool_t *pool)
82{
83  svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
84  apr_time_t this_time;
85  svn_fs_t *fs = repos->fs;
86
87  /* Initialize top and bottom values of binary search. */
88  SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
89  rev_bot = 0;
90  rev_top = rev_latest;
91
92  while (rev_bot <= rev_top)
93    {
94      rev_mid = (rev_top + rev_bot) / 2;
95      SVN_ERR(get_time(&this_time, fs, rev_mid, pool));
96
97      if (this_time > tm)/* we've overshot */
98        {
99          apr_time_t previous_time;
100
101          if ((rev_mid - 1) < 0)
102            {
103              *revision = 0;
104              break;
105            }
106
107          /* see if time falls between rev_mid and rev_mid-1: */
108          SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool));
109          if (previous_time <= tm)
110            {
111              *revision = rev_mid - 1;
112              break;
113            }
114
115          rev_top = rev_mid - 1;
116        }
117
118      else if (this_time < tm) /* we've undershot */
119        {
120          apr_time_t next_time;
121
122          if ((rev_mid + 1) > rev_latest)
123            {
124              *revision = rev_latest;
125              break;
126            }
127
128          /* see if time falls between rev_mid and rev_mid+1: */
129          SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
130          if (next_time > tm)
131            {
132              *revision = rev_mid;
133              break;
134            }
135
136          rev_bot = rev_mid + 1;
137        }
138
139      else
140        {
141          *revision = rev_mid;  /* exact match! */
142          break;
143        }
144    }
145
146  return SVN_NO_ERROR;
147}
148
149
150svn_error_t *
151svn_repos_get_committed_info(svn_revnum_t *committed_rev,
152                             const char **committed_date,
153                             const char **last_author,
154                             svn_fs_root_t *root,
155                             const char *path,
156                             apr_pool_t *pool)
157{
158  apr_hash_t *revprops;
159
160  svn_fs_t *fs = svn_fs_root_fs(root);
161
162  /* ### It might be simpler just to declare that revision
163     properties have char * (i.e., UTF-8) values, not arbitrary
164     binary values, hmmm. */
165  svn_string_t *committed_date_s, *last_author_s;
166
167  /* Get the CR field out of the node's skel. */
168  SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
169
170  /* Get the revision properties of this revision. */
171  SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
172
173  /* Extract date and author from these revprops. */
174  committed_date_s = apr_hash_get(revprops,
175                                  SVN_PROP_REVISION_DATE,
176                                  sizeof(SVN_PROP_REVISION_DATE)-1);
177  last_author_s = apr_hash_get(revprops,
178                               SVN_PROP_REVISION_AUTHOR,
179                               sizeof(SVN_PROP_REVISION_AUTHOR)-1);
180
181  *committed_date = committed_date_s ? committed_date_s->data : NULL;
182  *last_author = last_author_s ? last_author_s->data : NULL;
183
184  return SVN_NO_ERROR;
185}
186
187svn_error_t *
188svn_repos_history2(svn_fs_t *fs,
189                   const char *path,
190                   svn_repos_history_func_t history_func,
191                   void *history_baton,
192                   svn_repos_authz_func_t authz_read_func,
193                   void *authz_read_baton,
194                   svn_revnum_t start,
195                   svn_revnum_t end,
196                   svn_boolean_t cross_copies,
197                   apr_pool_t *pool)
198{
199  svn_fs_history_t *history;
200  apr_pool_t *oldpool = svn_pool_create(pool);
201  apr_pool_t *newpool = svn_pool_create(pool);
202  const char *history_path;
203  svn_revnum_t history_rev;
204  svn_fs_root_t *root;
205
206  /* Validate the revisions. */
207  if (! SVN_IS_VALID_REVNUM(start))
208    return svn_error_createf
209      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
210       _("Invalid start revision %ld"), start);
211  if (! SVN_IS_VALID_REVNUM(end))
212    return svn_error_createf
213      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
214       _("Invalid end revision %ld"), end);
215
216  /* Ensure that the input is ordered. */
217  if (start > end)
218    {
219      svn_revnum_t tmprev = start;
220      start = end;
221      end = tmprev;
222    }
223
224  /* Get a revision root for END, and an initial HISTORY baton.  */
225  SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
226
227  if (authz_read_func)
228    {
229      svn_boolean_t readable;
230      SVN_ERR(authz_read_func(&readable, root, path,
231                              authz_read_baton, pool));
232      if (! readable)
233        return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
234    }
235
236  SVN_ERR(svn_fs_node_history(&history, root, path, oldpool));
237
238  /* Now, we loop over the history items, calling svn_fs_history_prev(). */
239  do
240    {
241      /* Note that we have to do some crazy pool work here.  We can't
242         get rid of the old history until we use it to get the new, so
243         we alternate back and forth between our subpools.  */
244      apr_pool_t *tmppool;
245      svn_error_t *err;
246
247      SVN_ERR(svn_fs_history_prev(&history, history, cross_copies, newpool));
248
249      /* Only continue if there is further history to deal with. */
250      if (! history)
251        break;
252
253      /* Fetch the location information for this history step. */
254      SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
255                                      history, newpool));
256
257      /* If this history item predates our START revision, quit
258         here. */
259      if (history_rev < start)
260        break;
261
262      /* Is the history item readable?  If not, quit. */
263      if (authz_read_func)
264        {
265          svn_boolean_t readable;
266          svn_fs_root_t *history_root;
267          SVN_ERR(svn_fs_revision_root(&history_root, fs,
268                                       history_rev, newpool));
269          SVN_ERR(authz_read_func(&readable, history_root, history_path,
270                                  authz_read_baton, newpool));
271          if (! readable)
272            break;
273        }
274
275      /* Call the user-provided callback function. */
276      err = history_func(history_baton, history_path, history_rev, newpool);
277      if (err)
278        {
279          if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
280            {
281              svn_error_clear(err);
282              goto cleanup;
283            }
284          else
285            {
286              return svn_error_trace(err);
287            }
288        }
289
290      /* We're done with the old history item, so we can clear its
291         pool, and then toggle our notion of "the old pool". */
292      svn_pool_clear(oldpool);
293      tmppool = oldpool;
294      oldpool = newpool;
295      newpool = tmppool;
296    }
297  while (history); /* shouldn't hit this */
298
299 cleanup:
300  svn_pool_destroy(oldpool);
301  svn_pool_destroy(newpool);
302  return SVN_NO_ERROR;
303}
304
305
306svn_error_t *
307svn_repos_deleted_rev(svn_fs_t *fs,
308                      const char *path,
309                      svn_revnum_t start,
310                      svn_revnum_t end,
311                      svn_revnum_t *deleted,
312                      apr_pool_t *pool)
313{
314  apr_pool_t *subpool;
315  svn_fs_root_t *root, *copy_root;
316  const char *copy_path;
317  svn_revnum_t mid_rev;
318  const svn_fs_id_t *start_node_id, *curr_node_id;
319  svn_error_t *err;
320
321  /* Validate the revision range. */
322  if (! SVN_IS_VALID_REVNUM(start))
323    return svn_error_createf
324      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
325       _("Invalid start revision %ld"), start);
326  if (! SVN_IS_VALID_REVNUM(end))
327    return svn_error_createf
328      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
329       _("Invalid end revision %ld"), end);
330
331  /* Ensure that the input is ordered. */
332  if (start > end)
333    {
334      svn_revnum_t tmprev = start;
335      start = end;
336      end = tmprev;
337    }
338
339  /* Ensure path exists in fs at start revision. */
340  SVN_ERR(svn_fs_revision_root(&root, fs, start, pool));
341  err = svn_fs_node_id(&start_node_id, root, path, pool);
342  if (err)
343    {
344      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
345        {
346          /* Path must exist in fs at start rev. */
347          *deleted = SVN_INVALID_REVNUM;
348          svn_error_clear(err);
349          return SVN_NO_ERROR;
350        }
351      return svn_error_trace(err);
352    }
353
354  /* Ensure path was deleted at or before end revision. */
355  SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
356  err = svn_fs_node_id(&curr_node_id, root, path, pool);
357  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
358    {
359      svn_error_clear(err);
360    }
361  else if (err)
362    {
363      return svn_error_trace(err);
364    }
365  else
366    {
367      /* path exists in the end node and the end node is equivalent
368         or otherwise equivalent to the start node.  This can mean
369         a few things:
370
371           1) The end node *is* simply the start node, uncopied
372              and unmodified in the start to end range.
373
374           2) The start node was modified, but never copied.
375
376           3) The start node was copied, but this copy occurred at
377              start or some rev *previous* to start, this is
378              effectively the same situation as 1 if the node was
379              never modified or 2 if it was.
380
381         In the first three cases the path was not deleted in
382         the specified range and we are done, but in the following
383         cases the start node must have been deleted at least once:
384
385           4) The start node was deleted and replaced by a copy of
386              itself at some rev between start and end.  This copy
387              may itself have been replaced with copies of itself.
388
389           5) The start node was deleted and replaced by a node which
390              it does not share any history with.
391      */
392      SVN_ERR(svn_fs_node_id(&curr_node_id, root, path, pool));
393      if (svn_fs_compare_ids(start_node_id, curr_node_id) != -1)
394        {
395          SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
396                                      path, pool));
397          if (!copy_root ||
398              (svn_fs_revision_root_revision(copy_root) <= start))
399            {
400              /* Case 1,2 or 3, nothing more to do. */
401              *deleted = SVN_INVALID_REVNUM;
402              return SVN_NO_ERROR;
403            }
404        }
405    }
406
407  /* If we get here we know that path exists in rev start and was deleted
408     at least once before rev end.  To find the revision path was first
409     deleted we use a binary search.  The rules for the determining if
410     the deletion comes before or after a given median revision are
411     described by this matrix:
412
413                   |             Most recent copy event that
414                   |               caused mid node to exist.
415                   |-----------------------------------------------------
416     Compare path  |                   |                |               |
417     at start and  |   Copied at       |  Copied at     | Never copied  |
418     mid nodes.    |   rev > start     |  rev <= start  |               |
419                   |                   |                |               |
420     -------------------------------------------------------------------|
421     Mid node is   |  A) Start node    |                                |
422     equivalent to |     replaced with |  E) Mid node == start node,    |
423     start node    |     an unmodified |     look HIGHER.               |
424                   |     copy of       |                                |
425                   |     itself,       |                                |
426                   |     look LOWER.   |                                |
427     -------------------------------------------------------------------|
428     Mid node is   |  B) Start node    |                                |
429     otherwise     |     replaced with |  F) Mid node is a modified     |
430     related to    |     a modified    |     version of start node,     |
431     start node    |     copy of       |     look HIGHER.               |
432                   |     itself,       |                                |
433                   |     look LOWER.   |                                |
434     -------------------------------------------------------------------|
435     Mid node is   |                                                    |
436     unrelated to  |  C) Start node replaced with unrelated mid node,   |
437     start node    |     look LOWER.                                    |
438                   |                                                    |
439     -------------------------------------------------------------------|
440     Path doesn't  |                                                    |
441     exist at mid  |  D) Start node deleted before mid node,            |
442     node          |     look LOWER                                     |
443                   |                                                    |
444     --------------------------------------------------------------------
445  */
446
447  mid_rev = (start + end) / 2;
448  subpool = svn_pool_create(pool);
449
450  while (1)
451    {
452      svn_pool_clear(subpool);
453
454      /* Get revision root and node id for mid_rev at that revision. */
455      SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, subpool));
456      err = svn_fs_node_id(&curr_node_id, root, path, subpool);
457
458      if (err)
459        {
460          if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
461            {
462              /* Case D: Look lower in the range. */
463              svn_error_clear(err);
464              end = mid_rev;
465              mid_rev = (start + mid_rev) / 2;
466            }
467          else
468            return svn_error_trace(err);
469        }
470      else
471        {
472          /* Determine the relationship between the start node
473             and the current node. */
474          int cmp = svn_fs_compare_ids(start_node_id, curr_node_id);
475          SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root,
476                                      path, subpool));
477          if (cmp == -1 ||
478              (copy_root &&
479               (svn_fs_revision_root_revision(copy_root) > start)))
480            {
481              /* Cases A, B, C: Look at lower revs. */
482              end = mid_rev;
483              mid_rev = (start + mid_rev) / 2;
484            }
485          else if (end - mid_rev == 1)
486            {
487              /* Found the node path was deleted. */
488              *deleted = end;
489              break;
490            }
491          else
492            {
493              /* Cases E, F: Look at higher revs. */
494              start = mid_rev;
495              mid_rev = (start + end) / 2;
496            }
497        }
498    }
499
500  svn_pool_destroy(subpool);
501  return SVN_NO_ERROR;
502}
503
504
505/* Helper func:  return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
506   unreadable. */
507static svn_error_t *
508check_readability(svn_fs_root_t *root,
509                  const char *path,
510                  svn_repos_authz_func_t authz_read_func,
511                  void *authz_read_baton,
512                  apr_pool_t *pool)
513{
514  svn_boolean_t readable;
515  SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
516  if (! readable)
517    return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
518                            _("Unreadable path encountered; access denied"));
519  return SVN_NO_ERROR;
520}
521
522
523/* The purpose of this function is to discover if fs_path@future_rev
524 * is derived from fs_path@peg_rev.  The return is placed in *is_ancestor. */
525
526static svn_error_t *
527check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
528                           svn_fs_t *fs,
529                           const char *fs_path,
530                           svn_revnum_t peg_revision,
531                           svn_revnum_t future_revision,
532                           apr_pool_t *pool)
533{
534  svn_fs_root_t *root;
535  svn_fs_history_t *history;
536  const char *path = NULL;
537  svn_revnum_t revision;
538  apr_pool_t *lastpool, *currpool;
539
540  lastpool = svn_pool_create(pool);
541  currpool = svn_pool_create(pool);
542
543  SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
544
545  SVN_ERR(svn_fs_node_history(&history, root, fs_path, lastpool));
546
547  /* Since paths that are different according to strcmp may still be
548     equivalent (due to number of consecutive slashes and the fact that
549     "" is the same as "/"), we get the "canonical" path in the first
550     iteration below so that the comparison after the loop will work
551     correctly. */
552  fs_path = NULL;
553
554  while (1)
555    {
556      apr_pool_t *tmppool;
557
558      SVN_ERR(svn_fs_history_prev(&history, history, TRUE, currpool));
559
560      if (!history)
561        break;
562
563      SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
564
565      if (!fs_path)
566        fs_path = apr_pstrdup(pool, path);
567
568      if (revision <= peg_revision)
569        break;
570
571      /* Clear old pool and flip. */
572      svn_pool_clear(lastpool);
573      tmppool = lastpool;
574      lastpool = currpool;
575      currpool = tmppool;
576    }
577
578  /* We must have had at least one iteration above where we
579     reassigned fs_path. Else, the path wouldn't have existed at
580     future_revision and svn_fs_history would have thrown. */
581  SVN_ERR_ASSERT(fs_path != NULL);
582
583  *is_ancestor = (history && strcmp(path, fs_path) == 0);
584
585  return SVN_NO_ERROR;
586}
587
588
589svn_error_t *
590svn_repos__prev_location(svn_revnum_t *appeared_rev,
591                         const char **prev_path,
592                         svn_revnum_t *prev_rev,
593                         svn_fs_t *fs,
594                         svn_revnum_t revision,
595                         const char *path,
596                         apr_pool_t *pool)
597{
598  svn_fs_root_t *root, *copy_root;
599  const char *copy_path, *copy_src_path, *remainder;
600  svn_revnum_t copy_src_rev;
601
602  /* Initialize return variables. */
603  if (appeared_rev)
604    *appeared_rev = SVN_INVALID_REVNUM;
605  if (prev_rev)
606    *prev_rev = SVN_INVALID_REVNUM;
607  if (prev_path)
608    *prev_path = NULL;
609
610  /* Ask about the most recent copy which affected PATH@REVISION.  If
611     there was no such copy, we're done.  */
612  SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
613  SVN_ERR(svn_fs_closest_copy(&copy_root, &copy_path, root, path, pool));
614  if (! copy_root)
615    return SVN_NO_ERROR;
616
617  /* Ultimately, it's not the path of the closest copy's source that
618     we care about -- it's our own path's location in the copy source
619     revision.  So we'll tack the relative path that expresses the
620     difference between the copy destination and our path in the copy
621     revision onto the copy source path to determine this information.
622
623     In other words, if our path is "/branches/my-branch/foo/bar", and
624     we know that the closest relevant copy was a copy of "/trunk" to
625     "/branches/my-branch", then that relative path under the copy
626     destination is "/foo/bar".  Tacking that onto the copy source
627     path tells us that our path was located at "/trunk/foo/bar"
628     before the copy.
629  */
630  SVN_ERR(svn_fs_copied_from(&copy_src_rev, &copy_src_path,
631                             copy_root, copy_path, pool));
632  remainder = svn_fspath__skip_ancestor(copy_path, path);
633  if (prev_path)
634    *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
635  if (appeared_rev)
636    *appeared_rev = svn_fs_revision_root_revision(copy_root);
637  if (prev_rev)
638    *prev_rev = copy_src_rev;
639  return SVN_NO_ERROR;
640}
641
642
643svn_error_t *
644svn_repos_trace_node_locations(svn_fs_t *fs,
645                               apr_hash_t **locations,
646                               const char *fs_path,
647                               svn_revnum_t peg_revision,
648                               const apr_array_header_t *location_revisions_orig,
649                               svn_repos_authz_func_t authz_read_func,
650                               void *authz_read_baton,
651                               apr_pool_t *pool)
652{
653  apr_array_header_t *location_revisions;
654  svn_revnum_t *revision_ptr, *revision_ptr_end;
655  svn_fs_root_t *root;
656  const char *path;
657  svn_revnum_t revision;
658  svn_boolean_t is_ancestor;
659  apr_pool_t *lastpool, *currpool;
660  const svn_fs_id_t *id;
661
662  SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
663
664  /* Ensure that FS_PATH is absolute, because our path-math below will
665     depend on that being the case.  */
666  if (*fs_path != '/')
667    fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);
668
669  /* Another sanity check. */
670  if (authz_read_func)
671    {
672      svn_fs_root_t *peg_root;
673      SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
674      SVN_ERR(check_readability(peg_root, fs_path,
675                                authz_read_func, authz_read_baton, pool));
676    }
677
678  *locations = apr_hash_make(pool);
679
680  /* We flip between two pools in the second loop below. */
681  lastpool = svn_pool_create(pool);
682  currpool = svn_pool_create(pool);
683
684  /* First - let's sort the array of the revisions from the greatest revision
685   * downward, so it will be easier to search on. */
686  location_revisions = apr_array_copy(pool, location_revisions_orig);
687  qsort(location_revisions->elts, location_revisions->nelts,
688        sizeof(*revision_ptr), svn_sort_compare_revisions);
689
690  revision_ptr = (svn_revnum_t *)location_revisions->elts;
691  revision_ptr_end = revision_ptr + location_revisions->nelts;
692
693  /* Ignore revisions R that are younger than the peg_revisions where
694     path@peg_revision is not an ancestor of path@R. */
695  is_ancestor = FALSE;
696  while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
697    {
698      svn_pool_clear(currpool);
699      SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path,
700                                         peg_revision, *revision_ptr,
701                                         currpool));
702      if (is_ancestor)
703        break;
704      ++revision_ptr;
705    }
706
707  revision = is_ancestor ? *revision_ptr : peg_revision;
708  path = fs_path;
709  if (authz_read_func)
710    {
711      SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
712      SVN_ERR(check_readability(root, fs_path, authz_read_func,
713                                authz_read_baton, pool));
714    }
715
716  while (revision_ptr < revision_ptr_end)
717    {
718      apr_pool_t *tmppool;
719      svn_revnum_t appeared_rev, prev_rev;
720      const char *prev_path;
721
722      /* Find the target of the innermost copy relevant to path@revision.
723         The copy may be of path itself, or of a parent directory. */
724      SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
725                                       fs, revision, path, currpool));
726      if (! prev_path)
727        break;
728
729      /* Assign the current path to all younger revisions until we reach
730         the copy target rev. */
731      while ((revision_ptr < revision_ptr_end)
732             && (*revision_ptr >= appeared_rev))
733        {
734          /* *revision_ptr is allocated out of pool, so we can point
735             to in the hash table. */
736          apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
737                       apr_pstrdup(pool, path));
738          revision_ptr++;
739        }
740
741      /* Ignore all revs between the copy target rev and the copy
742         source rev (non-inclusive). */
743      while ((revision_ptr < revision_ptr_end)
744             && (*revision_ptr > prev_rev))
745        revision_ptr++;
746
747      /* State update. */
748      path = prev_path;
749      revision = prev_rev;
750
751      if (authz_read_func)
752        {
753          svn_boolean_t readable;
754          SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
755          SVN_ERR(authz_read_func(&readable, root, path,
756                                  authz_read_baton, currpool));
757          if (!readable)
758            {
759              svn_pool_destroy(lastpool);
760              svn_pool_destroy(currpool);
761              return SVN_NO_ERROR;
762            }
763        }
764
765      /* Clear last pool and switch. */
766      svn_pool_clear(lastpool);
767      tmppool = lastpool;
768      lastpool = currpool;
769      currpool = tmppool;
770    }
771
772  /* There are no copies relevant to path@revision.  So any remaining
773     revisions either predate the creation of path@revision or have
774     the node existing at the same path.  We will look up path@lrev
775     for each remaining location-revision and make sure it is related
776     to path@revision. */
777  SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
778  SVN_ERR(svn_fs_node_id(&id, root, path, pool));
779  while (revision_ptr < revision_ptr_end)
780    {
781      svn_node_kind_t kind;
782      const svn_fs_id_t *lrev_id;
783
784      svn_pool_clear(currpool);
785      SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool));
786      SVN_ERR(svn_fs_check_path(&kind, root, path, currpool));
787      if (kind == svn_node_none)
788        break;
789      SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
790      if (! svn_fs_check_related(id, lrev_id))
791        break;
792
793      /* The node exists at the same path; record that and advance. */
794      apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
795                   apr_pstrdup(pool, path));
796      revision_ptr++;
797    }
798
799  /* Ignore any remaining location-revisions; they predate the
800     creation of path@revision. */
801
802  svn_pool_destroy(lastpool);
803  svn_pool_destroy(currpool);
804
805  return SVN_NO_ERROR;
806}
807
808
809/* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
810   its revision range fits between END_REV and START_REV, possibly
811   cropping the range so that it fits *entirely* in that range. */
812static svn_error_t *
813maybe_crop_and_send_segment(svn_location_segment_t *segment,
814                            svn_revnum_t start_rev,
815                            svn_revnum_t end_rev,
816                            svn_location_segment_receiver_t receiver,
817                            void *receiver_baton,
818                            apr_pool_t *pool)
819{
820  /* We only want to transmit this segment if some portion of it
821     is between our END_REV and START_REV. */
822  if (! ((segment->range_start > start_rev)
823         || (segment->range_end < end_rev)))
824    {
825      /* Correct our segment range when the range straddles one of
826         our requested revision boundaries. */
827      if (segment->range_start < end_rev)
828        segment->range_start = end_rev;
829      if (segment->range_end > start_rev)
830        segment->range_end = start_rev;
831      SVN_ERR(receiver(segment, receiver_baton, pool));
832    }
833  return SVN_NO_ERROR;
834}
835
836
837svn_error_t *
838svn_repos_node_location_segments(svn_repos_t *repos,
839                                 const char *path,
840                                 svn_revnum_t peg_revision,
841                                 svn_revnum_t start_rev,
842                                 svn_revnum_t end_rev,
843                                 svn_location_segment_receiver_t receiver,
844                                 void *receiver_baton,
845                                 svn_repos_authz_func_t authz_read_func,
846                                 void *authz_read_baton,
847                                 apr_pool_t *pool)
848{
849  svn_fs_t *fs = svn_repos_fs(repos);
850  svn_stringbuf_t *current_path;
851  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
852  apr_pool_t *subpool;
853
854  /* No PEG_REVISION?  We'll use HEAD. */
855  if (! SVN_IS_VALID_REVNUM(peg_revision))
856    {
857      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
858      peg_revision = youngest_rev;
859    }
860
861  /* No START_REV?  We'll use HEAD (which we may have already fetched). */
862  if (! SVN_IS_VALID_REVNUM(start_rev))
863    {
864      if (SVN_IS_VALID_REVNUM(youngest_rev))
865        start_rev = youngest_rev;
866      else
867        SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
868    }
869
870  /* No END_REV?  We'll use 0. */
871  end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
872
873  /* Are the revision properly ordered?  They better be -- the API
874     demands it. */
875  SVN_ERR_ASSERT(end_rev <= start_rev);
876  SVN_ERR_ASSERT(start_rev <= peg_revision);
877
878  /* Ensure that PATH is absolute, because our path-math will depend
879     on that being the case.  */
880  if (*path != '/')
881    path = apr_pstrcat(pool, "/", path, (char *)NULL);
882
883  /* Auth check. */
884  if (authz_read_func)
885    {
886      svn_fs_root_t *peg_root;
887      SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
888      SVN_ERR(check_readability(peg_root, path,
889                                authz_read_func, authz_read_baton, pool));
890    }
891
892  /* Okay, let's get searching! */
893  subpool = svn_pool_create(pool);
894  current_rev = peg_revision;
895  current_path = svn_stringbuf_create(path, pool);
896  while (current_rev >= end_rev)
897    {
898      svn_revnum_t appeared_rev, prev_rev;
899      const char *cur_path, *prev_path;
900      svn_location_segment_t *segment;
901
902      svn_pool_clear(subpool);
903
904      cur_path = apr_pstrmemdup(subpool, current_path->data,
905                                current_path->len);
906      segment = apr_pcalloc(subpool, sizeof(*segment));
907      segment->range_end = current_rev;
908      segment->range_start = end_rev;
909      /* segment path should be absolute without leading '/'. */
910      segment->path = cur_path + 1;
911
912      SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
913                                       fs, current_rev, cur_path, subpool));
914
915      /* If there are no previous locations for this thing (meaning,
916         it originated at the current path), then we simply need to
917         find its revision of origin to populate our final segment.
918         Otherwise, the APPEARED_REV is the start of current segment's
919         range. */
920      if (! prev_path)
921        {
922          svn_fs_root_t *revroot;
923          SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
924          SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
925                                         cur_path, subpool));
926          if (segment->range_start < end_rev)
927            segment->range_start = end_rev;
928          current_rev = SVN_INVALID_REVNUM;
929        }
930      else
931        {
932          segment->range_start = appeared_rev;
933          svn_stringbuf_set(current_path, prev_path);
934          current_rev = prev_rev;
935        }
936
937      /* Report our segment, providing it passes authz muster. */
938      if (authz_read_func)
939        {
940          svn_boolean_t readable;
941          svn_fs_root_t *cur_rev_root;
942
943          /* authz_read_func requires path to have a leading slash. */
944          const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
945                                             (char *)NULL);
946
947          SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
948                                       segment->range_end, subpool));
949          SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
950                                  authz_read_baton, subpool));
951          if (! readable)
952            return SVN_NO_ERROR;
953        }
954
955      /* Transmit the segment (if it's within the scope of our concern). */
956      SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
957                                          receiver, receiver_baton, subpool));
958
959      /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
960         (and didn't ever reach END_REV).  */
961      if (! SVN_IS_VALID_REVNUM(current_rev))
962        break;
963
964      /* If there's a gap in the history, we need to report as much
965         (if the gap is within the scope of our concern). */
966      if (segment->range_start - current_rev > 1)
967        {
968          svn_location_segment_t *gap_segment;
969          gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
970          gap_segment->range_end = segment->range_start - 1;
971          gap_segment->range_start = current_rev + 1;
972          gap_segment->path = NULL;
973          SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
974                                              receiver, receiver_baton,
975                                              subpool));
976        }
977    }
978  svn_pool_destroy(subpool);
979  return SVN_NO_ERROR;
980}
981
982/* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
983static svn_error_t *
984get_path_mergeinfo(apr_hash_t **mergeinfo,
985                   svn_fs_t *fs,
986                   const char *path,
987                   svn_revnum_t revnum,
988                   apr_pool_t *result_pool,
989                   apr_pool_t *scratch_pool)
990{
991  svn_mergeinfo_catalog_t tmp_catalog;
992  svn_fs_root_t *root;
993  apr_array_header_t *paths = apr_array_make(scratch_pool, 1,
994                                             sizeof(const char *));
995
996  APR_ARRAY_PUSH(paths, const char *) = path;
997
998  SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool));
999  /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1000     because we will filter out unreadable revisions in
1001     find_interesting_revision(), above */
1002  SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
1003                                svn_mergeinfo_inherited, FALSE, TRUE,
1004                                result_pool, scratch_pool));
1005
1006  *mergeinfo = svn_hash_gets(tmp_catalog, path);
1007  if (!*mergeinfo)
1008    *mergeinfo = apr_hash_make(result_pool);
1009
1010  return SVN_NO_ERROR;
1011}
1012
1013static APR_INLINE svn_boolean_t
1014is_path_in_hash(apr_hash_t *duplicate_path_revs,
1015                const char *path,
1016                svn_revnum_t revision,
1017                apr_pool_t *pool)
1018{
1019  const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
1020  void *ptr;
1021
1022  ptr = svn_hash_gets(duplicate_path_revs, key);
1023  return ptr != NULL;
1024}
1025
1026struct path_revision
1027{
1028  svn_revnum_t revnum;
1029  const char *path;
1030
1031  /* Does this path_rev have merges to also be included?  */
1032  apr_hash_t *merged_mergeinfo;
1033
1034  /* Is this a merged revision? */
1035  svn_boolean_t merged;
1036};
1037
1038/* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM.  Store
1039   the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL.  The
1040   returned *MERGED_MERGEINFO will be NULL if there are no changes. */
1041static svn_error_t *
1042get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1043                     svn_repos_t *repos,
1044                     struct path_revision *old_path_rev,
1045                     apr_pool_t *result_pool,
1046                     apr_pool_t *scratch_pool)
1047{
1048  apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1049  svn_error_t *err;
1050  svn_fs_root_t *root;
1051  apr_hash_t *changed_paths;
1052  const char *path = old_path_rev->path;
1053
1054  /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1055     if there is a property change. */
1056  SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1057                               scratch_pool));
1058  SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
1059  while (1)
1060    {
1061      svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
1062      if (changed_path && changed_path->prop_mod)
1063        break;
1064      if (svn_fspath__is_root(path, strlen(path)))
1065        {
1066          *merged_mergeinfo = NULL;
1067          return SVN_NO_ERROR;
1068        }
1069      path = svn_fspath__dirname(path, scratch_pool);
1070    }
1071
1072  /* First, find the mergeinfo difference for old_path_rev->revnum, and
1073     old_path_rev->revnum - 1. */
1074  err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path,
1075                           old_path_rev->revnum, scratch_pool,
1076                           scratch_pool);
1077  if (err)
1078    {
1079      if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1080        {
1081          /* Issue #3896: If invalid mergeinfo is encountered the
1082             best we can do is ignore it and act is if there are
1083             no mergeinfo differences. */
1084          svn_error_clear(err);
1085          *merged_mergeinfo = NULL;
1086          return SVN_NO_ERROR;
1087        }
1088      else
1089        {
1090          return svn_error_trace(err);
1091        }
1092    }
1093
1094  err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path,
1095                           old_path_rev->revnum - 1, scratch_pool,
1096                           scratch_pool);
1097  if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1098              || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1099    {
1100      /* If the path doesn't exist in the previous revision or it does exist
1101         but has invalid mergeinfo (Issue #3896), assume no merges. */
1102      svn_error_clear(err);
1103      *merged_mergeinfo = NULL;
1104      return SVN_NO_ERROR;
1105    }
1106  else
1107    SVN_ERR(err);
1108
1109  /* Then calculate and merge the differences. */
1110  SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1111                              curr_mergeinfo, FALSE, result_pool,
1112                              scratch_pool));
1113  SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1114
1115  /* Store the result. */
1116  if (apr_hash_count(changed))
1117    *merged_mergeinfo = changed;
1118  else
1119    *merged_mergeinfo = NULL;
1120
1121  return SVN_NO_ERROR;
1122}
1123
1124static svn_error_t *
1125find_interesting_revisions(apr_array_header_t *path_revisions,
1126                           svn_repos_t *repos,
1127                           const char *path,
1128                           svn_revnum_t start,
1129                           svn_revnum_t end,
1130                           svn_boolean_t include_merged_revisions,
1131                           svn_boolean_t mark_as_merged,
1132                           apr_hash_t *duplicate_path_revs,
1133                           svn_repos_authz_func_t authz_read_func,
1134                           void *authz_read_baton,
1135                           apr_pool_t *result_pool,
1136                           apr_pool_t *scratch_pool)
1137{
1138  apr_pool_t *iterpool, *last_pool;
1139  svn_fs_history_t *history;
1140  svn_fs_root_t *root;
1141  svn_node_kind_t kind;
1142
1143  /* We switch between two pools while looping, since we need information from
1144     the last iteration to be available. */
1145  iterpool = svn_pool_create(scratch_pool);
1146  last_pool = svn_pool_create(scratch_pool);
1147
1148  /* The path had better be a file in this revision. */
1149  SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1150  SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1151  if (kind != svn_node_file)
1152    return svn_error_createf
1153      (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1154       path, end);
1155
1156  /* Open a history object. */
1157  SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1158  while (1)
1159    {
1160      struct path_revision *path_rev;
1161      svn_revnum_t tmp_revnum;
1162      const char *tmp_path;
1163      apr_pool_t *tmp_pool;
1164
1165      svn_pool_clear(iterpool);
1166
1167      /* Fetch the history object to walk through. */
1168      SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1169      if (!history)
1170        break;
1171      SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1172                                      history, iterpool));
1173
1174      /* Check to see if we already saw this path (and it's ancestors) */
1175      if (include_merged_revisions
1176          && is_path_in_hash(duplicate_path_revs, tmp_path,
1177                             tmp_revnum, iterpool))
1178         break;
1179
1180      /* Check authorization. */
1181      if (authz_read_func)
1182        {
1183          svn_boolean_t readable;
1184          svn_fs_root_t *tmp_root;
1185
1186          SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1187                                       iterpool));
1188          SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1189                                  authz_read_baton, iterpool));
1190          if (! readable)
1191            break;
1192        }
1193
1194      /* We didn't break, so we must really want this path-rev. */
1195      path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1196      path_rev->path = apr_pstrdup(result_pool, tmp_path);
1197      path_rev->revnum = tmp_revnum;
1198      path_rev->merged = mark_as_merged;
1199      APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1200
1201      if (include_merged_revisions)
1202        SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1203                                     path_rev, result_pool, iterpool));
1204      else
1205        path_rev->merged_mergeinfo = NULL;
1206
1207      /* Add the path/rev pair to the hash, so we can filter out future
1208         occurrences of it.  We only care about this if including merged
1209         revisions, 'cause that's the only time we can have duplicates. */
1210      svn_hash_sets(duplicate_path_revs,
1211                    apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1212                                 path_rev->revnum),
1213                    (void *)0xdeadbeef);
1214
1215      if (path_rev->revnum <= start)
1216        break;
1217
1218      /* Swap pools. */
1219      tmp_pool = iterpool;
1220      iterpool = last_pool;
1221      last_pool = tmp_pool;
1222    }
1223
1224  svn_pool_destroy(iterpool);
1225  svn_pool_destroy(last_pool);
1226
1227  return SVN_NO_ERROR;
1228}
1229
1230/* Comparison function to sort path/revisions in increasing order */
1231static int
1232compare_path_revisions(const void *a, const void *b)
1233{
1234  struct path_revision *a_pr = *(struct path_revision *const *)a;
1235  struct path_revision *b_pr = *(struct path_revision *const *)b;
1236
1237  if (a_pr->revnum == b_pr->revnum)
1238    return 0;
1239
1240  return a_pr->revnum < b_pr->revnum ? 1 : -1;
1241}
1242
1243static svn_error_t *
1244find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1245                      svn_revnum_t start,
1246                      const apr_array_header_t *mainline_path_revisions,
1247                      svn_repos_t *repos,
1248                      apr_hash_t *duplicate_path_revs,
1249                      svn_repos_authz_func_t authz_read_func,
1250                      void *authz_read_baton,
1251                      apr_pool_t *result_pool,
1252                      apr_pool_t *scratch_pool)
1253{
1254  const apr_array_header_t *old;
1255  apr_array_header_t *new_merged_path_revs;
1256  apr_pool_t *iterpool, *last_pool;
1257  apr_array_header_t *merged_path_revisions =
1258    apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1259
1260  old = mainline_path_revisions;
1261  iterpool = svn_pool_create(scratch_pool);
1262  last_pool = svn_pool_create(scratch_pool);
1263
1264  do
1265    {
1266      int i;
1267      apr_pool_t *temp_pool;
1268
1269      svn_pool_clear(iterpool);
1270      new_merged_path_revs = apr_array_make(iterpool, 0,
1271                                            sizeof(struct path_revision *));
1272
1273      /* Iterate over OLD, checking for non-empty mergeinfo.  If found, gather
1274         path_revisions for any merged revisions, and store those in NEW. */
1275      for (i = 0; i < old->nelts; i++)
1276        {
1277          apr_pool_t *iterpool2;
1278          apr_hash_index_t *hi;
1279          struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1280                                                       struct path_revision *);
1281          if (!old_pr->merged_mergeinfo)
1282            continue;
1283
1284          iterpool2 = svn_pool_create(iterpool);
1285
1286          /* Determine and trace the merge sources. */
1287          for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1288               hi = apr_hash_next(hi))
1289            {
1290              apr_pool_t *iterpool3;
1291              svn_rangelist_t *rangelist;
1292              const char *path;
1293              int j;
1294
1295              svn_pool_clear(iterpool2);
1296              iterpool3 = svn_pool_create(iterpool2);
1297
1298              apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
1299
1300              for (j = 0; j < rangelist->nelts; j++)
1301                {
1302                  svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1303                                                           svn_merge_range_t *);
1304                  svn_node_kind_t kind;
1305                  svn_fs_root_t *root;
1306
1307                  if (range->end < start)
1308                    continue;
1309
1310                  svn_pool_clear(iterpool3);
1311
1312                  SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1313                                               iterpool3));
1314                  SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1315                  if (kind != svn_node_file)
1316                    continue;
1317
1318                  /* Search and find revisions to add to the NEW list. */
1319                  SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1320                                                     repos, path,
1321                                                     range->start, range->end,
1322                                                     TRUE, TRUE,
1323                                                     duplicate_path_revs,
1324                                                     authz_read_func,
1325                                                     authz_read_baton,
1326                                                     result_pool, iterpool3));
1327                }
1328              svn_pool_destroy(iterpool3);
1329            }
1330          svn_pool_destroy(iterpool2);
1331        }
1332
1333      /* Append the newly found path revisions with the old ones. */
1334      merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1335                                               new_merged_path_revs);
1336
1337      /* Swap data structures */
1338      old = new_merged_path_revs;
1339      temp_pool = last_pool;
1340      last_pool = iterpool;
1341      iterpool = temp_pool;
1342    }
1343  while (new_merged_path_revs->nelts > 0);
1344
1345  /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1346  qsort(merged_path_revisions->elts, merged_path_revisions->nelts,
1347        sizeof(struct path_revision *), compare_path_revisions);
1348
1349  /* Copy to the output array. */
1350  *merged_path_revisions_out = apr_array_copy(result_pool,
1351                                              merged_path_revisions);
1352
1353  svn_pool_destroy(iterpool);
1354  svn_pool_destroy(last_pool);
1355
1356  return SVN_NO_ERROR;
1357}
1358
1359struct send_baton
1360{
1361  apr_pool_t *iterpool;
1362  apr_pool_t *last_pool;
1363  apr_hash_t *last_props;
1364  const char *last_path;
1365  svn_fs_root_t *last_root;
1366};
1367
1368/* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1369   SB. */
1370static svn_error_t *
1371send_path_revision(struct path_revision *path_rev,
1372                   svn_repos_t *repos,
1373                   struct send_baton *sb,
1374                   svn_file_rev_handler_t handler,
1375                   void *handler_baton)
1376{
1377  apr_hash_t *rev_props;
1378  apr_hash_t *props;
1379  apr_array_header_t *prop_diffs;
1380  svn_fs_root_t *root;
1381  svn_txdelta_stream_t *delta_stream;
1382  svn_txdelta_window_handler_t delta_handler = NULL;
1383  void *delta_baton = NULL;
1384  apr_pool_t *tmp_pool;  /* For swapping */
1385  svn_boolean_t contents_changed;
1386
1387  svn_pool_clear(sb->iterpool);
1388
1389  /* Get the revision properties. */
1390  SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
1391                                   path_rev->revnum, sb->iterpool));
1392
1393  /* Open the revision root. */
1394  SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1395                               sb->iterpool));
1396
1397  /* Get the file's properties for this revision and compute the diffs. */
1398  SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1399                                   sb->iterpool));
1400  SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1401                         sb->iterpool));
1402
1403  /* Check if the contents changed. */
1404  /* Special case: In the first revision, we always provide a delta. */
1405  if (sb->last_root)
1406    SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
1407                                    sb->last_path, root, path_rev->path,
1408                                    sb->iterpool));
1409  else
1410    contents_changed = TRUE;
1411
1412  /* We have all we need, give to the handler. */
1413  SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1414                  rev_props, path_rev->merged,
1415                  contents_changed ? &delta_handler : NULL,
1416                  contents_changed ? &delta_baton : NULL,
1417                  prop_diffs, sb->iterpool));
1418
1419  /* Compute and send delta if client asked for it.
1420     Note that this was initialized to NULL, so if !contents_changed,
1421     no deltas will be computed. */
1422  if (delta_handler)
1423    {
1424      /* Get the content delta. */
1425      SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1426                                           sb->last_root, sb->last_path,
1427                                           root, path_rev->path,
1428                                           sb->iterpool));
1429      /* And send. */
1430      SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1431                                        delta_handler, delta_baton,
1432                                        sb->iterpool));
1433    }
1434
1435  /* Remember root, path and props for next iteration. */
1436  sb->last_root = root;
1437  sb->last_path = path_rev->path;
1438  sb->last_props = props;
1439
1440  /* Swap the pools. */
1441  tmp_pool = sb->iterpool;
1442  sb->iterpool = sb->last_pool;
1443  sb->last_pool = tmp_pool;
1444
1445  return SVN_NO_ERROR;
1446}
1447
1448/* Similar to svn_repos_get_file_revs2() but returns paths while walking
1449   history instead of after collecting all history.
1450
1451   This allows implementing clients to immediately start processing and
1452   stop when they got the information they need. (E.g. all or a specific set
1453   of lines were modified) */
1454static svn_error_t *
1455get_file_revs_backwards(svn_repos_t *repos,
1456                        const char *path,
1457                        svn_revnum_t start,
1458                        svn_revnum_t end,
1459                        svn_repos_authz_func_t authz_read_func,
1460                        void *authz_read_baton,
1461                        svn_file_rev_handler_t handler,
1462                        void *handler_baton,
1463                        apr_pool_t *scratch_pool)
1464{
1465  apr_pool_t *iterpool, *last_pool;
1466  svn_fs_history_t *history;
1467  svn_fs_root_t *root;
1468  svn_node_kind_t kind;
1469  struct send_baton sb;
1470
1471  /* We switch between two pools while looping and so does the path-rev
1472     handler for actually reported revisions. We do this as we
1473     need just information from last iteration to be available. */
1474
1475  iterpool = svn_pool_create(scratch_pool);
1476  last_pool = svn_pool_create(scratch_pool);
1477  sb.iterpool = svn_pool_create(scratch_pool);
1478  sb.last_pool = svn_pool_create(scratch_pool);
1479
1480  /* We want the first txdelta to be against the empty file. */
1481  sb.last_root = NULL;
1482  sb.last_path = NULL;
1483
1484  /* Create an empty hash table for the first property diff. */
1485  sb.last_props = apr_hash_make(sb.last_pool);
1486
1487  /* The path had better be a file in this revision. */
1488  SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1489  SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1490  if (kind != svn_node_file)
1491    return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1492                             NULL, _("'%s' is not a file in revision %ld"),
1493                             path, end);
1494
1495  /* Open a history object. */
1496  SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1497  while (1)
1498    {
1499      struct path_revision *path_rev;
1500      svn_revnum_t tmp_revnum;
1501      const char *tmp_path;
1502
1503      svn_pool_clear(iterpool);
1504
1505      /* Fetch the history object to walk through. */
1506      SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1507      if (!history)
1508        break;
1509      SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1510                                      history, iterpool));
1511
1512      /* Check authorization. */
1513      if (authz_read_func)
1514        {
1515          svn_boolean_t readable;
1516          svn_fs_root_t *tmp_root;
1517
1518          SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1519                                       iterpool));
1520          SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1521                                  authz_read_baton, iterpool));
1522          if (! readable)
1523            break;
1524        }
1525
1526      /* We didn't break, so we must really want this path-rev. */
1527      path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1528      path_rev->path = tmp_path;
1529      path_rev->revnum = tmp_revnum;
1530      path_rev->merged = FALSE;
1531
1532      SVN_ERR(send_path_revision(path_rev, repos, &sb,
1533                                 handler, handler_baton));
1534
1535      if (path_rev->revnum <= start)
1536        break;
1537
1538      /* Swap pools. */
1539      {
1540        apr_pool_t *tmp_pool = iterpool;
1541        iterpool = last_pool;
1542        last_pool = tmp_pool;
1543      }
1544    }
1545
1546  svn_pool_destroy(iterpool);
1547  svn_pool_destroy(last_pool);
1548  svn_pool_destroy(sb.last_pool);
1549  svn_pool_destroy(sb.iterpool);
1550
1551  return SVN_NO_ERROR;
1552
1553}
1554
1555
1556/* We don't yet support sending revisions in reverse order; the caller wait
1557 * until we've traced back through the entire history, and then accept
1558 * them from oldest to youngest.  Someday this may change, but in the meantime,
1559 * the general algorithm is thus:
1560 *
1561 *  1) Trace back through the history of an object, adding each revision
1562 *     found to the MAINLINE_PATH_REVISIONS array, marking any which were
1563 *     merges.
1564 *  2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1565 *     merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1566 *     DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1567 *     times.
1568 *  3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1569 *     oldest to youngest, interleaving as appropriate.  This is implemented
1570 *     similar to an insertion sort, but instead of inserting into another
1571 *     array, we just call the appropriate handler.
1572 *
1573 * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1574 *          this would return an error (path not found) or just the first
1575 *          revision before end.
1576 */
1577svn_error_t *
1578svn_repos_get_file_revs2(svn_repos_t *repos,
1579                         const char *path,
1580                         svn_revnum_t start,
1581                         svn_revnum_t end,
1582                         svn_boolean_t include_merged_revisions,
1583                         svn_repos_authz_func_t authz_read_func,
1584                         void *authz_read_baton,
1585                         svn_file_rev_handler_t handler,
1586                         void *handler_baton,
1587                         apr_pool_t *scratch_pool)
1588{
1589  apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1590  apr_hash_t *duplicate_path_revs;
1591  struct send_baton sb;
1592  int mainline_pos, merged_pos;
1593
1594  if (end < start)
1595    {
1596      if (include_merged_revisions)
1597        return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1598
1599      return svn_error_trace(
1600                      get_file_revs_backwards(repos, path,
1601                                              end, start,
1602                                              authz_read_func,
1603                                              authz_read_baton,
1604                                              handler,
1605                                              handler_baton,
1606                                              scratch_pool));
1607    }
1608
1609  /* We switch between two pools while looping, since we need information from
1610     the last iteration to be available. */
1611  sb.iterpool = svn_pool_create(scratch_pool);
1612  sb.last_pool = svn_pool_create(scratch_pool);
1613
1614  /* We want the first txdelta to be against the empty file. */
1615  sb.last_root = NULL;
1616  sb.last_path = NULL;
1617
1618  /* Create an empty hash table for the first property diff. */
1619  sb.last_props = apr_hash_make(sb.last_pool);
1620
1621
1622  /* Get the revisions we are interested in. */
1623  duplicate_path_revs = apr_hash_make(scratch_pool);
1624  mainline_path_revisions = apr_array_make(scratch_pool, 100,
1625                                           sizeof(struct path_revision *));
1626  SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1627                                     start, end, include_merged_revisions,
1628                                     FALSE, duplicate_path_revs,
1629                                     authz_read_func, authz_read_baton,
1630                                     scratch_pool, sb.iterpool));
1631
1632  /* If we are including merged revisions, go get those, too. */
1633  if (include_merged_revisions)
1634    SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1635                                  mainline_path_revisions, repos,
1636                                  duplicate_path_revs, authz_read_func,
1637                                  authz_read_baton,
1638                                  scratch_pool, sb.iterpool));
1639  else
1640    merged_path_revisions = apr_array_make(scratch_pool, 0,
1641                                           sizeof(struct path_revision *));
1642
1643  /* We must have at least one revision to get. */
1644  SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1645
1646  /* Walk through both mainline and merged revisions, and send them in
1647     reverse chronological order, interleaving as appropriate. */
1648  mainline_pos = mainline_path_revisions->nelts - 1;
1649  merged_pos = merged_path_revisions->nelts - 1;
1650  while (mainline_pos >= 0 && merged_pos >= 0)
1651    {
1652      struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1653                                                    mainline_pos,
1654                                                    struct path_revision *);
1655      struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1656                                                      merged_pos,
1657                                                      struct path_revision *);
1658
1659      if (main_pr->revnum <= merged_pr->revnum)
1660        {
1661          SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1662                                     handler_baton));
1663          mainline_pos -= 1;
1664        }
1665      else
1666        {
1667          SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1668                                     handler_baton));
1669          merged_pos -= 1;
1670        }
1671    }
1672
1673  /* Send any remaining revisions from the mainline list. */
1674  for (; mainline_pos >= 0; mainline_pos -= 1)
1675    {
1676      struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1677                                                    mainline_pos,
1678                                                    struct path_revision *);
1679      SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1680    }
1681
1682  /* Ditto for the merged list. */
1683  for (; merged_pos >= 0; merged_pos -= 1)
1684    {
1685      struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1686                                                      merged_pos,
1687                                                      struct path_revision *);
1688      SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1689                                 handler_baton));
1690    }
1691
1692  svn_pool_destroy(sb.last_pool);
1693  svn_pool_destroy(sb.iterpool);
1694
1695  return SVN_NO_ERROR;
1696}
1697