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