]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_repos/rev_hunt.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_repos / rev_hunt.c
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 \f
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. */
58 static svn_error_t *
59 get_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
77 svn_error_t *
78 svn_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
150 svn_error_t *
151 svn_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
187 svn_error_t *
188 svn_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
306 svn_error_t *
307 svn_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. */
507 static svn_error_t *
508 check_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
526 static svn_error_t *
527 check_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
589 svn_error_t *
590 svn_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
643 svn_error_t *
644 svn_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       if (authz_read_func)
730         {
731           svn_boolean_t readable;
732           svn_fs_root_t *tmp_root;
733
734           SVN_ERR(svn_fs_revision_root(&tmp_root, fs, revision, currpool));
735           SVN_ERR(authz_read_func(&readable, tmp_root, path,
736                                   authz_read_baton, currpool));
737           if (! readable)
738             {
739               svn_pool_destroy(lastpool);
740               svn_pool_destroy(currpool);
741
742               return SVN_NO_ERROR;
743             }
744         }
745
746       /* Assign the current path to all younger revisions until we reach
747          the copy target rev. */
748       while ((revision_ptr < revision_ptr_end)
749              && (*revision_ptr >= appeared_rev))
750         {
751           /* *revision_ptr is allocated out of pool, so we can point
752              to in the hash table. */
753           apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
754                        apr_pstrdup(pool, path));
755           revision_ptr++;
756         }
757
758       /* Ignore all revs between the copy target rev and the copy
759          source rev (non-inclusive). */
760       while ((revision_ptr < revision_ptr_end)
761              && (*revision_ptr > prev_rev))
762         revision_ptr++;
763
764       /* State update. */
765       path = prev_path;
766       revision = prev_rev;
767
768       /* Clear last pool and switch. */
769       svn_pool_clear(lastpool);
770       tmppool = lastpool;
771       lastpool = currpool;
772       currpool = tmppool;
773     }
774
775   /* There are no copies relevant to path@revision.  So any remaining
776      revisions either predate the creation of path@revision or have
777      the node existing at the same path.  We will look up path@lrev
778      for each remaining location-revision and make sure it is related
779      to path@revision. */
780   SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
781   SVN_ERR(svn_fs_node_id(&id, root, path, pool));
782   while (revision_ptr < revision_ptr_end)
783     {
784       svn_node_kind_t kind;
785       const svn_fs_id_t *lrev_id;
786
787       svn_pool_clear(currpool);
788       SVN_ERR(svn_fs_revision_root(&root, fs, *revision_ptr, currpool));
789       SVN_ERR(svn_fs_check_path(&kind, root, path, currpool));
790       if (kind == svn_node_none)
791         break;
792       SVN_ERR(svn_fs_node_id(&lrev_id, root, path, currpool));
793       if (! svn_fs_check_related(id, lrev_id))
794         break;
795
796       /* The node exists at the same path; record that and advance. */
797       apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
798                    apr_pstrdup(pool, path));
799       revision_ptr++;
800     }
801
802   /* Ignore any remaining location-revisions; they predate the
803      creation of path@revision. */
804
805   svn_pool_destroy(lastpool);
806   svn_pool_destroy(currpool);
807
808   return SVN_NO_ERROR;
809 }
810
811
812 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
813    its revision range fits between END_REV and START_REV, possibly
814    cropping the range so that it fits *entirely* in that range. */
815 static svn_error_t *
816 maybe_crop_and_send_segment(svn_location_segment_t *segment,
817                             svn_revnum_t start_rev,
818                             svn_revnum_t end_rev,
819                             svn_location_segment_receiver_t receiver,
820                             void *receiver_baton,
821                             apr_pool_t *pool)
822 {
823   /* We only want to transmit this segment if some portion of it
824      is between our END_REV and START_REV. */
825   if (! ((segment->range_start > start_rev)
826          || (segment->range_end < end_rev)))
827     {
828       /* Correct our segment range when the range straddles one of
829          our requested revision boundaries. */
830       if (segment->range_start < end_rev)
831         segment->range_start = end_rev;
832       if (segment->range_end > start_rev)
833         segment->range_end = start_rev;
834       SVN_ERR(receiver(segment, receiver_baton, pool));
835     }
836   return SVN_NO_ERROR;
837 }
838
839
840 svn_error_t *
841 svn_repos_node_location_segments(svn_repos_t *repos,
842                                  const char *path,
843                                  svn_revnum_t peg_revision,
844                                  svn_revnum_t start_rev,
845                                  svn_revnum_t end_rev,
846                                  svn_location_segment_receiver_t receiver,
847                                  void *receiver_baton,
848                                  svn_repos_authz_func_t authz_read_func,
849                                  void *authz_read_baton,
850                                  apr_pool_t *pool)
851 {
852   svn_fs_t *fs = svn_repos_fs(repos);
853   svn_stringbuf_t *current_path;
854   svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
855   apr_pool_t *subpool;
856
857   /* No PEG_REVISION?  We'll use HEAD. */
858   if (! SVN_IS_VALID_REVNUM(peg_revision))
859     {
860       SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
861       peg_revision = youngest_rev;
862     }
863
864   /* No START_REV?  We'll use HEAD (which we may have already fetched). */
865   if (! SVN_IS_VALID_REVNUM(start_rev))
866     {
867       if (SVN_IS_VALID_REVNUM(youngest_rev))
868         start_rev = youngest_rev;
869       else
870         SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
871     }
872
873   /* No END_REV?  We'll use 0. */
874   end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
875
876   /* Are the revision properly ordered?  They better be -- the API
877      demands it. */
878   SVN_ERR_ASSERT(end_rev <= start_rev);
879   SVN_ERR_ASSERT(start_rev <= peg_revision);
880
881   /* Ensure that PATH is absolute, because our path-math will depend
882      on that being the case.  */
883   if (*path != '/')
884     path = apr_pstrcat(pool, "/", path, (char *)NULL);
885
886   /* Auth check. */
887   if (authz_read_func)
888     {
889       svn_fs_root_t *peg_root;
890       SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
891       SVN_ERR(check_readability(peg_root, path,
892                                 authz_read_func, authz_read_baton, pool));
893     }
894
895   /* Okay, let's get searching! */
896   subpool = svn_pool_create(pool);
897   current_rev = peg_revision;
898   current_path = svn_stringbuf_create(path, pool);
899   while (current_rev >= end_rev)
900     {
901       svn_revnum_t appeared_rev, prev_rev;
902       const char *cur_path, *prev_path;
903       svn_location_segment_t *segment;
904
905       svn_pool_clear(subpool);
906
907       cur_path = apr_pstrmemdup(subpool, current_path->data,
908                                 current_path->len);
909       segment = apr_pcalloc(subpool, sizeof(*segment));
910       segment->range_end = current_rev;
911       segment->range_start = end_rev;
912       /* segment path should be absolute without leading '/'. */
913       segment->path = cur_path + 1;
914
915       SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
916                                        fs, current_rev, cur_path, subpool));
917
918       /* If there are no previous locations for this thing (meaning,
919          it originated at the current path), then we simply need to
920          find its revision of origin to populate our final segment.
921          Otherwise, the APPEARED_REV is the start of current segment's
922          range. */
923       if (! prev_path)
924         {
925           svn_fs_root_t *revroot;
926           SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
927           SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
928                                          cur_path, subpool));
929           if (segment->range_start < end_rev)
930             segment->range_start = end_rev;
931           current_rev = SVN_INVALID_REVNUM;
932         }
933       else
934         {
935           segment->range_start = appeared_rev;
936           svn_stringbuf_set(current_path, prev_path);
937           current_rev = prev_rev;
938         }
939
940       /* Report our segment, providing it passes authz muster. */
941       if (authz_read_func)
942         {
943           svn_boolean_t readable;
944           svn_fs_root_t *cur_rev_root;
945
946           /* authz_read_func requires path to have a leading slash. */
947           const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
948                                              (char *)NULL);
949
950           SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
951                                        segment->range_end, subpool));
952           SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
953                                   authz_read_baton, subpool));
954           if (! readable)
955             return SVN_NO_ERROR;
956         }
957
958       /* Transmit the segment (if it's within the scope of our concern). */
959       SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
960                                           receiver, receiver_baton, subpool));
961
962       /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
963          (and didn't ever reach END_REV).  */
964       if (! SVN_IS_VALID_REVNUM(current_rev))
965         break;
966
967       /* If there's a gap in the history, we need to report as much
968          (if the gap is within the scope of our concern). */
969       if (segment->range_start - current_rev > 1)
970         {
971           svn_location_segment_t *gap_segment;
972           gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
973           gap_segment->range_end = segment->range_start - 1;
974           gap_segment->range_start = current_rev + 1;
975           gap_segment->path = NULL;
976           SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
977                                               receiver, receiver_baton,
978                                               subpool));
979         }
980     }
981   svn_pool_destroy(subpool);
982   return SVN_NO_ERROR;
983 }
984
985 /* Get the mergeinfo for PATH in REPOS at REVNUM and store it in MERGEINFO. */
986 static svn_error_t *
987 get_path_mergeinfo(apr_hash_t **mergeinfo,
988                    svn_fs_t *fs,
989                    const char *path,
990                    svn_revnum_t revnum,
991                    apr_pool_t *result_pool,
992                    apr_pool_t *scratch_pool)
993 {
994   svn_mergeinfo_catalog_t tmp_catalog;
995   svn_fs_root_t *root;
996   apr_array_header_t *paths = apr_array_make(scratch_pool, 1,
997                                              sizeof(const char *));
998
999   APR_ARRAY_PUSH(paths, const char *) = path;
1000
1001   SVN_ERR(svn_fs_revision_root(&root, fs, revnum, scratch_pool));
1002   /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1003      because we will filter out unreadable revisions in
1004      find_interesting_revision(), above */
1005   SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root, paths,
1006                                 svn_mergeinfo_inherited, FALSE, TRUE,
1007                                 result_pool, scratch_pool));
1008
1009   *mergeinfo = svn_hash_gets(tmp_catalog, path);
1010   if (!*mergeinfo)
1011     *mergeinfo = apr_hash_make(result_pool);
1012
1013   return SVN_NO_ERROR;
1014 }
1015
1016 static APR_INLINE svn_boolean_t
1017 is_path_in_hash(apr_hash_t *duplicate_path_revs,
1018                 const char *path,
1019                 svn_revnum_t revision,
1020                 apr_pool_t *pool)
1021 {
1022   const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
1023   void *ptr;
1024
1025   ptr = svn_hash_gets(duplicate_path_revs, key);
1026   return ptr != NULL;
1027 }
1028
1029 struct path_revision
1030 {
1031   svn_revnum_t revnum;
1032   const char *path;
1033
1034   /* Does this path_rev have merges to also be included?  */
1035   apr_hash_t *merged_mergeinfo;
1036
1037   /* Is this a merged revision? */
1038   svn_boolean_t merged;
1039 };
1040
1041 /* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM.  Store
1042    the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL.  The
1043    returned *MERGED_MERGEINFO will be NULL if there are no changes. */
1044 static svn_error_t *
1045 get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1046                      svn_repos_t *repos,
1047                      struct path_revision *old_path_rev,
1048                      apr_pool_t *result_pool,
1049                      apr_pool_t *scratch_pool)
1050 {
1051   apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1052   svn_error_t *err;
1053   svn_fs_root_t *root;
1054   apr_hash_t *changed_paths;
1055   const char *path = old_path_rev->path;
1056
1057   /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1058      if there is a property change. */
1059   SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1060                                scratch_pool));
1061   SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
1062   while (1)
1063     {
1064       svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
1065       if (changed_path && changed_path->prop_mod)
1066         break;
1067       if (svn_fspath__is_root(path, strlen(path)))
1068         {
1069           *merged_mergeinfo = NULL;
1070           return SVN_NO_ERROR;
1071         }
1072       path = svn_fspath__dirname(path, scratch_pool);
1073     }
1074
1075   /* First, find the mergeinfo difference for old_path_rev->revnum, and
1076      old_path_rev->revnum - 1. */
1077   err = get_path_mergeinfo(&curr_mergeinfo, repos->fs, old_path_rev->path,
1078                            old_path_rev->revnum, scratch_pool,
1079                            scratch_pool);
1080   if (err)
1081     {
1082       if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1083         {
1084           /* Issue #3896: If invalid mergeinfo is encountered the
1085              best we can do is ignore it and act is if there are
1086              no mergeinfo differences. */
1087           svn_error_clear(err);
1088           *merged_mergeinfo = NULL;
1089           return SVN_NO_ERROR;
1090         }
1091       else
1092         {
1093           return svn_error_trace(err);
1094         }
1095     }
1096
1097   err = get_path_mergeinfo(&prev_mergeinfo, repos->fs, old_path_rev->path,
1098                            old_path_rev->revnum - 1, scratch_pool,
1099                            scratch_pool);
1100   if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1101               || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1102     {
1103       /* If the path doesn't exist in the previous revision or it does exist
1104          but has invalid mergeinfo (Issue #3896), assume no merges. */
1105       svn_error_clear(err);
1106       *merged_mergeinfo = NULL;
1107       return SVN_NO_ERROR;
1108     }
1109   else
1110     SVN_ERR(err);
1111
1112   /* Then calculate and merge the differences. */
1113   SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1114                               curr_mergeinfo, FALSE, result_pool,
1115                               scratch_pool));
1116   SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1117
1118   /* Store the result. */
1119   if (apr_hash_count(changed))
1120     *merged_mergeinfo = changed;
1121   else
1122     *merged_mergeinfo = NULL;
1123
1124   return SVN_NO_ERROR;
1125 }
1126
1127 static svn_error_t *
1128 find_interesting_revisions(apr_array_header_t *path_revisions,
1129                            svn_repos_t *repos,
1130                            const char *path,
1131                            svn_revnum_t start,
1132                            svn_revnum_t end,
1133                            svn_boolean_t include_merged_revisions,
1134                            svn_boolean_t mark_as_merged,
1135                            apr_hash_t *duplicate_path_revs,
1136                            svn_repos_authz_func_t authz_read_func,
1137                            void *authz_read_baton,
1138                            apr_pool_t *result_pool,
1139                            apr_pool_t *scratch_pool)
1140 {
1141   apr_pool_t *iterpool, *last_pool;
1142   svn_fs_history_t *history;
1143   svn_fs_root_t *root;
1144   svn_node_kind_t kind;
1145
1146   /* We switch between two pools while looping, since we need information from
1147      the last iteration to be available. */
1148   iterpool = svn_pool_create(scratch_pool);
1149   last_pool = svn_pool_create(scratch_pool);
1150
1151   /* The path had better be a file in this revision. */
1152   SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1153   SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1154   if (kind != svn_node_file)
1155     return svn_error_createf
1156       (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1157        path, end);
1158
1159   /* Open a history object. */
1160   SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1161   while (1)
1162     {
1163       struct path_revision *path_rev;
1164       svn_revnum_t tmp_revnum;
1165       const char *tmp_path;
1166       apr_pool_t *tmp_pool;
1167
1168       svn_pool_clear(iterpool);
1169
1170       /* Fetch the history object to walk through. */
1171       SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1172       if (!history)
1173         break;
1174       SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1175                                       history, iterpool));
1176
1177       /* Check to see if we already saw this path (and it's ancestors) */
1178       if (include_merged_revisions
1179           && is_path_in_hash(duplicate_path_revs, tmp_path,
1180                              tmp_revnum, iterpool))
1181          break;
1182
1183       /* Check authorization. */
1184       if (authz_read_func)
1185         {
1186           svn_boolean_t readable;
1187           svn_fs_root_t *tmp_root;
1188
1189           SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1190                                        iterpool));
1191           SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1192                                   authz_read_baton, iterpool));
1193           if (! readable)
1194             break;
1195         }
1196
1197       /* We didn't break, so we must really want this path-rev. */
1198       path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1199       path_rev->path = apr_pstrdup(result_pool, tmp_path);
1200       path_rev->revnum = tmp_revnum;
1201       path_rev->merged = mark_as_merged;
1202       APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1203
1204       if (include_merged_revisions)
1205         SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1206                                      path_rev, result_pool, iterpool));
1207       else
1208         path_rev->merged_mergeinfo = NULL;
1209
1210       /* Add the path/rev pair to the hash, so we can filter out future
1211          occurrences of it.  We only care about this if including merged
1212          revisions, 'cause that's the only time we can have duplicates. */
1213       svn_hash_sets(duplicate_path_revs,
1214                     apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1215                                  path_rev->revnum),
1216                     (void *)0xdeadbeef);
1217
1218       if (path_rev->revnum <= start)
1219         break;
1220
1221       /* Swap pools. */
1222       tmp_pool = iterpool;
1223       iterpool = last_pool;
1224       last_pool = tmp_pool;
1225     }
1226
1227   svn_pool_destroy(iterpool);
1228   svn_pool_destroy(last_pool);
1229
1230   return SVN_NO_ERROR;
1231 }
1232
1233 /* Comparison function to sort path/revisions in increasing order */
1234 static int
1235 compare_path_revisions(const void *a, const void *b)
1236 {
1237   struct path_revision *a_pr = *(struct path_revision *const *)a;
1238   struct path_revision *b_pr = *(struct path_revision *const *)b;
1239
1240   if (a_pr->revnum == b_pr->revnum)
1241     return 0;
1242
1243   return a_pr->revnum < b_pr->revnum ? 1 : -1;
1244 }
1245
1246 static svn_error_t *
1247 find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1248                       svn_revnum_t start,
1249                       const apr_array_header_t *mainline_path_revisions,
1250                       svn_repos_t *repos,
1251                       apr_hash_t *duplicate_path_revs,
1252                       svn_repos_authz_func_t authz_read_func,
1253                       void *authz_read_baton,
1254                       apr_pool_t *result_pool,
1255                       apr_pool_t *scratch_pool)
1256 {
1257   const apr_array_header_t *old;
1258   apr_array_header_t *new_merged_path_revs;
1259   apr_pool_t *iterpool, *last_pool;
1260   apr_array_header_t *merged_path_revisions =
1261     apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1262
1263   old = mainline_path_revisions;
1264   iterpool = svn_pool_create(scratch_pool);
1265   last_pool = svn_pool_create(scratch_pool);
1266
1267   do
1268     {
1269       int i;
1270       apr_pool_t *temp_pool;
1271
1272       svn_pool_clear(iterpool);
1273       new_merged_path_revs = apr_array_make(iterpool, 0,
1274                                             sizeof(struct path_revision *));
1275
1276       /* Iterate over OLD, checking for non-empty mergeinfo.  If found, gather
1277          path_revisions for any merged revisions, and store those in NEW. */
1278       for (i = 0; i < old->nelts; i++)
1279         {
1280           apr_pool_t *iterpool2;
1281           apr_hash_index_t *hi;
1282           struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1283                                                        struct path_revision *);
1284           if (!old_pr->merged_mergeinfo)
1285             continue;
1286
1287           iterpool2 = svn_pool_create(iterpool);
1288
1289           /* Determine and trace the merge sources. */
1290           for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1291                hi = apr_hash_next(hi))
1292             {
1293               apr_pool_t *iterpool3;
1294               svn_rangelist_t *rangelist;
1295               const char *path;
1296               int j;
1297
1298               svn_pool_clear(iterpool2);
1299               iterpool3 = svn_pool_create(iterpool2);
1300
1301               apr_hash_this(hi, (void *) &path, NULL, (void *) &rangelist);
1302
1303               for (j = 0; j < rangelist->nelts; j++)
1304                 {
1305                   svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1306                                                            svn_merge_range_t *);
1307                   svn_node_kind_t kind;
1308                   svn_fs_root_t *root;
1309
1310                   if (range->end < start)
1311                     continue;
1312
1313                   svn_pool_clear(iterpool3);
1314
1315                   SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1316                                                iterpool3));
1317                   SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1318                   if (kind != svn_node_file)
1319                     continue;
1320
1321                   /* Search and find revisions to add to the NEW list. */
1322                   SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1323                                                      repos, path,
1324                                                      range->start, range->end,
1325                                                      TRUE, TRUE,
1326                                                      duplicate_path_revs,
1327                                                      authz_read_func,
1328                                                      authz_read_baton,
1329                                                      result_pool, iterpool3));
1330                 }
1331               svn_pool_destroy(iterpool3);
1332             }
1333           svn_pool_destroy(iterpool2);
1334         }
1335
1336       /* Append the newly found path revisions with the old ones. */
1337       merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1338                                                new_merged_path_revs);
1339
1340       /* Swap data structures */
1341       old = new_merged_path_revs;
1342       temp_pool = last_pool;
1343       last_pool = iterpool;
1344       iterpool = temp_pool;
1345     }
1346   while (new_merged_path_revs->nelts > 0);
1347
1348   /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1349   qsort(merged_path_revisions->elts, merged_path_revisions->nelts,
1350         sizeof(struct path_revision *), compare_path_revisions);
1351
1352   /* Copy to the output array. */
1353   *merged_path_revisions_out = apr_array_copy(result_pool,
1354                                               merged_path_revisions);
1355
1356   svn_pool_destroy(iterpool);
1357   svn_pool_destroy(last_pool);
1358
1359   return SVN_NO_ERROR;
1360 }
1361
1362 struct send_baton
1363 {
1364   apr_pool_t *iterpool;
1365   apr_pool_t *last_pool;
1366   apr_hash_t *last_props;
1367   const char *last_path;
1368   svn_fs_root_t *last_root;
1369 };
1370
1371 /* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1372    SB. */
1373 static svn_error_t *
1374 send_path_revision(struct path_revision *path_rev,
1375                    svn_repos_t *repos,
1376                    struct send_baton *sb,
1377                    svn_file_rev_handler_t handler,
1378                    void *handler_baton)
1379 {
1380   apr_hash_t *rev_props;
1381   apr_hash_t *props;
1382   apr_array_header_t *prop_diffs;
1383   svn_fs_root_t *root;
1384   svn_txdelta_stream_t *delta_stream;
1385   svn_txdelta_window_handler_t delta_handler = NULL;
1386   void *delta_baton = NULL;
1387   apr_pool_t *tmp_pool;  /* For swapping */
1388   svn_boolean_t contents_changed;
1389
1390   svn_pool_clear(sb->iterpool);
1391
1392   /* Get the revision properties. */
1393   SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
1394                                    path_rev->revnum, sb->iterpool));
1395
1396   /* Open the revision root. */
1397   SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1398                                sb->iterpool));
1399
1400   /* Get the file's properties for this revision and compute the diffs. */
1401   SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1402                                    sb->iterpool));
1403   SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1404                          sb->iterpool));
1405
1406   /* Check if the contents changed. */
1407   /* Special case: In the first revision, we always provide a delta. */
1408   if (sb->last_root)
1409     SVN_ERR(svn_fs_contents_changed(&contents_changed, sb->last_root,
1410                                     sb->last_path, root, path_rev->path,
1411                                     sb->iterpool));
1412   else
1413     contents_changed = TRUE;
1414
1415   /* We have all we need, give to the handler. */
1416   SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1417                   rev_props, path_rev->merged,
1418                   contents_changed ? &delta_handler : NULL,
1419                   contents_changed ? &delta_baton : NULL,
1420                   prop_diffs, sb->iterpool));
1421
1422   /* Compute and send delta if client asked for it.
1423      Note that this was initialized to NULL, so if !contents_changed,
1424      no deltas will be computed. */
1425   if (delta_handler)
1426     {
1427       /* Get the content delta. */
1428       SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1429                                            sb->last_root, sb->last_path,
1430                                            root, path_rev->path,
1431                                            sb->iterpool));
1432       /* And send. */
1433       SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1434                                         delta_handler, delta_baton,
1435                                         sb->iterpool));
1436     }
1437
1438   /* Remember root, path and props for next iteration. */
1439   sb->last_root = root;
1440   sb->last_path = path_rev->path;
1441   sb->last_props = props;
1442
1443   /* Swap the pools. */
1444   tmp_pool = sb->iterpool;
1445   sb->iterpool = sb->last_pool;
1446   sb->last_pool = tmp_pool;
1447
1448   return SVN_NO_ERROR;
1449 }
1450
1451 /* Similar to svn_repos_get_file_revs2() but returns paths while walking
1452    history instead of after collecting all history.
1453
1454    This allows implementing clients to immediately start processing and
1455    stop when they got the information they need. (E.g. all or a specific set
1456    of lines were modified) */
1457 static svn_error_t *
1458 get_file_revs_backwards(svn_repos_t *repos,
1459                         const char *path,
1460                         svn_revnum_t start,
1461                         svn_revnum_t end,
1462                         svn_repos_authz_func_t authz_read_func,
1463                         void *authz_read_baton,
1464                         svn_file_rev_handler_t handler,
1465                         void *handler_baton,
1466                         apr_pool_t *scratch_pool)
1467 {
1468   apr_pool_t *iterpool, *last_pool;
1469   svn_fs_history_t *history;
1470   svn_fs_root_t *root;
1471   svn_node_kind_t kind;
1472   struct send_baton sb;
1473
1474   /* We switch between two pools while looping and so does the path-rev
1475      handler for actually reported revisions. We do this as we
1476      need just information from last iteration to be available. */
1477
1478   iterpool = svn_pool_create(scratch_pool);
1479   last_pool = svn_pool_create(scratch_pool);
1480   sb.iterpool = svn_pool_create(scratch_pool);
1481   sb.last_pool = svn_pool_create(scratch_pool);
1482
1483   /* We want the first txdelta to be against the empty file. */
1484   sb.last_root = NULL;
1485   sb.last_path = NULL;
1486
1487   /* Create an empty hash table for the first property diff. */
1488   sb.last_props = apr_hash_make(sb.last_pool);
1489
1490   /* The path had better be a file in this revision. */
1491   SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1492   SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1493   if (kind != svn_node_file)
1494     return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1495                              NULL, _("'%s' is not a file in revision %ld"),
1496                              path, end);
1497
1498   /* Open a history object. */
1499   SVN_ERR(svn_fs_node_history(&history, root, path, scratch_pool));
1500   while (1)
1501     {
1502       struct path_revision *path_rev;
1503       svn_revnum_t tmp_revnum;
1504       const char *tmp_path;
1505
1506       svn_pool_clear(iterpool);
1507
1508       /* Fetch the history object to walk through. */
1509       SVN_ERR(svn_fs_history_prev(&history, history, TRUE, iterpool));
1510       if (!history)
1511         break;
1512       SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1513                                       history, iterpool));
1514
1515       /* Check authorization. */
1516       if (authz_read_func)
1517         {
1518           svn_boolean_t readable;
1519           svn_fs_root_t *tmp_root;
1520
1521           SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1522                                        iterpool));
1523           SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1524                                   authz_read_baton, iterpool));
1525           if (! readable)
1526             break;
1527         }
1528
1529       /* We didn't break, so we must really want this path-rev. */
1530       path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1531       path_rev->path = tmp_path;
1532       path_rev->revnum = tmp_revnum;
1533       path_rev->merged = FALSE;
1534
1535       SVN_ERR(send_path_revision(path_rev, repos, &sb,
1536                                  handler, handler_baton));
1537
1538       if (path_rev->revnum <= start)
1539         break;
1540
1541       /* Swap pools. */
1542       {
1543         apr_pool_t *tmp_pool = iterpool;
1544         iterpool = last_pool;
1545         last_pool = tmp_pool;
1546       }
1547     }
1548
1549   svn_pool_destroy(iterpool);
1550   svn_pool_destroy(last_pool);
1551   svn_pool_destroy(sb.last_pool);
1552   svn_pool_destroy(sb.iterpool);
1553
1554   return SVN_NO_ERROR;
1555
1556 }
1557
1558
1559 /* We don't yet support sending revisions in reverse order; the caller wait
1560  * until we've traced back through the entire history, and then accept
1561  * them from oldest to youngest.  Someday this may change, but in the meantime,
1562  * the general algorithm is thus:
1563  *
1564  *  1) Trace back through the history of an object, adding each revision
1565  *     found to the MAINLINE_PATH_REVISIONS array, marking any which were
1566  *     merges.
1567  *  2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1568  *     merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1569  *     DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1570  *     times.
1571  *  3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1572  *     oldest to youngest, interleaving as appropriate.  This is implemented
1573  *     similar to an insertion sort, but instead of inserting into another
1574  *     array, we just call the appropriate handler.
1575  *
1576  * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1577  *          this would return an error (path not found) or just the first
1578  *          revision before end.
1579  */
1580 svn_error_t *
1581 svn_repos_get_file_revs2(svn_repos_t *repos,
1582                          const char *path,
1583                          svn_revnum_t start,
1584                          svn_revnum_t end,
1585                          svn_boolean_t include_merged_revisions,
1586                          svn_repos_authz_func_t authz_read_func,
1587                          void *authz_read_baton,
1588                          svn_file_rev_handler_t handler,
1589                          void *handler_baton,
1590                          apr_pool_t *scratch_pool)
1591 {
1592   apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1593   apr_hash_t *duplicate_path_revs;
1594   struct send_baton sb;
1595   int mainline_pos, merged_pos;
1596
1597   if (end < start)
1598     {
1599       if (include_merged_revisions)
1600         return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1601
1602       return svn_error_trace(
1603                       get_file_revs_backwards(repos, path,
1604                                               end, start,
1605                                               authz_read_func,
1606                                               authz_read_baton,
1607                                               handler,
1608                                               handler_baton,
1609                                               scratch_pool));
1610     }
1611
1612   /* We switch between two pools while looping, since we need information from
1613      the last iteration to be available. */
1614   sb.iterpool = svn_pool_create(scratch_pool);
1615   sb.last_pool = svn_pool_create(scratch_pool);
1616
1617   /* We want the first txdelta to be against the empty file. */
1618   sb.last_root = NULL;
1619   sb.last_path = NULL;
1620
1621   /* Create an empty hash table for the first property diff. */
1622   sb.last_props = apr_hash_make(sb.last_pool);
1623
1624
1625   /* Get the revisions we are interested in. */
1626   duplicate_path_revs = apr_hash_make(scratch_pool);
1627   mainline_path_revisions = apr_array_make(scratch_pool, 100,
1628                                            sizeof(struct path_revision *));
1629   SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1630                                      start, end, include_merged_revisions,
1631                                      FALSE, duplicate_path_revs,
1632                                      authz_read_func, authz_read_baton,
1633                                      scratch_pool, sb.iterpool));
1634
1635   /* If we are including merged revisions, go get those, too. */
1636   if (include_merged_revisions)
1637     SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1638                                   mainline_path_revisions, repos,
1639                                   duplicate_path_revs, authz_read_func,
1640                                   authz_read_baton,
1641                                   scratch_pool, sb.iterpool));
1642   else
1643     merged_path_revisions = apr_array_make(scratch_pool, 0,
1644                                            sizeof(struct path_revision *));
1645
1646   /* We must have at least one revision to get. */
1647   SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1648
1649   /* Walk through both mainline and merged revisions, and send them in
1650      reverse chronological order, interleaving as appropriate. */
1651   mainline_pos = mainline_path_revisions->nelts - 1;
1652   merged_pos = merged_path_revisions->nelts - 1;
1653   while (mainline_pos >= 0 && merged_pos >= 0)
1654     {
1655       struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1656                                                     mainline_pos,
1657                                                     struct path_revision *);
1658       struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1659                                                       merged_pos,
1660                                                       struct path_revision *);
1661
1662       if (main_pr->revnum <= merged_pr->revnum)
1663         {
1664           SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1665                                      handler_baton));
1666           mainline_pos -= 1;
1667         }
1668       else
1669         {
1670           SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1671                                      handler_baton));
1672           merged_pos -= 1;
1673         }
1674     }
1675
1676   /* Send any remaining revisions from the mainline list. */
1677   for (; mainline_pos >= 0; mainline_pos -= 1)
1678     {
1679       struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1680                                                     mainline_pos,
1681                                                     struct path_revision *);
1682       SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1683     }
1684
1685   /* Ditto for the merged list. */
1686   for (; merged_pos >= 0; merged_pos -= 1)
1687     {
1688       struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1689                                                       merged_pos,
1690                                                       struct path_revision *);
1691       SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1692                                  handler_baton));
1693     }
1694
1695   svn_pool_destroy(sb.last_pool);
1696   svn_pool_destroy(sb.iterpool);
1697
1698   return SVN_NO_ERROR;
1699 }