]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/libsvn_repos/rev_hunt.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.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       /* 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. */
812 static svn_error_t *
813 maybe_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
837 svn_error_t *
838 svn_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. */
983 static svn_error_t *
984 get_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
1013 static APR_INLINE svn_boolean_t
1014 is_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
1026 struct 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. */
1041 static svn_error_t *
1042 get_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
1124 static svn_error_t *
1125 find_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 */
1231 static int
1232 compare_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
1243 static svn_error_t *
1244 find_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
1359 struct 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. */
1370 static svn_error_t *
1371 send_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) */
1454 static svn_error_t *
1455 get_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  */
1577 svn_error_t *
1578 svn_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 }