1/* log.c --- retrieving log messages
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23
24#include <stdlib.h>
25#define APR_WANT_STRFUNC
26#include <apr_want.h>
27
28#include "svn_compat.h"
29#include "svn_private_config.h"
30#include "svn_hash.h"
31#include "svn_pools.h"
32#include "svn_error.h"
33#include "svn_path.h"
34#include "svn_fs.h"
35#include "svn_repos.h"
36#include "svn_string.h"
37#include "svn_sorts.h"
38#include "svn_props.h"
39#include "svn_mergeinfo.h"
40#include "repos.h"
41#include "private/svn_fspath.h"
42#include "private/svn_mergeinfo_private.h"
43#include "private/svn_subr_private.h"
44
45
46
47svn_error_t *
48svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
49                                svn_repos_t *repos,
50                                svn_revnum_t revision,
51                                svn_repos_authz_func_t authz_read_func,
52                                void *authz_read_baton,
53                                apr_pool_t *pool)
54{
55  svn_fs_t *fs = svn_repos_fs(repos);
56  svn_fs_root_t *rev_root;
57  apr_hash_t *changes;
58  apr_hash_index_t *hi;
59  svn_boolean_t found_readable = FALSE;
60  svn_boolean_t found_unreadable = FALSE;
61  apr_pool_t *subpool;
62
63  /* By default, we'll grant full read access to REVISION. */
64  *access_level = svn_repos_revision_access_full;
65
66  /* No auth-checking function?  We're done. */
67  if (! authz_read_func)
68    return SVN_NO_ERROR;
69
70  /* Fetch the changes associated with REVISION. */
71  SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
72  SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
73
74  /* No changed paths?  We're done. */
75  if (apr_hash_count(changes) == 0)
76    return SVN_NO_ERROR;
77
78  /* Otherwise, we have to check the readability of each changed
79     path, or at least enough to answer the question asked. */
80  subpool = svn_pool_create(pool);
81  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
82    {
83      const void *key;
84      void *val;
85      svn_fs_path_change2_t *change;
86      svn_boolean_t readable;
87
88      svn_pool_clear(subpool);
89      apr_hash_this(hi, &key, NULL, &val);
90      change = val;
91
92      SVN_ERR(authz_read_func(&readable, rev_root, key,
93                              authz_read_baton, subpool));
94      if (! readable)
95        found_unreadable = TRUE;
96      else
97        found_readable = TRUE;
98
99      /* If we have at least one of each (readable/unreadable), we
100         have our answer. */
101      if (found_readable && found_unreadable)
102        goto decision;
103
104      switch (change->change_kind)
105        {
106        case svn_fs_path_change_add:
107        case svn_fs_path_change_replace:
108          {
109            const char *copyfrom_path;
110            svn_revnum_t copyfrom_rev;
111
112            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
113                                       rev_root, key, subpool));
114            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
115              {
116                svn_fs_root_t *copyfrom_root;
117                SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
118                                             copyfrom_rev, subpool));
119                SVN_ERR(authz_read_func(&readable,
120                                        copyfrom_root, copyfrom_path,
121                                        authz_read_baton, subpool));
122                if (! readable)
123                  found_unreadable = TRUE;
124
125                /* If we have at least one of each (readable/unreadable), we
126                   have our answer. */
127                if (found_readable && found_unreadable)
128                  goto decision;
129              }
130          }
131          break;
132
133        case svn_fs_path_change_delete:
134        case svn_fs_path_change_modify:
135        default:
136          break;
137        }
138    }
139
140 decision:
141  svn_pool_destroy(subpool);
142
143  /* Either every changed path was unreadable... */
144  if (! found_readable)
145    *access_level = svn_repos_revision_access_none;
146
147  /* ... or some changed path was unreadable... */
148  else if (found_unreadable)
149    *access_level = svn_repos_revision_access_partial;
150
151  /* ... or every changed path was readable (the default). */
152  return SVN_NO_ERROR;
153}
154
155
156/* Store as keys in CHANGED the paths of all node in ROOT that show a
157 * significant change.  "Significant" means that the text or
158 * properties of the node were changed, or that the node was added or
159 * deleted.
160 *
161 * The CHANGED hash set and its keys and values are allocated in POOL;
162 * keys are const char * paths and values are svn_log_changed_path_t.
163 *
164 * To prevent changes from being processed over and over again, the
165 * changed paths for ROOT may be passed in PREFETCHED_CHANGES.  If the
166 * latter is NULL, we will request the list inside this function.
167 *
168 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
169 * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
170 * copyfrom_path) is readable:
171 *
172 *     - If some paths are readable and some are not, then silently
173 *     omit the unreadable paths from the CHANGED hash, and return
174 *     SVN_ERR_AUTHZ_PARTIALLY_READABLE.
175 *
176 *     - If absolutely every changed-path (and copyfrom_path) is
177 *     unreadable, then return an empty CHANGED hash and
178 *     SVN_ERR_AUTHZ_UNREADABLE.  (This is to distinguish a revision
179 *     which truly has no changed paths from a revision in which all
180 *     paths are unreadable.)
181 */
182static svn_error_t *
183detect_changed(apr_hash_t **changed,
184               svn_fs_root_t *root,
185               svn_fs_t *fs,
186               apr_hash_t *prefetched_changes,
187               svn_repos_authz_func_t authz_read_func,
188               void *authz_read_baton,
189               apr_pool_t *pool)
190{
191  apr_hash_t *changes = prefetched_changes;
192  apr_hash_index_t *hi;
193  apr_pool_t *subpool;
194  svn_boolean_t found_readable = FALSE;
195  svn_boolean_t found_unreadable = FALSE;
196
197  *changed = svn_hash__make(pool);
198  if (changes == NULL)
199    SVN_ERR(svn_fs_paths_changed2(&changes, root, pool));
200
201  if (apr_hash_count(changes) == 0)
202    /* No paths changed in this revision?  Uh, sure, I guess the
203       revision is readable, then.  */
204    return SVN_NO_ERROR;
205
206  subpool = svn_pool_create(pool);
207
208  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
209    {
210      /* NOTE:  Much of this loop is going to look quite similar to
211         svn_repos_check_revision_access(), but we have to do more things
212         here, so we'll live with the duplication. */
213      const void *key;
214      void *val;
215      svn_fs_path_change2_t *change;
216      const char *path;
217      char action;
218      svn_log_changed_path2_t *item;
219
220      svn_pool_clear(subpool);
221
222      /* KEY will be the path, VAL the change. */
223      apr_hash_this(hi, &key, NULL, &val);
224      path = (const char *) key;
225      change = val;
226
227      /* Skip path if unreadable. */
228      if (authz_read_func)
229        {
230          svn_boolean_t readable;
231          SVN_ERR(authz_read_func(&readable,
232                                  root, path,
233                                  authz_read_baton, subpool));
234          if (! readable)
235            {
236              found_unreadable = TRUE;
237              continue;
238            }
239        }
240
241      /* At least one changed-path was readable. */
242      found_readable = TRUE;
243
244      switch (change->change_kind)
245        {
246        case svn_fs_path_change_reset:
247          continue;
248
249        case svn_fs_path_change_add:
250          action = 'A';
251          break;
252
253        case svn_fs_path_change_replace:
254          action = 'R';
255          break;
256
257        case svn_fs_path_change_delete:
258          action = 'D';
259          break;
260
261        case svn_fs_path_change_modify:
262        default:
263          action = 'M';
264          break;
265        }
266
267      item = svn_log_changed_path2_create(pool);
268      item->action = action;
269      item->node_kind = change->node_kind;
270      item->copyfrom_rev = SVN_INVALID_REVNUM;
271      item->text_modified = change->text_mod ? svn_tristate_true
272                                             : svn_tristate_false;
273      item->props_modified = change->prop_mod ? svn_tristate_true
274                                              : svn_tristate_false;
275
276      /* Pre-1.6 revision files don't store the change path kind, so fetch
277         it manually. */
278      if (item->node_kind == svn_node_unknown)
279        {
280          svn_fs_root_t *check_root = root;
281          const char *check_path = path;
282
283          /* Deleted items don't exist so check earlier revision.  We
284             know the parent must exist and could be a copy */
285          if (change->change_kind == svn_fs_path_change_delete)
286            {
287              svn_fs_history_t *history;
288              svn_revnum_t prev_rev;
289              const char *parent_path, *name;
290
291              svn_fspath__split(&parent_path, &name, path, subpool);
292
293              SVN_ERR(svn_fs_node_history(&history, root, parent_path,
294                                          subpool));
295
296              /* Two calls because the first call returns the original
297                 revision as the deleted child means it is 'interesting' */
298              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
299              SVN_ERR(svn_fs_history_prev(&history, history, TRUE, subpool));
300
301              SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
302                                              subpool));
303              SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev, subpool));
304              check_path = svn_fspath__join(parent_path, name, subpool);
305            }
306
307          SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
308                                    subpool));
309        }
310
311
312      if ((action == 'A') || (action == 'R'))
313        {
314          const char *copyfrom_path = change->copyfrom_path;
315          svn_revnum_t copyfrom_rev = change->copyfrom_rev;
316
317          /* the following is a potentially expensive operation since on FSFS
318             we will follow the DAG from ROOT to PATH and that requires
319             actually reading the directories along the way. */
320          if (!change->copyfrom_known)
321            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
322                                      root, path, subpool));
323
324          if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
325            {
326              svn_boolean_t readable = TRUE;
327
328              if (authz_read_func)
329                {
330                  svn_fs_root_t *copyfrom_root;
331
332                  SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
333                                               copyfrom_rev, subpool));
334                  SVN_ERR(authz_read_func(&readable,
335                                          copyfrom_root, copyfrom_path,
336                                          authz_read_baton, subpool));
337                  if (! readable)
338                    found_unreadable = TRUE;
339                }
340
341              if (readable)
342                {
343                  item->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
344                  item->copyfrom_rev = copyfrom_rev;
345                }
346            }
347        }
348      svn_hash_sets(*changed, apr_pstrdup(pool, path), item);
349    }
350
351  svn_pool_destroy(subpool);
352
353  if (! found_readable)
354    /* Every changed-path was unreadable. */
355    return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE,
356                            NULL, NULL);
357
358  if (found_unreadable)
359    /* At least one changed-path was unreadable. */
360    return svn_error_create(SVN_ERR_AUTHZ_PARTIALLY_READABLE,
361                            NULL, NULL);
362
363  /* Every changed-path was readable. */
364  return SVN_NO_ERROR;
365}
366
367/* This is used by svn_repos_get_logs to keep track of multiple
368 * path history information while working through history.
369 *
370 * The two pools are swapped after each iteration through history because
371 * to get the next history requires the previous one.
372 */
373struct path_info
374{
375  svn_stringbuf_t *path;
376  svn_revnum_t history_rev;
377  svn_boolean_t done;
378  svn_boolean_t first_time;
379
380  /* If possible, we like to keep open the history object for each path,
381     since it avoids needed to open and close it many times as we walk
382     backwards in time.  To do so we need two pools, so that we can clear
383     one each time through.  If we're not holding the history open for
384     this path then these three pointers will be NULL. */
385  svn_fs_history_t *hist;
386  apr_pool_t *newpool;
387  apr_pool_t *oldpool;
388};
389
390/* Advance to the next history for the path.
391 *
392 * If INFO->HIST is not NULL we do this using that existing history object,
393 * otherwise we open a new one.
394 *
395 * If no more history is available or the history revision is less
396 * (earlier) than START, or the history is not available due
397 * to authorization, then INFO->DONE is set to TRUE.
398 *
399 * A STRICT value of FALSE will indicate to follow history across copied
400 * paths.
401 *
402 * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
403 * AUTHZ_READ_BATON and FS) to check whether INFO->PATH is still readable if
404 * we do indeed find more history for the path.
405 */
406static svn_error_t *
407get_history(struct path_info *info,
408            svn_fs_t *fs,
409            svn_boolean_t strict,
410            svn_repos_authz_func_t authz_read_func,
411            void *authz_read_baton,
412            svn_revnum_t start,
413            apr_pool_t *pool)
414{
415  svn_fs_root_t *history_root = NULL;
416  svn_fs_history_t *hist;
417  apr_pool_t *subpool;
418  const char *path;
419
420  if (info->hist)
421    {
422      subpool = info->newpool;
423
424      SVN_ERR(svn_fs_history_prev(&info->hist, info->hist, ! strict, subpool));
425
426      hist = info->hist;
427    }
428  else
429    {
430      subpool = svn_pool_create(pool);
431
432      /* Open the history located at the last rev we were at. */
433      SVN_ERR(svn_fs_revision_root(&history_root, fs, info->history_rev,
434                                   subpool));
435
436      SVN_ERR(svn_fs_node_history(&hist, history_root, info->path->data,
437                                  subpool));
438
439      SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
440
441      if (info->first_time)
442        info->first_time = FALSE;
443      else
444        SVN_ERR(svn_fs_history_prev(&hist, hist, ! strict, subpool));
445    }
446
447  if (! hist)
448    {
449      svn_pool_destroy(subpool);
450      if (info->oldpool)
451        svn_pool_destroy(info->oldpool);
452      info->done = TRUE;
453      return SVN_NO_ERROR;
454    }
455
456  /* Fetch the location information for this history step. */
457  SVN_ERR(svn_fs_history_location(&path, &info->history_rev,
458                                  hist, subpool));
459
460  svn_stringbuf_set(info->path, path);
461
462  /* If this history item predates our START revision then
463     don't fetch any more for this path. */
464  if (info->history_rev < start)
465    {
466      svn_pool_destroy(subpool);
467      if (info->oldpool)
468        svn_pool_destroy(info->oldpool);
469      info->done = TRUE;
470      return SVN_NO_ERROR;
471    }
472
473  /* Is the history item readable?  If not, done with path. */
474  if (authz_read_func)
475    {
476      svn_boolean_t readable;
477      SVN_ERR(svn_fs_revision_root(&history_root, fs,
478                                   info->history_rev,
479                                   subpool));
480      SVN_ERR(authz_read_func(&readable, history_root,
481                              info->path->data,
482                              authz_read_baton,
483                              subpool));
484      if (! readable)
485        info->done = TRUE;
486    }
487
488  if (! info->hist)
489    {
490      svn_pool_destroy(subpool);
491    }
492  else
493    {
494      apr_pool_t *temppool = info->oldpool;
495      info->oldpool = info->newpool;
496      svn_pool_clear(temppool);
497      info->newpool = temppool;
498    }
499
500  return SVN_NO_ERROR;
501}
502
503/* Set INFO->HIST to the next history for the path *if* there is history
504 * available and INFO->HISTORY_REV is equal to or greater than CURRENT.
505 *
506 * *CHANGED is set to TRUE if the path has history in the CURRENT revision,
507 * otherwise it is not touched.
508 *
509 * If we do need to get the next history revision for the path, call
510 * get_history to do it -- see it for details.
511 */
512static svn_error_t *
513check_history(svn_boolean_t *changed,
514              struct path_info *info,
515              svn_fs_t *fs,
516              svn_revnum_t current,
517              svn_boolean_t strict,
518              svn_repos_authz_func_t authz_read_func,
519              void *authz_read_baton,
520              svn_revnum_t start,
521              apr_pool_t *pool)
522{
523  /* If we're already done with histories for this path,
524     don't try to fetch any more. */
525  if (info->done)
526    return SVN_NO_ERROR;
527
528  /* If the last rev we got for this path is less than CURRENT,
529     then just return and don't fetch history for this path.
530     The caller will get to this rev eventually or else reach
531     the limit. */
532  if (info->history_rev < current)
533    return SVN_NO_ERROR;
534
535  /* If the last rev we got for this path is equal to CURRENT
536     then set *CHANGED to true and get the next history
537     rev where this path was changed. */
538  *changed = TRUE;
539  return get_history(info, fs, strict, authz_read_func,
540                     authz_read_baton, start, pool);
541}
542
543/* Return the next interesting revision in our list of HISTORIES. */
544static svn_revnum_t
545next_history_rev(const apr_array_header_t *histories)
546{
547  svn_revnum_t next_rev = SVN_INVALID_REVNUM;
548  int i;
549
550  for (i = 0; i < histories->nelts; ++i)
551    {
552      struct path_info *info = APR_ARRAY_IDX(histories, i,
553                                             struct path_info *);
554      if (info->done)
555        continue;
556      if (info->history_rev > next_rev)
557        next_rev = info->history_rev;
558    }
559
560  return next_rev;
561}
562
563/* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
564   catalogs describing how mergeinfo values on paths (which are the
565   keys of those catalogs) were changed in REV.  If *PREFETCHED_CAHNGES
566   already contains the changed paths for REV, use that.  Otherwise,
567   request that data and return it in *PREFETCHED_CHANGES. */
568/* ### TODO: This would make a *great*, useful public function,
569   ### svn_repos_fs_mergeinfo_changed()!  -- cmpilato  */
570static svn_error_t *
571fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
572                     svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
573                     apr_hash_t **prefetched_changes,
574                     svn_fs_t *fs,
575                     svn_revnum_t rev,
576                     apr_pool_t *result_pool,
577                     apr_pool_t *scratch_pool)
578
579{
580  svn_fs_root_t *root;
581  apr_pool_t *iterpool;
582  apr_hash_index_t *hi;
583
584  /* Initialize return variables. */
585  *deleted_mergeinfo_catalog = svn_hash__make(result_pool);
586  *added_mergeinfo_catalog = svn_hash__make(result_pool);
587
588  /* Revision 0 has no mergeinfo and no mergeinfo changes. */
589  if (rev == 0)
590    return SVN_NO_ERROR;
591
592  /* We're going to use the changed-paths information for REV to
593     narrow down our search. */
594  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
595  if (*prefetched_changes == NULL)
596    SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
597
598  /* No changed paths?  We're done. */
599  if (apr_hash_count(*prefetched_changes) == 0)
600    return SVN_NO_ERROR;
601
602  /* Loop over changes, looking for anything that might carry an
603     svn:mergeinfo change and is one of our paths of interest, or a
604     child or [grand]parent directory thereof. */
605  iterpool = svn_pool_create(scratch_pool);
606  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
607       hi;
608       hi = apr_hash_next(hi))
609    {
610      const void *key;
611      void *val;
612      svn_fs_path_change2_t *change;
613      const char *changed_path, *base_path = NULL;
614      svn_revnum_t base_rev = SVN_INVALID_REVNUM;
615      svn_fs_root_t *base_root = NULL;
616      svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
617
618      svn_pool_clear(iterpool);
619
620      /* KEY will be the path, VAL the change. */
621      apr_hash_this(hi, &key, NULL, &val);
622      changed_path = key;
623      change = val;
624
625      /* If there was no property change on this item, ignore it. */
626      if (! change->prop_mod)
627        continue;
628
629      switch (change->change_kind)
630        {
631
632        /* ### TODO: Can the add, replace, and modify cases be joined
633           ### together to all use svn_repos__prev_location()?  The
634           ### difference would be the fallback case (path/rev-1 for
635           ### modifies, NULL otherwise).  -- cmpilato  */
636
637        /* If the path was added or replaced, see if it was created via
638           copy.  If so, that will tell us where its previous location
639           was.  If not, there's no previous location to examine.  */
640        case svn_fs_path_change_add:
641        case svn_fs_path_change_replace:
642          {
643            const char *copyfrom_path;
644            svn_revnum_t copyfrom_rev;
645
646            SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
647                                       root, changed_path, iterpool));
648            if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
649              {
650                base_path = apr_pstrdup(scratch_pool, copyfrom_path);
651                base_rev = copyfrom_rev;
652              }
653            break;
654          }
655
656        /* If the path was merely modified, see if its previous
657           location was affected by a copy which happened in this
658           revision before assuming it holds the same path it did the
659           previous revision. */
660        case svn_fs_path_change_modify:
661          {
662            svn_revnum_t appeared_rev;
663
664            SVN_ERR(svn_repos__prev_location(&appeared_rev, &base_path,
665                                             &base_rev, fs, rev,
666                                             changed_path, iterpool));
667
668            /* If this path isn't the result of a copy that occurred
669               in this revision, we can find the previous version of
670               it in REV - 1 at the same path. */
671            if (! (base_path && SVN_IS_VALID_REVNUM(base_rev)
672                   && (appeared_rev == rev)))
673              {
674                base_path = changed_path;
675                base_rev = rev - 1;
676              }
677            break;
678          }
679
680        /* We don't care about any of the other cases. */
681        case svn_fs_path_change_delete:
682        case svn_fs_path_change_reset:
683        default:
684          continue;
685        }
686
687      /* If there was a base location, fetch its mergeinfo property value. */
688      if (base_path && SVN_IS_VALID_REVNUM(base_rev))
689        {
690          SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, iterpool));
691          SVN_ERR(svn_fs_node_prop(&prev_mergeinfo_value, base_root, base_path,
692                                   SVN_PROP_MERGEINFO, iterpool));
693        }
694
695      /* Now fetch the current (as of REV) mergeinfo property value. */
696      SVN_ERR(svn_fs_node_prop(&mergeinfo_value, root, changed_path,
697                               SVN_PROP_MERGEINFO, iterpool));
698
699      /* No mergeinfo on either the new or previous location?  Just
700         skip it.  (If there *was* a change, it would have been in
701         inherited mergeinfo only, which should be picked up by the
702         iteration of this loop that finds the parent paths that
703         really got changed.)  */
704      if (! (mergeinfo_value || prev_mergeinfo_value))
705        continue;
706
707      /* If mergeinfo was explicitly added or removed on this path, we
708         need to check to see if that was a real semantic change of
709         meaning.  So, fill in the "missing" mergeinfo value with the
710         inherited mergeinfo for that path/revision.  */
711      if (prev_mergeinfo_value && (! mergeinfo_value))
712        {
713          apr_array_header_t *query_paths =
714            apr_array_make(iterpool, 1, sizeof(const char *));
715          svn_mergeinfo_t tmp_mergeinfo;
716          svn_mergeinfo_catalog_t tmp_catalog;
717
718          APR_ARRAY_PUSH(query_paths, const char *) = changed_path;
719          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, root,
720                                        query_paths, svn_mergeinfo_inherited,
721                                        FALSE, TRUE, iterpool, iterpool));
722          tmp_mergeinfo = svn_hash_gets(tmp_catalog, changed_path);
723          if (tmp_mergeinfo)
724            SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_value,
725                                            tmp_mergeinfo,
726                                            iterpool));
727        }
728      else if (mergeinfo_value && (! prev_mergeinfo_value)
729               && base_path && SVN_IS_VALID_REVNUM(base_rev))
730        {
731          apr_array_header_t *query_paths =
732            apr_array_make(iterpool, 1, sizeof(const char *));
733          svn_mergeinfo_t tmp_mergeinfo;
734          svn_mergeinfo_catalog_t tmp_catalog;
735
736          APR_ARRAY_PUSH(query_paths, const char *) = base_path;
737          SVN_ERR(svn_fs_get_mergeinfo2(&tmp_catalog, base_root,
738                                        query_paths, svn_mergeinfo_inherited,
739                                        FALSE, TRUE, iterpool, iterpool));
740          tmp_mergeinfo = svn_hash_gets(tmp_catalog, base_path);
741          if (tmp_mergeinfo)
742            SVN_ERR(svn_mergeinfo_to_string(&prev_mergeinfo_value,
743                                            tmp_mergeinfo,
744                                            iterpool));
745        }
746
747      /* If the old and new mergeinfo differ in any way, store the
748         before and after mergeinfo values in our return hashes. */
749      if ((prev_mergeinfo_value && (! mergeinfo_value))
750          || ((! prev_mergeinfo_value) && mergeinfo_value)
751          || (prev_mergeinfo_value && mergeinfo_value
752              && (! svn_string_compare(mergeinfo_value,
753                                       prev_mergeinfo_value))))
754        {
755          svn_mergeinfo_t prev_mergeinfo = NULL, mergeinfo = NULL;
756          svn_mergeinfo_t deleted, added;
757          const char *hash_path;
758
759          if (mergeinfo_value)
760            SVN_ERR(svn_mergeinfo_parse(&mergeinfo,
761                                        mergeinfo_value->data, iterpool));
762          if (prev_mergeinfo_value)
763            SVN_ERR(svn_mergeinfo_parse(&prev_mergeinfo,
764                                        prev_mergeinfo_value->data, iterpool));
765          SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
766                                      mergeinfo, FALSE, result_pool,
767                                      iterpool));
768
769          /* Toss interesting stuff into our return catalogs. */
770          hash_path = apr_pstrdup(result_pool, changed_path);
771          svn_hash_sets(*deleted_mergeinfo_catalog, hash_path, deleted);
772          svn_hash_sets(*added_mergeinfo_catalog, hash_path, added);
773        }
774    }
775
776  svn_pool_destroy(iterpool);
777  return SVN_NO_ERROR;
778}
779
780
781/* Determine what (if any) mergeinfo for PATHS was modified in
782   revision REV, returning the differences for added mergeinfo in
783   *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
784   If *PREFETCHED_CAHNGES already contains the changed paths for
785   REV, use that.  Otherwise, request that data and return it in
786   *PREFETCHED_CHANGES.
787   Use POOL for all allocations. */
788static svn_error_t *
789get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
790                               svn_mergeinfo_t *deleted_mergeinfo,
791                               apr_hash_t **prefetched_changes,
792                               svn_fs_t *fs,
793                               const apr_array_header_t *paths,
794                               svn_revnum_t rev,
795                               apr_pool_t *result_pool,
796                               apr_pool_t *scratch_pool)
797{
798  svn_mergeinfo_catalog_t added_mergeinfo_catalog, deleted_mergeinfo_catalog;
799  apr_hash_index_t *hi;
800  svn_fs_root_t *root;
801  apr_pool_t *iterpool;
802  int i;
803  svn_error_t *err;
804
805  /* Initialize return value. */
806  *added_mergeinfo = svn_hash__make(result_pool);
807  *deleted_mergeinfo = svn_hash__make(result_pool);
808
809  /* If we're asking about revision 0, there's no mergeinfo to be found. */
810  if (rev == 0)
811    return SVN_NO_ERROR;
812
813  /* No paths?  No mergeinfo. */
814  if (! paths->nelts)
815    return SVN_NO_ERROR;
816
817  /* Create a work subpool and get a root for REV. */
818  SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
819
820  /* Fetch the mergeinfo changes for REV. */
821  err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
822                             &added_mergeinfo_catalog,
823                             prefetched_changes,
824                             fs, rev, scratch_pool, scratch_pool);
825  if (err)
826    {
827      if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
828        {
829          /* Issue #3896: If invalid mergeinfo is encountered the
830             best we can do is ignore it and act as if there were
831             no mergeinfo modifications. */
832          svn_error_clear(err);
833          return SVN_NO_ERROR;
834        }
835      else
836        {
837          return svn_error_trace(err);
838        }
839    }
840
841  /* In most revisions, there will be no mergeinfo change at all. */
842  if (   apr_hash_count(deleted_mergeinfo_catalog) == 0
843      && apr_hash_count(added_mergeinfo_catalog) == 0)
844    return SVN_NO_ERROR;
845
846  /* Check our PATHS for any changes to their inherited mergeinfo.
847     (We deal with changes to mergeinfo directly *on* the paths in the
848     following loop.)  */
849  iterpool = svn_pool_create(scratch_pool);
850  for (i = 0; i < paths->nelts; i++)
851    {
852      const char *path = APR_ARRAY_IDX(paths, i, const char *);
853      const char *prev_path;
854      apr_ssize_t klen;
855      svn_revnum_t appeared_rev, prev_rev;
856      svn_fs_root_t *prev_root;
857      svn_mergeinfo_catalog_t catalog, inherited_catalog;
858      svn_mergeinfo_t prev_mergeinfo, mergeinfo, deleted, added,
859        prev_inherited_mergeinfo, inherited_mergeinfo;
860      apr_array_header_t *query_paths;
861
862      svn_pool_clear(iterpool);
863
864      /* If this path is represented in the changed-mergeinfo hashes,
865         we'll deal with it in the loop below. */
866      if (svn_hash_gets(deleted_mergeinfo_catalog, path))
867        continue;
868
869      /* Figure out what path/rev to compare against.  Ignore
870         not-found errors returned by the filesystem.  */
871      err = svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
872                                     fs, rev, path, iterpool);
873      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
874                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
875        {
876          svn_error_clear(err);
877          err = SVN_NO_ERROR;
878          continue;
879        }
880      SVN_ERR(err);
881
882      /* If this path isn't the result of a copy that occurred in this
883         revision, we can find the previous version of it in REV - 1
884         at the same path. */
885      if (! (prev_path && SVN_IS_VALID_REVNUM(prev_rev)
886             && (appeared_rev == rev)))
887        {
888          prev_path = path;
889          prev_rev = rev - 1;
890        }
891
892      /* Fetch the previous mergeinfo (including inherited stuff) for
893         this path.  Ignore not-found errors returned by the
894         filesystem or invalid mergeinfo (Issue #3896).*/
895      SVN_ERR(svn_fs_revision_root(&prev_root, fs, prev_rev, iterpool));
896      query_paths = apr_array_make(iterpool, 1, sizeof(const char *));
897      APR_ARRAY_PUSH(query_paths, const char *) = prev_path;
898      err = svn_fs_get_mergeinfo2(&catalog, prev_root, query_paths,
899                                  svn_mergeinfo_inherited, FALSE, TRUE,
900                                  iterpool, iterpool);
901      if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
902                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
903                  err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
904        {
905          svn_error_clear(err);
906          err = SVN_NO_ERROR;
907          continue;
908        }
909      SVN_ERR(err);
910
911      /* Issue #4022 'svn log -g interprets change in inherited mergeinfo due
912         to move as a merge': A copy where the source and destination inherit
913         mergeinfo from the same parent means the inherited mergeinfo of the
914         source and destination will differ, but this diffrence is not
915         indicative of a merge unless the mergeinfo on the inherited parent
916         has actually changed.
917
918         To check for this we must fetch the "raw" previous inherited
919         mergeinfo and the "raw" mergeinfo @REV then compare these. */
920      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, prev_root, query_paths,
921                                    svn_mergeinfo_nearest_ancestor, FALSE,
922                                    FALSE, /* adjust_inherited_mergeinfo */
923                                    iterpool, iterpool));
924
925      klen = strlen(prev_path);
926      prev_mergeinfo = apr_hash_get(catalog, prev_path, klen);
927      prev_inherited_mergeinfo = apr_hash_get(inherited_catalog, prev_path, klen);
928
929      /* Fetch the current mergeinfo (as of REV, and including
930         inherited stuff) for this path. */
931      APR_ARRAY_IDX(query_paths, 0, const char *) = path;
932      SVN_ERR(svn_fs_get_mergeinfo2(&catalog, root, query_paths,
933                                    svn_mergeinfo_inherited, FALSE, TRUE,
934                                    iterpool, iterpool));
935
936      /* Issue #4022 again, fetch the raw inherited mergeinfo. */
937      SVN_ERR(svn_fs_get_mergeinfo2(&inherited_catalog, root, query_paths,
938                                    svn_mergeinfo_nearest_ancestor, FALSE,
939                                    FALSE, /* adjust_inherited_mergeinfo */
940                                    iterpool, iterpool));
941
942      klen = strlen(path);
943      mergeinfo = apr_hash_get(catalog, path, klen);
944      inherited_mergeinfo = apr_hash_get(inherited_catalog, path, klen);
945
946      if (!prev_mergeinfo && !mergeinfo)
947        continue;
948
949      /* Last bit of issue #4022 checking. */
950      if (prev_inherited_mergeinfo && inherited_mergeinfo)
951        {
952          svn_boolean_t inherits_same_mergeinfo;
953
954          SVN_ERR(svn_mergeinfo__equals(&inherits_same_mergeinfo,
955                                        prev_inherited_mergeinfo,
956                                        inherited_mergeinfo,
957                                        TRUE, iterpool));
958          /* If a copy rather than an actual merge brought about an
959             inherited mergeinfo change then we are finished. */
960          if (inherits_same_mergeinfo)
961            continue;
962        }
963      else
964        {
965          svn_boolean_t same_mergeinfo;
966          SVN_ERR(svn_mergeinfo__equals(&same_mergeinfo,
967                                        prev_inherited_mergeinfo,
968                                        FALSE,
969                                        TRUE, iterpool));
970          if (same_mergeinfo)
971            continue;
972        }
973
974      /* Compare, constrast, and combine the results. */
975      SVN_ERR(svn_mergeinfo_diff2(&deleted, &added, prev_mergeinfo,
976                                  mergeinfo, FALSE, result_pool, iterpool));
977      SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo, deleted,
978                                   result_pool, iterpool));
979      SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo, added,
980                                   result_pool, iterpool));
981     }
982
983  /* Merge all the mergeinfos which are, or are children of, one of
984     our paths of interest into one giant delta mergeinfo.  */
985  for (hi = apr_hash_first(scratch_pool, added_mergeinfo_catalog);
986       hi; hi = apr_hash_next(hi))
987    {
988      const void *key;
989      apr_ssize_t klen;
990      void *val;
991      const char *changed_path;
992      svn_mergeinfo_t added, deleted;
993
994      /* The path is the key, the mergeinfo delta is the value. */
995      apr_hash_this(hi, &key, &klen, &val);
996      changed_path = key;
997      added = val;
998
999      for (i = 0; i < paths->nelts; i++)
1000        {
1001          const char *path = APR_ARRAY_IDX(paths, i, const char *);
1002          if (! svn_fspath__skip_ancestor(path, changed_path))
1003            continue;
1004          svn_pool_clear(iterpool);
1005          deleted = apr_hash_get(deleted_mergeinfo_catalog, key, klen);
1006          SVN_ERR(svn_mergeinfo_merge2(*deleted_mergeinfo,
1007                                       svn_mergeinfo_dup(deleted, result_pool),
1008                                       result_pool, iterpool));
1009          SVN_ERR(svn_mergeinfo_merge2(*added_mergeinfo,
1010                                       svn_mergeinfo_dup(added, result_pool),
1011                                       result_pool, iterpool));
1012
1013          break;
1014        }
1015    }
1016
1017  svn_pool_destroy(iterpool);
1018  return SVN_NO_ERROR;
1019}
1020
1021
1022/* Fill LOG_ENTRY with history information in FS at REV. */
1023static svn_error_t *
1024fill_log_entry(svn_log_entry_t *log_entry,
1025               svn_revnum_t rev,
1026               svn_fs_t *fs,
1027               apr_hash_t *prefetched_changes,
1028               svn_boolean_t discover_changed_paths,
1029               const apr_array_header_t *revprops,
1030               svn_repos_authz_func_t authz_read_func,
1031               void *authz_read_baton,
1032               apr_pool_t *pool)
1033{
1034  apr_hash_t *r_props, *changed_paths = NULL;
1035  svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
1036
1037  /* Discover changed paths if the user requested them
1038     or if we need to check that they are readable. */
1039  if ((rev > 0)
1040      && (authz_read_func || discover_changed_paths))
1041    {
1042      svn_fs_root_t *newroot;
1043      svn_error_t *patherr;
1044
1045      SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
1046      patherr = detect_changed(&changed_paths,
1047                               newroot, fs, prefetched_changes,
1048                               authz_read_func, authz_read_baton,
1049                               pool);
1050
1051      if (patherr
1052          && patherr->apr_err == SVN_ERR_AUTHZ_UNREADABLE)
1053        {
1054          /* All changed-paths are unreadable, so clear all fields. */
1055          svn_error_clear(patherr);
1056          changed_paths = NULL;
1057          get_revprops = FALSE;
1058        }
1059      else if (patherr
1060               && patherr->apr_err == SVN_ERR_AUTHZ_PARTIALLY_READABLE)
1061        {
1062          /* At least one changed-path was unreadable, so censor all
1063             but author and date.  (The unreadable paths are already
1064             missing from the hash.) */
1065          svn_error_clear(patherr);
1066          censor_revprops = TRUE;
1067        }
1068      else if (patherr)
1069        return patherr;
1070
1071      /* It may be the case that an authz func was passed in, but
1072         the user still doesn't want to see any changed-paths. */
1073      if (! discover_changed_paths)
1074        changed_paths = NULL;
1075    }
1076
1077  if (get_revprops)
1078    {
1079      /* User is allowed to see at least some revprops. */
1080      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
1081      if (revprops == NULL)
1082        {
1083          /* Requested all revprops... */
1084          if (censor_revprops)
1085            {
1086              /* ... but we can only return author/date. */
1087              log_entry->revprops = svn_hash__make(pool);
1088              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
1089                            svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR));
1090              svn_hash_sets(log_entry->revprops, SVN_PROP_REVISION_DATE,
1091                            svn_hash_gets(r_props, SVN_PROP_REVISION_DATE));
1092            }
1093          else
1094            /* ... so return all we got. */
1095            log_entry->revprops = r_props;
1096        }
1097      else
1098        {
1099          /* Requested only some revprops... */
1100          int i;
1101          for (i = 0; i < revprops->nelts; i++)
1102            {
1103              char *name = APR_ARRAY_IDX(revprops, i, char *);
1104              svn_string_t *value = svn_hash_gets(r_props, name);
1105              if (censor_revprops
1106                  && !(strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0
1107                       || strcmp(name, SVN_PROP_REVISION_DATE) == 0))
1108                /* ... but we can only return author/date. */
1109                continue;
1110              if (log_entry->revprops == NULL)
1111                log_entry->revprops = svn_hash__make(pool);
1112              svn_hash_sets(log_entry->revprops, name, value);
1113            }
1114        }
1115    }
1116
1117  log_entry->changed_paths = changed_paths;
1118  log_entry->changed_paths2 = changed_paths;
1119  log_entry->revision = rev;
1120
1121  return SVN_NO_ERROR;
1122}
1123
1124/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
1125
1126   FS is used with REV to fetch the interesting history information,
1127   such as changed paths, revprops, etc.
1128
1129   The detect_changed function is used if either AUTHZ_READ_FUNC is
1130   not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
1131
1132   If DESCENDING_ORDER is true, send child messages in descending order.
1133
1134   If REVPROPS is NULL, retrieve all revision properties; else, retrieve
1135   only the revision properties named by the (const char *) array elements
1136   (i.e. retrieve none if the array is empty).
1137
1138   LOG_TARGET_HISTORY_AS_MERGEINFO, HANDLING_MERGED_REVISION, and
1139   NESTED_MERGES are as per the arguments of the same name to DO_LOGS.  If
1140   HANDLING_MERGED_REVISION is true and *all* changed paths within REV are
1141   already represented in LOG_TARGET_HISTORY_AS_MERGEINFO, then don't send
1142   the log message for REV.  If SUBTRACTIVE_MERGE is true, then REV was
1143   reverse merged.
1144
1145   If HANDLING_MERGED_REVISIONS is FALSE then ignore NESTED_MERGES.  Otherwise
1146   if NESTED_MERGES is not NULL and REV is contained in it, then don't send
1147   the log for REV, otherwise send it normally and add REV to
1148   NESTED_MERGES. */
1149static svn_error_t *
1150send_log(svn_revnum_t rev,
1151         svn_fs_t *fs,
1152         apr_hash_t *prefetched_changes,
1153         svn_mergeinfo_t log_target_history_as_mergeinfo,
1154         apr_hash_t *nested_merges,
1155         svn_boolean_t discover_changed_paths,
1156         svn_boolean_t subtractive_merge,
1157         svn_boolean_t handling_merged_revision,
1158         const apr_array_header_t *revprops,
1159         svn_boolean_t has_children,
1160         svn_log_entry_receiver_t receiver,
1161         void *receiver_baton,
1162         svn_repos_authz_func_t authz_read_func,
1163         void *authz_read_baton,
1164         apr_pool_t *pool)
1165{
1166  svn_log_entry_t *log_entry;
1167  /* Assume we want to send the log for REV. */
1168  svn_boolean_t found_rev_of_interest = TRUE;
1169
1170  log_entry = svn_log_entry_create(pool);
1171  SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
1172                         discover_changed_paths || handling_merged_revision,
1173                         revprops, authz_read_func, authz_read_baton,
1174                         pool));
1175  log_entry->has_children = has_children;
1176  log_entry->subtractive_merge = subtractive_merge;
1177
1178  /* Is REV a merged revision that is already part of
1179     LOG_TARGET_HISTORY_AS_MERGEINFO?  If so then there is no
1180     need to send it, since it already was (or will be) sent. */
1181  if (handling_merged_revision
1182      && log_entry->changed_paths2
1183      && log_target_history_as_mergeinfo
1184      && apr_hash_count(log_target_history_as_mergeinfo))
1185    {
1186      apr_hash_index_t *hi;
1187      apr_pool_t *subpool = svn_pool_create(pool);
1188
1189      /* REV was merged in, but it might already be part of the log target's
1190         natural history, so change our starting assumption. */
1191      found_rev_of_interest = FALSE;
1192
1193      /* Look at each changed path in REV. */
1194      for (hi = apr_hash_first(subpool, log_entry->changed_paths2);
1195           hi;
1196           hi = apr_hash_next(hi))
1197        {
1198          svn_boolean_t path_is_in_history = FALSE;
1199          const char *changed_path = svn__apr_hash_index_key(hi);
1200          apr_hash_index_t *hi2;
1201          apr_pool_t *inner_subpool = svn_pool_create(subpool);
1202
1203          /* Look at each path on the log target's mergeinfo. */
1204          for (hi2 = apr_hash_first(inner_subpool,
1205                                    log_target_history_as_mergeinfo);
1206               hi2;
1207               hi2 = apr_hash_next(hi2))
1208            {
1209              const char *mergeinfo_path =
1210                svn__apr_hash_index_key(hi2);
1211              svn_rangelist_t *rangelist =
1212                svn__apr_hash_index_val(hi2);
1213
1214              /* Check whether CHANGED_PATH at revision REV is a child of
1215                 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
1216              if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
1217                {
1218                  int i;
1219
1220                  for (i = 0; i < rangelist->nelts; i++)
1221                    {
1222                      svn_merge_range_t *range =
1223                        APR_ARRAY_IDX(rangelist, i,
1224                                      svn_merge_range_t *);
1225                      if (rev > range->start && rev <= range->end)
1226                        {
1227                          path_is_in_history = TRUE;
1228                          break;
1229                        }
1230                    }
1231                }
1232              if (path_is_in_history)
1233                break;
1234            }
1235          svn_pool_destroy(inner_subpool);
1236
1237          if (!path_is_in_history)
1238            {
1239              /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
1240                 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
1241                 log for REV. */
1242              found_rev_of_interest = TRUE;
1243              break;
1244            }
1245        }
1246      svn_pool_destroy(subpool);
1247    }
1248
1249  /* If we only got changed paths the sake of detecting redundant merged
1250     revisions, then be sure we don't send that info to the receiver. */
1251  if (!discover_changed_paths && handling_merged_revision)
1252    log_entry->changed_paths = log_entry->changed_paths2 = NULL;
1253
1254  /* Send the entry to the receiver, unless it is a redundant merged
1255     revision. */
1256  if (found_rev_of_interest)
1257    {
1258      /* Is REV a merged revision we've already sent? */
1259      if (nested_merges && handling_merged_revision)
1260        {
1261          svn_revnum_t *merged_rev = apr_hash_get(nested_merges, &rev,
1262                                                  sizeof(svn_revnum_t *));
1263
1264          if (merged_rev)
1265            {
1266              /* We already sent REV. */
1267              return SVN_NO_ERROR;
1268            }
1269          else
1270            {
1271              /* NESTED_REVS needs to last across all the send_log, do_logs,
1272                 handle_merged_revisions() recursions, so use the pool it
1273                 was created in at the top of the recursion. */
1274              apr_pool_t *hash_pool = apr_hash_pool_get(nested_merges);
1275              svn_revnum_t *long_lived_rev = apr_palloc(hash_pool,
1276                                                        sizeof(svn_revnum_t));
1277              *long_lived_rev = rev;
1278              apr_hash_set(nested_merges, long_lived_rev,
1279                           sizeof(svn_revnum_t *), long_lived_rev);
1280            }
1281        }
1282
1283      return (*receiver)(receiver_baton, log_entry, pool);
1284    }
1285  else
1286    {
1287      return SVN_NO_ERROR;
1288    }
1289}
1290
1291/* This controls how many history objects we keep open.  For any targets
1292   over this number we have to open and close their histories as needed,
1293   which is CPU intensive, but keeps us from using an unbounded amount of
1294   memory. */
1295#define MAX_OPEN_HISTORIES 32
1296
1297/* Get the histories for PATHS, and store them in *HISTORIES.
1298
1299   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1300   repository locations as fatal -- just ignore them.  */
1301static svn_error_t *
1302get_path_histories(apr_array_header_t **histories,
1303                   svn_fs_t *fs,
1304                   const apr_array_header_t *paths,
1305                   svn_revnum_t hist_start,
1306                   svn_revnum_t hist_end,
1307                   svn_boolean_t strict_node_history,
1308                   svn_boolean_t ignore_missing_locations,
1309                   svn_repos_authz_func_t authz_read_func,
1310                   void *authz_read_baton,
1311                   apr_pool_t *pool)
1312{
1313  svn_fs_root_t *root;
1314  apr_pool_t *iterpool;
1315  svn_error_t *err;
1316  int i;
1317
1318  /* Create a history object for each path so we can walk through
1319     them all at the same time until we have all changes or LIMIT
1320     is reached.
1321
1322     There is some pool fun going on due to the fact that we have
1323     to hold on to the old pool with the history before we can
1324     get the next history.
1325  */
1326  *histories = apr_array_make(pool, paths->nelts,
1327                              sizeof(struct path_info *));
1328
1329  SVN_ERR(svn_fs_revision_root(&root, fs, hist_end, pool));
1330
1331  iterpool = svn_pool_create(pool);
1332  for (i = 0; i < paths->nelts; i++)
1333    {
1334      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
1335      struct path_info *info = apr_palloc(pool,
1336                                          sizeof(struct path_info));
1337
1338      if (authz_read_func)
1339        {
1340          svn_boolean_t readable;
1341
1342          svn_pool_clear(iterpool);
1343
1344          SVN_ERR(authz_read_func(&readable, root, this_path,
1345                                  authz_read_baton, iterpool));
1346          if (! readable)
1347            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
1348        }
1349
1350      info->path = svn_stringbuf_create(this_path, pool);
1351      info->done = FALSE;
1352      info->history_rev = hist_end;
1353      info->first_time = TRUE;
1354
1355      if (i < MAX_OPEN_HISTORIES)
1356        {
1357          err = svn_fs_node_history(&info->hist, root, this_path, pool);
1358          if (err
1359              && ignore_missing_locations
1360              && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1361                  err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1362                  err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1363            {
1364              svn_error_clear(err);
1365              continue;
1366            }
1367          SVN_ERR(err);
1368          info->newpool = svn_pool_create(pool);
1369          info->oldpool = svn_pool_create(pool);
1370        }
1371      else
1372        {
1373          info->hist = NULL;
1374          info->oldpool = NULL;
1375          info->newpool = NULL;
1376        }
1377
1378      err = get_history(info, fs,
1379                        strict_node_history,
1380                        authz_read_func, authz_read_baton,
1381                        hist_start, pool);
1382      if (err
1383          && ignore_missing_locations
1384          && (err->apr_err == SVN_ERR_FS_NOT_FOUND ||
1385              err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
1386              err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION))
1387        {
1388          svn_error_clear(err);
1389          continue;
1390        }
1391      SVN_ERR(err);
1392      APR_ARRAY_PUSH(*histories, struct path_info *) = info;
1393    }
1394  svn_pool_destroy(iterpool);
1395
1396  return SVN_NO_ERROR;
1397}
1398
1399/* Remove and return the first item from ARR. */
1400static void *
1401array_pop_front(apr_array_header_t *arr)
1402{
1403  void *item = arr->elts;
1404
1405  if (apr_is_empty_array(arr))
1406    return NULL;
1407
1408  arr->elts += arr->elt_size;
1409  arr->nelts -= 1;
1410  arr->nalloc -= 1;
1411  return item;
1412}
1413
1414/* A struct which represents a single revision range, and the paths which
1415   have mergeinfo in that range. */
1416struct path_list_range
1417{
1418  apr_array_header_t *paths;
1419  svn_merge_range_t range;
1420
1421  /* Is RANGE the result of a reverse merge? */
1422  svn_boolean_t reverse_merge;
1423};
1424
1425/* A struct which represents "inverse mergeinfo", that is, instead of having
1426   a path->revision_range_list mapping, which is the way mergeinfo is commonly
1427   represented, this struct enables a revision_range_list,path tuple, where
1428   the paths can be accessed by revision. */
1429struct rangelist_path
1430{
1431  svn_rangelist_t *rangelist;
1432  const char *path;
1433};
1434
1435/* Comparator function for combine_mergeinfo_path_lists().  Sorts
1436   rangelist_path structs in increasing order based upon starting revision,
1437   then ending revision of the first element in the rangelist.
1438
1439   This does not sort rangelists based upon subsequent elements, only the
1440   first range.  We'll sort any subsequent ranges in the correct order
1441   when they get bumped up to the front by removal of earlier ones, so we
1442   don't really have to sort them here.  See combine_mergeinfo_path_lists()
1443   for details. */
1444static int
1445compare_rangelist_paths(const void *a, const void *b)
1446{
1447  struct rangelist_path *rpa = *((struct rangelist_path *const *) a);
1448  struct rangelist_path *rpb = *((struct rangelist_path *const *) b);
1449  svn_merge_range_t *mra = APR_ARRAY_IDX(rpa->rangelist, 0,
1450                                         svn_merge_range_t *);
1451  svn_merge_range_t *mrb = APR_ARRAY_IDX(rpb->rangelist, 0,
1452                                         svn_merge_range_t *);
1453
1454  if (mra->start < mrb->start)
1455    return -1;
1456  if (mra->start > mrb->start)
1457    return 1;
1458  if (mra->end < mrb->end)
1459    return -1;
1460  if (mra->end > mrb->end)
1461    return 1;
1462
1463  return 0;
1464}
1465
1466/* From MERGEINFO, return in *COMBINED_LIST, allocated in POOL, a list of
1467   'struct path_list_range's.  This list represents the rangelists in
1468   MERGEINFO and each path which has mergeinfo in that range.
1469   If REVERSE_MERGE is true, then MERGEINFO represents mergeinfo removed
1470   as the result of a reverse merge. */
1471static svn_error_t *
1472combine_mergeinfo_path_lists(apr_array_header_t **combined_list,
1473                             svn_mergeinfo_t mergeinfo,
1474                             svn_boolean_t reverse_merge,
1475                             apr_pool_t *pool)
1476{
1477  apr_hash_index_t *hi;
1478  apr_array_header_t *rangelist_paths;
1479  apr_pool_t *subpool = svn_pool_create(pool);
1480
1481  /* Create a list of (revision range, path) tuples from MERGEINFO. */
1482  rangelist_paths = apr_array_make(subpool, apr_hash_count(mergeinfo),
1483                                   sizeof(struct rangelist_path *));
1484  for (hi = apr_hash_first(subpool, mergeinfo); hi;
1485       hi = apr_hash_next(hi))
1486    {
1487      int i;
1488      struct rangelist_path *rp = apr_palloc(subpool, sizeof(*rp));
1489      apr_hash_this(hi, (void *) &rp->path, NULL,
1490                    (void *) &rp->rangelist);
1491      APR_ARRAY_PUSH(rangelist_paths, struct rangelist_path *) = rp;
1492
1493      /* We need to make local copies of the rangelist, since we will be
1494         modifying it, below. */
1495      rp->rangelist = svn_rangelist_dup(rp->rangelist, subpool);
1496
1497      /* Make all of the rangelists inclusive, both start and end. */
1498      for (i = 0; i < rp->rangelist->nelts; i++)
1499        APR_ARRAY_IDX(rp->rangelist, i, svn_merge_range_t *)->start += 1;
1500    }
1501
1502  /* Loop over the (revision range, path) tuples, chopping them into
1503     (revision range, paths) tuples, and appending those to the output
1504     list. */
1505  if (! *combined_list)
1506    *combined_list = apr_array_make(pool, 0, sizeof(struct path_list_range *));
1507
1508  while (rangelist_paths->nelts > 1)
1509    {
1510      svn_revnum_t youngest, next_youngest, tail, youngest_end;
1511      struct path_list_range *plr;
1512      struct rangelist_path *rp;
1513      int num_revs;
1514      int i;
1515
1516      /* First, sort the list such that the start revision of the first
1517         revision arrays are sorted. */
1518      qsort(rangelist_paths->elts, rangelist_paths->nelts,
1519            rangelist_paths->elt_size, compare_rangelist_paths);
1520
1521      /* Next, find the number of revision ranges which start with the same
1522         revision. */
1523      rp = APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1524      youngest =
1525        APR_ARRAY_IDX(rp->rangelist, 0, struct svn_merge_range_t *)->start;
1526      next_youngest = youngest;
1527      for (num_revs = 1; next_youngest == youngest; num_revs++)
1528        {
1529          if (num_revs == rangelist_paths->nelts)
1530            {
1531              num_revs += 1;
1532              break;
1533            }
1534          rp = APR_ARRAY_IDX(rangelist_paths, num_revs,
1535                             struct rangelist_path *);
1536          next_youngest = APR_ARRAY_IDX(rp->rangelist, 0,
1537                                        struct svn_merge_range_t *)->start;
1538        }
1539      num_revs -= 1;
1540
1541      /* The start of the new range will be YOUNGEST, and we now find the end
1542         of the new range, which should be either one less than the next
1543         earliest start of a rangelist, or the end of the first rangelist. */
1544      youngest_end =
1545        APR_ARRAY_IDX(APR_ARRAY_IDX(rangelist_paths, 0,
1546                                    struct rangelist_path *)->rangelist,
1547                      0, svn_merge_range_t *)->end;
1548      if ( (next_youngest == youngest) || (youngest_end < next_youngest) )
1549        tail = youngest_end;
1550      else
1551        tail = next_youngest - 1;
1552
1553      /* Insert the (earliest, tail) tuple into the output list, along with
1554         a list of paths which match it. */
1555      plr = apr_palloc(pool, sizeof(*plr));
1556      plr->reverse_merge = reverse_merge;
1557      plr->range.start = youngest;
1558      plr->range.end = tail;
1559      plr->paths = apr_array_make(pool, num_revs, sizeof(const char *));
1560      for (i = 0; i < num_revs; i++)
1561        APR_ARRAY_PUSH(plr->paths, const char *) =
1562          APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *)->path;
1563      APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1564
1565      /* Now, check to see which (rangelist path) combinations we can remove,
1566         and do so. */
1567      for (i = 0; i < num_revs; i++)
1568        {
1569          svn_merge_range_t *range;
1570          rp = APR_ARRAY_IDX(rangelist_paths, i, struct rangelist_path *);
1571          range = APR_ARRAY_IDX(rp->rangelist, 0, svn_merge_range_t *);
1572
1573          /* Set the start of the range to beyond the end of the range we
1574             just built.  If the range is now "inverted", we can get pop it
1575             off the list. */
1576          range->start = tail + 1;
1577          if (range->start > range->end)
1578            {
1579              if (rp->rangelist->nelts == 1)
1580                {
1581                  /* The range is the only on its list, so we should remove
1582                     the entire rangelist_path, adjusting our loop control
1583                     variables appropriately. */
1584                  array_pop_front(rangelist_paths);
1585                  i--;
1586                  num_revs--;
1587                }
1588              else
1589                {
1590                  /* We have more than one range on the list, so just remove
1591                     the first one. */
1592                  array_pop_front(rp->rangelist);
1593                }
1594            }
1595        }
1596    }
1597
1598  /* Finally, add the last remaining (revision range, path) to the output
1599     list. */
1600  if (rangelist_paths->nelts > 0)
1601    {
1602      struct rangelist_path *first_rp =
1603        APR_ARRAY_IDX(rangelist_paths, 0, struct rangelist_path *);
1604      while (first_rp->rangelist->nelts > 0)
1605        {
1606          struct path_list_range *plr = apr_palloc(pool, sizeof(*plr));
1607
1608          plr->reverse_merge = reverse_merge;
1609          plr->paths = apr_array_make(pool, 1, sizeof(const char *));
1610          APR_ARRAY_PUSH(plr->paths, const char *) = first_rp->path;
1611          plr->range = *APR_ARRAY_IDX(first_rp->rangelist, 0,
1612                                      svn_merge_range_t *);
1613          array_pop_front(first_rp->rangelist);
1614          APR_ARRAY_PUSH(*combined_list, struct path_list_range *) = plr;
1615        }
1616    }
1617
1618  svn_pool_destroy(subpool);
1619
1620  return SVN_NO_ERROR;
1621}
1622
1623
1624/* Pity that C is so ... linear. */
1625static svn_error_t *
1626do_logs(svn_fs_t *fs,
1627        const apr_array_header_t *paths,
1628        svn_mergeinfo_t log_target_history_as_mergeinfo,
1629        svn_mergeinfo_t processed,
1630        apr_hash_t *nested_merges,
1631        svn_revnum_t hist_start,
1632        svn_revnum_t hist_end,
1633        int limit,
1634        svn_boolean_t discover_changed_paths,
1635        svn_boolean_t strict_node_history,
1636        svn_boolean_t include_merged_revisions,
1637        svn_boolean_t handling_merged_revisions,
1638        svn_boolean_t subtractive_merge,
1639        svn_boolean_t ignore_missing_locations,
1640        const apr_array_header_t *revprops,
1641        svn_boolean_t descending_order,
1642        svn_log_entry_receiver_t receiver,
1643        void *receiver_baton,
1644        svn_repos_authz_func_t authz_read_func,
1645        void *authz_read_baton,
1646        apr_pool_t *pool);
1647
1648/* Comparator function for handle_merged_revisions().  Sorts path_list_range
1649   structs in increasing order based on the struct's RANGE.START revision,
1650   then RANGE.END revision. */
1651static int
1652compare_path_list_range(const void *a, const void *b)
1653{
1654  struct path_list_range *plr_a = *((struct path_list_range *const *) a);
1655  struct path_list_range *plr_b = *((struct path_list_range *const *) b);
1656
1657  if (plr_a->range.start < plr_b->range.start)
1658    return -1;
1659  if (plr_a->range.start > plr_b->range.start)
1660    return 1;
1661  if (plr_a->range.end < plr_b->range.end)
1662    return -1;
1663  if (plr_a->range.end > plr_b->range.end)
1664    return 1;
1665
1666  return 0;
1667}
1668
1669/* Examine the ADDED_MERGEINFO and DELETED_MERGEINFO for revision REV in FS
1670   (as collected by examining paths of interest to a log operation), and
1671   determine which revisions to report as having been merged or reverse-merged
1672   via the commit resulting in REV.
1673
1674   Silently ignore some failures to find the revisions mentioned in the
1675   added/deleted mergeinfos, as might happen if there is invalid mergeinfo.
1676
1677   Other parameters are as described by do_logs(), around which this
1678   is a recursion wrapper. */
1679static svn_error_t *
1680handle_merged_revisions(svn_revnum_t rev,
1681                        svn_fs_t *fs,
1682                        svn_mergeinfo_t log_target_history_as_mergeinfo,
1683                        apr_hash_t *nested_merges,
1684                        svn_mergeinfo_t processed,
1685                        svn_mergeinfo_t added_mergeinfo,
1686                        svn_mergeinfo_t deleted_mergeinfo,
1687                        svn_boolean_t discover_changed_paths,
1688                        svn_boolean_t strict_node_history,
1689                        const apr_array_header_t *revprops,
1690                        svn_log_entry_receiver_t receiver,
1691                        void *receiver_baton,
1692                        svn_repos_authz_func_t authz_read_func,
1693                        void *authz_read_baton,
1694                        apr_pool_t *pool)
1695{
1696  apr_array_header_t *combined_list = NULL;
1697  svn_log_entry_t *empty_log_entry;
1698  apr_pool_t *iterpool;
1699  int i;
1700
1701  if (apr_hash_count(added_mergeinfo) == 0
1702      && apr_hash_count(deleted_mergeinfo) == 0)
1703    return SVN_NO_ERROR;
1704
1705  if (apr_hash_count(added_mergeinfo))
1706    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, added_mergeinfo,
1707                                          FALSE, pool));
1708
1709  if (apr_hash_count(deleted_mergeinfo))
1710    SVN_ERR(combine_mergeinfo_path_lists(&combined_list, deleted_mergeinfo,
1711                                          TRUE, pool));
1712
1713  SVN_ERR_ASSERT(combined_list != NULL);
1714  qsort(combined_list->elts, combined_list->nelts,
1715        combined_list->elt_size, compare_path_list_range);
1716
1717  /* Because the combined_lists are ordered youngest to oldest,
1718     iterate over them in reverse. */
1719  iterpool = svn_pool_create(pool);
1720  for (i = combined_list->nelts - 1; i >= 0; i--)
1721    {
1722      struct path_list_range *pl_range
1723        = APR_ARRAY_IDX(combined_list, i, struct path_list_range *);
1724
1725      svn_pool_clear(iterpool);
1726      SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
1727                      processed, nested_merges,
1728                      pl_range->range.start, pl_range->range.end, 0,
1729                      discover_changed_paths, strict_node_history,
1730                      TRUE, pl_range->reverse_merge, TRUE, TRUE,
1731                      revprops, TRUE, receiver, receiver_baton,
1732                      authz_read_func, authz_read_baton, iterpool));
1733    }
1734  svn_pool_destroy(iterpool);
1735
1736  /* Send the empty revision.  */
1737  empty_log_entry = svn_log_entry_create(pool);
1738  empty_log_entry->revision = SVN_INVALID_REVNUM;
1739  return (*receiver)(receiver_baton, empty_log_entry, pool);
1740}
1741
1742/* This is used by do_logs to differentiate between forward and
1743   reverse merges. */
1744struct added_deleted_mergeinfo
1745{
1746  svn_mergeinfo_t added_mergeinfo;
1747  svn_mergeinfo_t deleted_mergeinfo;
1748};
1749
1750/* Reduce the search range PATHS, HIST_START, HIST_END by removing
1751   parts already covered by PROCESSED.  If reduction is possible
1752   elements may be removed from PATHS and *START_REDUCED and
1753   *END_REDUCED may be set to a narrower range. */
1754static svn_error_t *
1755reduce_search(apr_array_header_t *paths,
1756              svn_revnum_t *hist_start,
1757              svn_revnum_t *hist_end,
1758              svn_mergeinfo_t processed,
1759              apr_pool_t *scratch_pool)
1760{
1761  /* We add 1 to end to compensate for store_search */
1762  svn_revnum_t start = *hist_start <= *hist_end ? *hist_start : *hist_end;
1763  svn_revnum_t end = *hist_start <= *hist_end ? *hist_end + 1 : *hist_start + 1;
1764  int i;
1765
1766  for (i = 0; i < paths->nelts; ++i)
1767    {
1768      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1769      svn_rangelist_t *ranges = svn_hash_gets(processed, path);
1770      int j;
1771
1772      if (!ranges)
1773        continue;
1774
1775      /* ranges is ordered, could we use some sort of binary search
1776         rather than iterating? */
1777      for (j = 0; j < ranges->nelts; ++j)
1778        {
1779          svn_merge_range_t *range = APR_ARRAY_IDX(ranges, j,
1780                                                   svn_merge_range_t *);
1781          if (range->start <= start && range->end >= end)
1782            {
1783              for (j = i; j < paths->nelts - 1; ++j)
1784                APR_ARRAY_IDX(paths, j, const char *)
1785                  = APR_ARRAY_IDX(paths, j + 1, const char *);
1786
1787              --paths->nelts;
1788              --i;
1789              break;
1790            }
1791
1792          /* If there is only one path then we also check for a
1793             partial overlap rather than the full overlap above, and
1794             reduce the [hist_start, hist_end] range rather than
1795             dropping the path. */
1796          if (paths->nelts == 1)
1797            {
1798              if (range->start <= start && range->end > start)
1799                {
1800                  if (start == *hist_start)
1801                    *hist_start = range->end - 1;
1802                  else
1803                    *hist_end = range->end - 1;
1804                  break;
1805                }
1806              if (range->start < end && range->end >= end)
1807                {
1808                  if (start == *hist_start)
1809                    *hist_end = range->start;
1810                  else
1811                    *hist_start = range->start;
1812                  break;
1813                }
1814            }
1815        }
1816    }
1817
1818  return SVN_NO_ERROR;
1819}
1820
1821/* Extend PROCESSED to cover PATHS from HIST_START to HIST_END */
1822static svn_error_t *
1823store_search(svn_mergeinfo_t processed,
1824             const apr_array_header_t *paths,
1825             svn_revnum_t hist_start,
1826             svn_revnum_t hist_end,
1827             apr_pool_t *scratch_pool)
1828{
1829  /* We add 1 to end so that we can use the mergeinfo API to handle
1830     singe revisions where HIST_START is equal to HIST_END. */
1831  svn_revnum_t start = hist_start <= hist_end ? hist_start : hist_end;
1832  svn_revnum_t end = hist_start <= hist_end ? hist_end + 1 : hist_start + 1;
1833  svn_mergeinfo_t mergeinfo = svn_hash__make(scratch_pool);
1834  apr_pool_t *processed_pool = apr_hash_pool_get(processed);
1835  int i;
1836
1837  for (i = 0; i < paths->nelts; ++i)
1838    {
1839      const char *path = APR_ARRAY_IDX(paths, i, const char *);
1840      svn_rangelist_t *ranges = apr_array_make(processed_pool, 1,
1841                                               sizeof(svn_merge_range_t*));
1842      svn_merge_range_t *range = apr_palloc(processed_pool,
1843                                            sizeof(svn_merge_range_t));
1844
1845      range->start = start;
1846      range->end = end;
1847      range->inheritable = TRUE;
1848      APR_ARRAY_PUSH(ranges, svn_merge_range_t *) = range;
1849      svn_hash_sets(mergeinfo, apr_pstrdup(processed_pool, path), ranges);
1850    }
1851  SVN_ERR(svn_mergeinfo_merge2(processed, mergeinfo,
1852                               apr_hash_pool_get(processed), scratch_pool));
1853
1854  return SVN_NO_ERROR;
1855}
1856
1857/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
1858   RECEIVER with RECEIVER_BATON on them.  If DESCENDING_ORDER is TRUE, send
1859   the logs back as we find them, else buffer the logs and send them back
1860   in youngest->oldest order.
1861
1862   If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
1863   repository locations as fatal -- just ignore them.
1864
1865   If LOG_TARGET_HISTORY_AS_MERGEINFO is not NULL then it contains mergeinfo
1866   representing the history of PATHS between HIST_START and HIST_END.
1867
1868   If HANDLING_MERGED_REVISIONS is TRUE then this is a recursive call for
1869   merged revisions, see INCLUDE_MERGED_REVISIONS argument to
1870   svn_repos_get_logs4().  If SUBTRACTIVE_MERGE is true, then this is a
1871   recursive call for reverse merged revisions.
1872
1873   If NESTED_MERGES is not NULL then it is a hash of revisions (svn_revnum_t *
1874   mapped to svn_revnum_t *) for logs that were previously sent.  On the first
1875   call to do_logs it should always be NULL.  If INCLUDE_MERGED_REVISIONS is
1876   TRUE, then NESTED_MERGES will be created on the first call to do_logs,
1877   allocated in POOL.  It is then shared across
1878   do_logs()/send_logs()/handle_merge_revisions() recursions, see also the
1879   argument of the same name in send_logs().
1880
1881   PROCESSED is a mergeinfo hash that represents the paths and
1882   revisions that have already been searched.  Allocated like
1883   NESTED_MERGES above.
1884
1885   All other parameters are the same as svn_repos_get_logs4().
1886 */
1887static svn_error_t *
1888do_logs(svn_fs_t *fs,
1889        const apr_array_header_t *paths,
1890        svn_mergeinfo_t log_target_history_as_mergeinfo,
1891        svn_mergeinfo_t processed,
1892        apr_hash_t *nested_merges,
1893        svn_revnum_t hist_start,
1894        svn_revnum_t hist_end,
1895        int limit,
1896        svn_boolean_t discover_changed_paths,
1897        svn_boolean_t strict_node_history,
1898        svn_boolean_t include_merged_revisions,
1899        svn_boolean_t subtractive_merge,
1900        svn_boolean_t handling_merged_revisions,
1901        svn_boolean_t ignore_missing_locations,
1902        const apr_array_header_t *revprops,
1903        svn_boolean_t descending_order,
1904        svn_log_entry_receiver_t receiver,
1905        void *receiver_baton,
1906        svn_repos_authz_func_t authz_read_func,
1907        void *authz_read_baton,
1908        apr_pool_t *pool)
1909{
1910  apr_pool_t *iterpool;
1911  apr_pool_t *subpool = NULL;
1912  apr_array_header_t *revs = NULL;
1913  apr_hash_t *rev_mergeinfo = NULL;
1914  svn_revnum_t current;
1915  apr_array_header_t *histories;
1916  svn_boolean_t any_histories_left = TRUE;
1917  int send_count = 0;
1918  int i;
1919
1920  if (processed)
1921    {
1922      /* Casting away const. This only happens on recursive calls when
1923         it is known to be safe because we allocated paths. */
1924      SVN_ERR(reduce_search((apr_array_header_t *)paths, &hist_start, &hist_end,
1925                            processed, pool));
1926    }
1927
1928  if (!paths->nelts)
1929    return SVN_NO_ERROR;
1930
1931  if (processed)
1932    SVN_ERR(store_search(processed, paths, hist_start, hist_end, pool));
1933
1934  /* We have a list of paths and a revision range.  But we don't care
1935     about all the revisions in the range -- only the ones in which
1936     one of our paths was changed.  So let's go figure out which
1937     revisions contain real changes to at least one of our paths.  */
1938  SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
1939                             strict_node_history, ignore_missing_locations,
1940                             authz_read_func, authz_read_baton, pool));
1941
1942  /* Loop through all the revisions in the range and add any
1943     where a path was changed to the array, or if they wanted
1944     history in reverse order just send it to them right away. */
1945  iterpool = svn_pool_create(pool);
1946  for (current = hist_end;
1947       any_histories_left;
1948       current = next_history_rev(histories))
1949    {
1950      svn_boolean_t changed = FALSE;
1951      any_histories_left = FALSE;
1952      svn_pool_clear(iterpool);
1953
1954      for (i = 0; i < histories->nelts; i++)
1955        {
1956          struct path_info *info = APR_ARRAY_IDX(histories, i,
1957                                                 struct path_info *);
1958
1959          /* Check history for this path in current rev. */
1960          SVN_ERR(check_history(&changed, info, fs, current,
1961                                strict_node_history, authz_read_func,
1962                                authz_read_baton, hist_start, pool));
1963          if (! info->done)
1964            any_histories_left = TRUE;
1965        }
1966
1967      /* If any of the paths changed in this rev then add or send it. */
1968      if (changed)
1969        {
1970          svn_mergeinfo_t added_mergeinfo = NULL;
1971          svn_mergeinfo_t deleted_mergeinfo = NULL;
1972          svn_boolean_t has_children = FALSE;
1973          apr_hash_t *changes = NULL;
1974
1975          /* If we're including merged revisions, we need to calculate
1976             the mergeinfo deltas committed in this revision to our
1977             various paths. */
1978          if (include_merged_revisions)
1979            {
1980              apr_array_header_t *cur_paths =
1981                apr_array_make(iterpool, paths->nelts, sizeof(const char *));
1982
1983              /* Get the current paths of our history objects so we can
1984                 query mergeinfo. */
1985              /* ### TODO: Should this be ignoring depleted history items? */
1986              for (i = 0; i < histories->nelts; i++)
1987                {
1988                  struct path_info *info = APR_ARRAY_IDX(histories, i,
1989                                                         struct path_info *);
1990                  APR_ARRAY_PUSH(cur_paths, const char *) = info->path->data;
1991                }
1992              SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
1993                                                     &deleted_mergeinfo,
1994                                                     &changes,
1995                                                     fs, cur_paths,
1996                                                     current, iterpool,
1997                                                     iterpool));
1998              has_children = (apr_hash_count(added_mergeinfo) > 0
1999                              || apr_hash_count(deleted_mergeinfo) > 0);
2000            }
2001
2002          /* If our caller wants logs in descending order, we can send
2003             'em now (because that's the order we're crawling history
2004             in anyway). */
2005          if (descending_order)
2006            {
2007              SVN_ERR(send_log(current, fs, changes,
2008                               log_target_history_as_mergeinfo, nested_merges,
2009                               discover_changed_paths,
2010                               subtractive_merge, handling_merged_revisions,
2011                               revprops, has_children,
2012                               receiver, receiver_baton,
2013                               authz_read_func, authz_read_baton, iterpool));
2014
2015              if (has_children) /* Implies include_merged_revisions == TRUE */
2016                {
2017                  if (!nested_merges)
2018                    {
2019                      /* We're at the start of the recursion stack, create a
2020                         single hash to be shared across all of the merged
2021                         recursions so we can track and squelch duplicates. */
2022                      subpool = svn_pool_create(pool);
2023                      nested_merges = svn_hash__make(subpool);
2024                      processed = svn_hash__make(subpool);
2025                    }
2026
2027                  SVN_ERR(handle_merged_revisions(
2028                    current, fs,
2029                    log_target_history_as_mergeinfo, nested_merges,
2030                    processed,
2031                    added_mergeinfo, deleted_mergeinfo,
2032                    discover_changed_paths,
2033                    strict_node_history,
2034                    revprops,
2035                    receiver, receiver_baton,
2036                    authz_read_func,
2037                    authz_read_baton,
2038                    iterpool));
2039                }
2040              if (limit && ++send_count >= limit)
2041                break;
2042            }
2043          /* Otherwise, the caller wanted logs in ascending order, so
2044             we have to buffer up a list of revs and (if doing
2045             mergeinfo) a hash of related mergeinfo deltas, and
2046             process them later. */
2047          else
2048            {
2049              if (! revs)
2050                revs = apr_array_make(pool, 64, sizeof(svn_revnum_t));
2051              APR_ARRAY_PUSH(revs, svn_revnum_t) = current;
2052
2053              if (added_mergeinfo || deleted_mergeinfo)
2054                {
2055                  svn_revnum_t *cur_rev = apr_pcalloc(pool, sizeof(*cur_rev));
2056                  struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2057                    apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
2058
2059                  if (added_mergeinfo)
2060                    add_and_del_mergeinfo->added_mergeinfo =
2061                      svn_mergeinfo_dup(added_mergeinfo, pool);
2062
2063                  if (deleted_mergeinfo)
2064                    add_and_del_mergeinfo->deleted_mergeinfo =
2065                      svn_mergeinfo_dup(deleted_mergeinfo, pool);
2066
2067                  *cur_rev = current;
2068                  if (! rev_mergeinfo)
2069                    rev_mergeinfo = svn_hash__make(pool);
2070                  apr_hash_set(rev_mergeinfo, cur_rev, sizeof(*cur_rev),
2071                               add_and_del_mergeinfo);
2072                }
2073            }
2074        }
2075    }
2076  svn_pool_destroy(iterpool);
2077
2078  if (subpool)
2079    {
2080      nested_merges = NULL;
2081      svn_pool_destroy(subpool);
2082    }
2083
2084  if (revs)
2085    {
2086      /* Work loop for processing the revisions we found since they wanted
2087         history in forward order. */
2088      iterpool = svn_pool_create(pool);
2089      for (i = 0; i < revs->nelts; ++i)
2090        {
2091          svn_mergeinfo_t added_mergeinfo;
2092          svn_mergeinfo_t deleted_mergeinfo;
2093          svn_boolean_t has_children = FALSE;
2094
2095          svn_pool_clear(iterpool);
2096          current = APR_ARRAY_IDX(revs, revs->nelts - i - 1, svn_revnum_t);
2097
2098          /* If we've got a hash of revision mergeinfo (which can only
2099             happen if INCLUDE_MERGED_REVISIONS was set), we check to
2100             see if this revision is one which merged in other
2101             revisions we need to handle recursively. */
2102          if (rev_mergeinfo)
2103            {
2104              struct added_deleted_mergeinfo *add_and_del_mergeinfo =
2105                apr_hash_get(rev_mergeinfo, &current, sizeof(svn_revnum_t));
2106              added_mergeinfo = add_and_del_mergeinfo->added_mergeinfo;
2107              deleted_mergeinfo = add_and_del_mergeinfo->deleted_mergeinfo;
2108              has_children = (apr_hash_count(added_mergeinfo) > 0
2109                              || apr_hash_count(deleted_mergeinfo) > 0);
2110            }
2111
2112          SVN_ERR(send_log(current, fs, NULL,
2113                           log_target_history_as_mergeinfo, nested_merges,
2114                           discover_changed_paths, subtractive_merge,
2115                           handling_merged_revisions, revprops, has_children,
2116                           receiver, receiver_baton, authz_read_func,
2117                           authz_read_baton, iterpool));
2118          if (has_children)
2119            {
2120              if (!nested_merges)
2121                {
2122                  subpool = svn_pool_create(pool);
2123                  nested_merges = svn_hash__make(subpool);
2124                }
2125
2126              SVN_ERR(handle_merged_revisions(current, fs,
2127                                              log_target_history_as_mergeinfo,
2128                                              nested_merges,
2129                                              processed,
2130                                              added_mergeinfo,
2131                                              deleted_mergeinfo,
2132                                              discover_changed_paths,
2133                                              strict_node_history, revprops,
2134                                              receiver, receiver_baton,
2135                                              authz_read_func,
2136                                              authz_read_baton,
2137                                              iterpool));
2138            }
2139          if (limit && i + 1 >= limit)
2140            break;
2141        }
2142      svn_pool_destroy(iterpool);
2143    }
2144
2145  return SVN_NO_ERROR;
2146}
2147
2148struct location_segment_baton
2149{
2150  apr_array_header_t *history_segments;
2151  apr_pool_t *pool;
2152};
2153
2154/* svn_location_segment_receiver_t implementation for svn_repos_get_logs4. */
2155static svn_error_t *
2156location_segment_receiver(svn_location_segment_t *segment,
2157                          void *baton,
2158                          apr_pool_t *pool)
2159{
2160  struct location_segment_baton *b = baton;
2161
2162  APR_ARRAY_PUSH(b->history_segments, svn_location_segment_t *) =
2163    svn_location_segment_dup(segment, b->pool);
2164
2165  return SVN_NO_ERROR;
2166}
2167
2168
2169/* Populate *PATHS_HISTORY_MERGEINFO with mergeinfo representing the combined
2170   history of each path in PATHS between START_REV and END_REV in REPOS's
2171   filesystem.  START_REV and END_REV must be valid revisions.  RESULT_POOL
2172   is used to allocate *PATHS_HISTORY_MERGEINFO, SCRATCH_POOL is used for all
2173   other (temporary) allocations.  Other parameters are the same as
2174   svn_repos_get_logs4(). */
2175static svn_error_t *
2176get_paths_history_as_mergeinfo(svn_mergeinfo_t *paths_history_mergeinfo,
2177                               svn_repos_t *repos,
2178                               const apr_array_header_t *paths,
2179                               svn_revnum_t start_rev,
2180                               svn_revnum_t end_rev,
2181                               svn_repos_authz_func_t authz_read_func,
2182                               void *authz_read_baton,
2183                               apr_pool_t *result_pool,
2184                               apr_pool_t *scratch_pool)
2185{
2186  int i;
2187  svn_mergeinfo_t path_history_mergeinfo;
2188  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2189
2190  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_rev));
2191  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_rev));
2192
2193  /* Ensure START_REV is the youngest revision, as required by
2194     svn_repos_node_location_segments, for which this is an iterative
2195     wrapper. */
2196  if (start_rev < end_rev)
2197    {
2198      svn_revnum_t tmp_rev = start_rev;
2199      start_rev = end_rev;
2200      end_rev = tmp_rev;
2201    }
2202
2203  *paths_history_mergeinfo = svn_hash__make(result_pool);
2204
2205  for (i = 0; i < paths->nelts; i++)
2206    {
2207      const char *this_path = APR_ARRAY_IDX(paths, i, const char *);
2208      struct location_segment_baton loc_seg_baton;
2209
2210      svn_pool_clear(iterpool);
2211      loc_seg_baton.pool = scratch_pool;
2212      loc_seg_baton.history_segments =
2213        apr_array_make(iterpool, 4, sizeof(svn_location_segment_t *));
2214
2215      SVN_ERR(svn_repos_node_location_segments(repos, this_path, start_rev,
2216                                               start_rev, end_rev,
2217                                               location_segment_receiver,
2218                                               &loc_seg_baton,
2219                                               authz_read_func,
2220                                               authz_read_baton,
2221                                               iterpool));
2222
2223      SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(
2224        &path_history_mergeinfo, loc_seg_baton.history_segments, iterpool));
2225      SVN_ERR(svn_mergeinfo_merge2(*paths_history_mergeinfo,
2226                                   svn_mergeinfo_dup(path_history_mergeinfo,
2227                                                     result_pool),
2228                                   result_pool, iterpool));
2229    }
2230  svn_pool_destroy(iterpool);
2231  return SVN_NO_ERROR;
2232}
2233
2234svn_error_t *
2235svn_repos_get_logs4(svn_repos_t *repos,
2236                    const apr_array_header_t *paths,
2237                    svn_revnum_t start,
2238                    svn_revnum_t end,
2239                    int limit,
2240                    svn_boolean_t discover_changed_paths,
2241                    svn_boolean_t strict_node_history,
2242                    svn_boolean_t include_merged_revisions,
2243                    const apr_array_header_t *revprops,
2244                    svn_repos_authz_func_t authz_read_func,
2245                    void *authz_read_baton,
2246                    svn_log_entry_receiver_t receiver,
2247                    void *receiver_baton,
2248                    apr_pool_t *pool)
2249{
2250  svn_revnum_t head = SVN_INVALID_REVNUM;
2251  svn_fs_t *fs = repos->fs;
2252  svn_boolean_t descending_order;
2253  svn_mergeinfo_t paths_history_mergeinfo = NULL;
2254
2255  /* Setup log range. */
2256  SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
2257
2258  if (! SVN_IS_VALID_REVNUM(start))
2259    start = head;
2260
2261  if (! SVN_IS_VALID_REVNUM(end))
2262    end = head;
2263
2264  /* Check that revisions are sane before ever invoking receiver. */
2265  if (start > head)
2266    return svn_error_createf
2267      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2268       _("No such revision %ld"), start);
2269  if (end > head)
2270    return svn_error_createf
2271      (SVN_ERR_FS_NO_SUCH_REVISION, 0,
2272       _("No such revision %ld"), end);
2273
2274  /* Ensure a youngest-to-oldest revision crawl ordering using our
2275     (possibly sanitized) range values. */
2276  descending_order = start >= end;
2277  if (descending_order)
2278    {
2279      svn_revnum_t tmp_rev = start;
2280      start = end;
2281      end = tmp_rev;
2282    }
2283
2284  if (! paths)
2285    paths = apr_array_make(pool, 0, sizeof(const char *));
2286
2287  /* If we're not including merged revisions, and we were given no
2288     paths or a single empty (or "/") path, then we can bypass a bunch
2289     of complexity because we already know in which revisions the root
2290     directory was changed -- all of them.  */
2291  if ((! include_merged_revisions)
2292      && ((! paths->nelts)
2293          || ((paths->nelts == 1)
2294              && (svn_path_is_empty(APR_ARRAY_IDX(paths, 0, const char *))
2295                  || (strcmp(APR_ARRAY_IDX(paths, 0, const char *),
2296                             "/") == 0)))))
2297    {
2298      apr_uint64_t send_count = 0;
2299      int i;
2300      apr_pool_t *iterpool = svn_pool_create(pool);
2301
2302      /* If we are provided an authz callback function, use it to
2303         verify that the user has read access to the root path in the
2304         first of our revisions.
2305
2306         ### FIXME:  Strictly speaking, we should be checking this
2307         ### access in every revision along the line.  But currently,
2308         ### there are no known authz implementations which concern
2309         ### themselves with per-revision access.  */
2310      if (authz_read_func)
2311        {
2312          svn_boolean_t readable;
2313          svn_fs_root_t *rev_root;
2314
2315          SVN_ERR(svn_fs_revision_root(&rev_root, fs,
2316                                       descending_order ? end : start, pool));
2317          SVN_ERR(authz_read_func(&readable, rev_root, "",
2318                                  authz_read_baton, pool));
2319          if (! readable)
2320            return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
2321        }
2322
2323      send_count = end - start + 1;
2324      if (limit && send_count > limit)
2325        send_count = limit;
2326      for (i = 0; i < send_count; ++i)
2327        {
2328          svn_revnum_t rev;
2329
2330          svn_pool_clear(iterpool);
2331
2332          if (descending_order)
2333            rev = end - i;
2334          else
2335            rev = start + i;
2336          SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
2337                           discover_changed_paths, FALSE,
2338                           FALSE, revprops, FALSE, receiver,
2339                           receiver_baton, authz_read_func,
2340                           authz_read_baton, iterpool));
2341        }
2342      svn_pool_destroy(iterpool);
2343
2344      return SVN_NO_ERROR;
2345    }
2346
2347  /* If we are including merged revisions, then create mergeinfo that
2348     represents all of PATHS' history between START and END.  We will use
2349     this later to squelch duplicate log revisions that might exist in
2350     both natural history and merged-in history.  See
2351     http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
2352  if (include_merged_revisions)
2353    {
2354      apr_pool_t *subpool = svn_pool_create(pool);
2355
2356      SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
2357                                             repos, paths, start, end,
2358                                             authz_read_func,
2359                                             authz_read_baton,
2360                                             pool, subpool));
2361      svn_pool_destroy(subpool);
2362    }
2363
2364  return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
2365                 limit, discover_changed_paths, strict_node_history,
2366                 include_merged_revisions, FALSE, FALSE, FALSE, revprops,
2367                 descending_order, receiver, receiver_baton,
2368                 authz_read_func, authz_read_baton, pool);
2369}
2370