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