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