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