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