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