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