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