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