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