1 /* rev_hunt.c --- routines to hunt down particular fs revisions and
2 * their properties.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25 #include <string.h>
26 #include "svn_compat.h"
27 #include "svn_private_config.h"
28 #include "svn_hash.h"
29 #include "svn_pools.h"
30 #include "svn_error.h"
31 #include "svn_error_codes.h"
32 #include "svn_fs.h"
33 #include "svn_repos.h"
34 #include "svn_string.h"
35 #include "svn_time.h"
36 #include "svn_sorts.h"
37 #include "svn_props.h"
38 #include "svn_mergeinfo.h"
39 #include "repos.h"
40 #include "private/svn_fspath.h"
41 #include "private/svn_fs_private.h"
42 #include "private/svn_sorts_private.h"
43
44
45 /* Note: this binary search assumes that the datestamp properties on
46 each revision are in chronological order. That is if revision A >
47 revision B, then A's datestamp is younger then B's datestamp.
48
49 If someone comes along and sets a bogus datestamp, this routine
50 might not work right.
51
52 ### todo: you know, we *could* have svn_fs_change_rev_prop() do
53 some semantic checking when it's asked to change special reserved
54 svn: properties. It could prevent such a problem. */
55
56
57 /* helper for svn_repos_dated_revision().
58
59 Set *TM to the apr_time_t datestamp on revision REV in FS. */
60 static svn_error_t *
get_time(apr_time_t * tm,svn_fs_t * fs,svn_revnum_t rev,apr_pool_t * pool)61 get_time(apr_time_t *tm,
62 svn_fs_t *fs,
63 svn_revnum_t rev,
64 apr_pool_t *pool)
65 {
66 svn_string_t *date_str;
67
68 SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
69 pool));
70 if (! date_str)
71 return svn_error_createf
72 (SVN_ERR_FS_GENERAL, NULL,
73 _("Failed to find time on revision %ld"), rev);
74
75 return svn_time_from_cstring(tm, date_str->data, pool);
76 }
77
78
79 svn_error_t *
svn_repos_dated_revision(svn_revnum_t * revision,svn_repos_t * repos,apr_time_t tm,apr_pool_t * pool)80 svn_repos_dated_revision(svn_revnum_t *revision,
81 svn_repos_t *repos,
82 apr_time_t tm,
83 apr_pool_t *pool)
84 {
85 svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
86 apr_time_t this_time;
87 svn_fs_t *fs = repos->fs;
88
89 /* Initialize top and bottom values of binary search. */
90 SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
91 rev_bot = 0;
92 rev_top = rev_latest;
93
94 while (rev_bot <= rev_top)
95 {
96 rev_mid = (rev_top + rev_bot) / 2;
97 SVN_ERR(get_time(&this_time, fs, rev_mid, pool));
98
99 if (this_time > tm)/* we've overshot */
100 {
101 apr_time_t previous_time;
102
103 if ((rev_mid - 1) < 0)
104 {
105 *revision = 0;
106 break;
107 }
108
109 /* see if time falls between rev_mid and rev_mid-1: */
110 SVN_ERR(get_time(&previous_time, fs, rev_mid - 1, pool));
111 if (previous_time <= tm)
112 {
113 *revision = rev_mid - 1;
114 break;
115 }
116
117 rev_top = rev_mid - 1;
118 }
119
120 else if (this_time < tm) /* we've undershot */
121 {
122 apr_time_t next_time;
123
124 if ((rev_mid + 1) > rev_latest)
125 {
126 *revision = rev_latest;
127 break;
128 }
129
130 /* see if time falls between rev_mid and rev_mid+1: */
131 SVN_ERR(get_time(&next_time, fs, rev_mid + 1, pool));
132 if (next_time > tm)
133 {
134 *revision = rev_mid;
135 break;
136 }
137
138 rev_bot = rev_mid + 1;
139 }
140
141 else
142 {
143 *revision = rev_mid; /* exact match! */
144 break;
145 }
146 }
147
148 return SVN_NO_ERROR;
149 }
150
151
152 svn_error_t *
svn_repos_get_committed_info(svn_revnum_t * committed_rev,const char ** committed_date,const char ** last_author,svn_fs_root_t * root,const char * path,apr_pool_t * pool)153 svn_repos_get_committed_info(svn_revnum_t *committed_rev,
154 const char **committed_date,
155 const char **last_author,
156 svn_fs_root_t *root,
157 const char *path,
158 apr_pool_t *pool)
159 {
160 apr_hash_t *revprops;
161
162 svn_fs_t *fs = svn_fs_root_fs(root);
163
164 /* ### It might be simpler just to declare that revision
165 properties have char * (i.e., UTF-8) values, not arbitrary
166 binary values, hmmm. */
167 svn_string_t *committed_date_s, *last_author_s;
168
169 /* Get the CR field out of the node's skel. */
170 SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
171
172 /* Get the revision properties of this revision. */
173 SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
174
175 /* Extract date and author from these revprops. */
176 committed_date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
177 last_author_s = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR);
178
179 *committed_date = committed_date_s ? committed_date_s->data : NULL;
180 *last_author = last_author_s ? last_author_s->data : NULL;
181
182 return SVN_NO_ERROR;
183 }
184
185 svn_error_t *
svn_repos_history2(svn_fs_t * fs,const char * path,svn_repos_history_func_t history_func,void * history_baton,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,svn_revnum_t start,svn_revnum_t end,svn_boolean_t cross_copies,apr_pool_t * pool)186 svn_repos_history2(svn_fs_t *fs,
187 const char *path,
188 svn_repos_history_func_t history_func,
189 void *history_baton,
190 svn_repos_authz_func_t authz_read_func,
191 void *authz_read_baton,
192 svn_revnum_t start,
193 svn_revnum_t end,
194 svn_boolean_t cross_copies,
195 apr_pool_t *pool)
196 {
197 svn_fs_history_t *history;
198 apr_pool_t *oldpool = svn_pool_create(pool);
199 apr_pool_t *newpool = svn_pool_create(pool);
200 const char *history_path;
201 svn_revnum_t history_rev;
202 svn_fs_root_t *root;
203
204 /* Validate the revisions. */
205 if (! SVN_IS_VALID_REVNUM(start))
206 return svn_error_createf
207 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
208 _("Invalid start revision %ld"), start);
209 if (! SVN_IS_VALID_REVNUM(end))
210 return svn_error_createf
211 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
212 _("Invalid end revision %ld"), end);
213
214 /* Ensure that the input is ordered. */
215 if (start > end)
216 {
217 svn_revnum_t tmprev = start;
218 start = end;
219 end = tmprev;
220 }
221
222 /* Get a revision root for END, and an initial HISTORY baton. */
223 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
224
225 if (authz_read_func)
226 {
227 svn_boolean_t readable;
228 SVN_ERR(authz_read_func(&readable, root, path,
229 authz_read_baton, pool));
230 if (! readable)
231 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
232 }
233
234 SVN_ERR(svn_fs_node_history2(&history, root, path, oldpool, oldpool));
235
236 /* Now, we loop over the history items, calling svn_fs_history_prev(). */
237 do
238 {
239 /* Note that we have to do some crazy pool work here. We can't
240 get rid of the old history until we use it to get the new, so
241 we alternate back and forth between our subpools. */
242 apr_pool_t *tmppool;
243 svn_error_t *err;
244
245 SVN_ERR(svn_fs_history_prev2(&history, history, cross_copies, newpool,
246 oldpool));
247
248 /* Only continue if there is further history to deal with. */
249 if (! history)
250 break;
251
252 /* Fetch the location information for this history step. */
253 SVN_ERR(svn_fs_history_location(&history_path, &history_rev,
254 history, newpool));
255
256 /* If this history item predates our START revision, quit
257 here. */
258 if (history_rev < start)
259 break;
260
261 /* Is the history item readable? If not, quit. */
262 if (authz_read_func)
263 {
264 svn_boolean_t readable;
265 svn_fs_root_t *history_root;
266 SVN_ERR(svn_fs_revision_root(&history_root, fs,
267 history_rev, newpool));
268 SVN_ERR(authz_read_func(&readable, history_root, history_path,
269 authz_read_baton, newpool));
270 if (! readable)
271 break;
272 }
273
274 /* Call the user-provided callback function. */
275 err = history_func(history_baton, history_path, history_rev, newpool);
276 if (err)
277 {
278 if (err->apr_err == SVN_ERR_CEASE_INVOCATION)
279 {
280 svn_error_clear(err);
281 goto cleanup;
282 }
283 else
284 {
285 return svn_error_trace(err);
286 }
287 }
288
289 /* We're done with the old history item, so we can clear its
290 pool, and then toggle our notion of "the old pool". */
291 svn_pool_clear(oldpool);
292 tmppool = oldpool;
293 oldpool = newpool;
294 newpool = tmppool;
295 }
296 while (history); /* shouldn't hit this */
297
298 cleanup:
299 svn_pool_destroy(oldpool);
300 svn_pool_destroy(newpool);
301 return SVN_NO_ERROR;
302 }
303
304
305 svn_error_t *
svn_repos_deleted_rev(svn_fs_t * fs,const char * path,svn_revnum_t start,svn_revnum_t end,svn_revnum_t * deleted,apr_pool_t * pool)306 svn_repos_deleted_rev(svn_fs_t *fs,
307 const char *path,
308 svn_revnum_t start,
309 svn_revnum_t end,
310 svn_revnum_t *deleted,
311 apr_pool_t *pool)
312 {
313 apr_pool_t *iterpool;
314 svn_fs_root_t *start_root, *root;
315 svn_revnum_t mid_rev;
316 svn_node_kind_t kind;
317 svn_fs_node_relation_t node_relation;
318
319 /* Validate the revision range. */
320 if (! SVN_IS_VALID_REVNUM(start))
321 return svn_error_createf
322 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
323 _("Invalid start revision %ld"), start);
324 if (! SVN_IS_VALID_REVNUM(end))
325 return svn_error_createf
326 (SVN_ERR_FS_NO_SUCH_REVISION, 0,
327 _("Invalid end revision %ld"), end);
328
329 /* Ensure that the input is ordered. */
330 if (start > end)
331 {
332 svn_revnum_t tmprev = start;
333 start = end;
334 end = tmprev;
335 }
336
337 /* Ensure path exists in fs at start revision. */
338 SVN_ERR(svn_fs_revision_root(&start_root, fs, start, pool));
339 SVN_ERR(svn_fs_check_path(&kind, start_root, path, pool));
340 if (kind == svn_node_none)
341 {
342 /* Path must exist in fs at start rev. */
343 *deleted = SVN_INVALID_REVNUM;
344 return SVN_NO_ERROR;
345 }
346
347 /* Ensure path was deleted at or before end revision. */
348 SVN_ERR(svn_fs_revision_root(&root, fs, end, pool));
349 SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
350 if (kind != svn_node_none)
351 {
352 /* path exists in the end node and the end node is equivalent
353 or otherwise equivalent to the start node. This can mean
354 a few things:
355
356 1) The end node *is* simply the start node, uncopied
357 and unmodified in the start to end range.
358
359 2) The start node was modified, but never copied.
360
361 3) The start node was copied, but this copy occurred at
362 start or some rev *previous* to start, this is
363 effectively the same situation as 1 if the node was
364 never modified or 2 if it was.
365
366 In the first three cases the path was not deleted in
367 the specified range and we are done, but in the following
368 cases the start node must have been deleted at least once:
369
370 4) The start node was deleted and replaced by a copy of
371 itself at some rev between start and end. This copy
372 may itself have been replaced with copies of itself.
373
374 5) The start node was deleted and replaced by a node which
375 it does not share any history with.
376 */
377 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
378 root, path, pool));
379 if (node_relation != svn_fs_node_unrelated)
380 {
381 svn_fs_root_t *copy_root;
382 const char *copy_path;
383 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
384 path, pool));
385 if (!copy_root ||
386 (svn_fs_revision_root_revision(copy_root) <= start))
387 {
388 /* Case 1,2 or 3, nothing more to do. */
389 *deleted = SVN_INVALID_REVNUM;
390 return SVN_NO_ERROR;
391 }
392 }
393 }
394
395 /* If we get here we know that path exists in rev start and was deleted
396 at least once before rev end. To find the revision path was first
397 deleted we use a binary search. The rules for the determining if
398 the deletion comes before or after a given median revision are
399 described by this matrix:
400
401 | Most recent copy event that
402 | caused mid node to exist.
403 |-----------------------------------------------------
404 Compare path | | | |
405 at start and | Copied at | Copied at | Never copied |
406 mid nodes. | rev > start | rev <= start | |
407 | | | |
408 -------------------------------------------------------------------|
409 Mid node is | A) Start node | |
410 equivalent to | replaced with | E) Mid node == start node, |
411 start node | an unmodified | look HIGHER. |
412 | copy of | |
413 | itself, | |
414 | look LOWER. | |
415 -------------------------------------------------------------------|
416 Mid node is | B) Start node | |
417 otherwise | replaced with | F) Mid node is a modified |
418 related to | a modified | version of start node, |
419 start node | copy of | look HIGHER. |
420 | itself, | |
421 | look LOWER. | |
422 -------------------------------------------------------------------|
423 Mid node is | |
424 unrelated to | C) Start node replaced with unrelated mid node, |
425 start node | look LOWER. |
426 | |
427 -------------------------------------------------------------------|
428 Path doesn't | |
429 exist at mid | D) Start node deleted before mid node, |
430 node | look LOWER |
431 | |
432 --------------------------------------------------------------------
433 */
434
435 mid_rev = (start + end) / 2;
436 iterpool = svn_pool_create(pool);
437
438 while (1)
439 {
440 svn_pool_clear(iterpool);
441
442 /* Get revision root and node id for mid_rev at that revision. */
443 SVN_ERR(svn_fs_revision_root(&root, fs, mid_rev, iterpool));
444 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool));
445 if (kind == svn_node_none)
446 {
447 /* Case D: Look lower in the range. */
448 end = mid_rev;
449 mid_rev = (start + mid_rev) / 2;
450 }
451 else
452 {
453 svn_fs_root_t *copy_root;
454 const char *copy_path;
455 /* Determine the relationship between the start node
456 and the current node. */
457 SVN_ERR(svn_fs_node_relation(&node_relation, start_root, path,
458 root, path, iterpool));
459 if (node_relation != svn_fs_node_unrelated)
460 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root,
461 path, iterpool));
462 if (node_relation == svn_fs_node_unrelated ||
463 (copy_root &&
464 (svn_fs_revision_root_revision(copy_root) > start)))
465 {
466 /* Cases A, B, C: Look at lower revs. */
467 end = mid_rev;
468 mid_rev = (start + mid_rev) / 2;
469 }
470 else if (end - mid_rev == 1)
471 {
472 /* Found the node path was deleted. */
473 *deleted = end;
474 break;
475 }
476 else
477 {
478 /* Cases E, F: Look at higher revs. */
479 start = mid_rev;
480 mid_rev = (start + end) / 2;
481 }
482 }
483 }
484
485 svn_pool_destroy(iterpool);
486 return SVN_NO_ERROR;
487 }
488
489
490 /* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
491 unreadable. */
492 static svn_error_t *
check_readability(svn_fs_root_t * root,const char * path,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,apr_pool_t * pool)493 check_readability(svn_fs_root_t *root,
494 const char *path,
495 svn_repos_authz_func_t authz_read_func,
496 void *authz_read_baton,
497 apr_pool_t *pool)
498 {
499 svn_boolean_t readable;
500 SVN_ERR(authz_read_func(&readable, root, path, authz_read_baton, pool));
501 if (! readable)
502 return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL,
503 _("Unreadable path encountered; access denied"));
504 return SVN_NO_ERROR;
505 }
506
507
508 /* The purpose of this function is to discover if fs_path@future_rev
509 * is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
510
511 static svn_error_t *
check_ancestry_of_peg_path(svn_boolean_t * is_ancestor,svn_fs_t * fs,const char * fs_path,svn_revnum_t peg_revision,svn_revnum_t future_revision,apr_pool_t * pool)512 check_ancestry_of_peg_path(svn_boolean_t *is_ancestor,
513 svn_fs_t *fs,
514 const char *fs_path,
515 svn_revnum_t peg_revision,
516 svn_revnum_t future_revision,
517 apr_pool_t *pool)
518 {
519 svn_fs_root_t *root;
520 svn_fs_history_t *history;
521 const char *path = NULL;
522 svn_revnum_t revision;
523 apr_pool_t *lastpool, *currpool;
524
525 lastpool = svn_pool_create(pool);
526 currpool = svn_pool_create(pool);
527
528 SVN_ERR(svn_fs_revision_root(&root, fs, future_revision, pool));
529
530 SVN_ERR(svn_fs_node_history2(&history, root, fs_path, lastpool, lastpool));
531
532 /* Since paths that are different according to strcmp may still be
533 equivalent (due to number of consecutive slashes and the fact that
534 "" is the same as "/"), we get the "canonical" path in the first
535 iteration below so that the comparison after the loop will work
536 correctly. */
537 fs_path = NULL;
538
539 while (1)
540 {
541 apr_pool_t *tmppool;
542
543 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, currpool,
544 lastpool));
545
546 if (!history)
547 break;
548
549 SVN_ERR(svn_fs_history_location(&path, &revision, history, currpool));
550
551 if (!fs_path)
552 fs_path = apr_pstrdup(pool, path);
553
554 if (revision <= peg_revision)
555 break;
556
557 /* Clear old pool and flip. */
558 svn_pool_clear(lastpool);
559 tmppool = lastpool;
560 lastpool = currpool;
561 currpool = tmppool;
562 }
563
564 /* We must have had at least one iteration above where we
565 reassigned fs_path. Else, the path wouldn't have existed at
566 future_revision and svn_fs_history would have thrown. */
567 SVN_ERR_ASSERT(fs_path != NULL);
568
569 *is_ancestor = (history && strcmp(path, fs_path) == 0);
570
571 return SVN_NO_ERROR;
572 }
573
574
575 svn_error_t *
svn_repos__prev_location(svn_revnum_t * appeared_rev,const char ** prev_path,svn_revnum_t * prev_rev,svn_fs_t * fs,svn_revnum_t revision,const char * path,apr_pool_t * pool)576 svn_repos__prev_location(svn_revnum_t *appeared_rev,
577 const char **prev_path,
578 svn_revnum_t *prev_rev,
579 svn_fs_t *fs,
580 svn_revnum_t revision,
581 const char *path,
582 apr_pool_t *pool)
583 {
584 svn_fs_root_t *root, *copy_root;
585 const char *copy_path, *copy_src_path, *remainder;
586 svn_revnum_t copy_src_rev;
587
588 /* Initialize return variables. */
589 if (appeared_rev)
590 *appeared_rev = SVN_INVALID_REVNUM;
591 if (prev_rev)
592 *prev_rev = SVN_INVALID_REVNUM;
593 if (prev_path)
594 *prev_path = NULL;
595
596 /* Ask about the most recent copy which affected PATH@REVISION. If
597 there was no such copy, we're done. */
598 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
599 SVN_ERR(svn_fs_closest_copy(©_root, ©_path, root, path, pool));
600 if (! copy_root)
601 return SVN_NO_ERROR;
602
603 /* Ultimately, it's not the path of the closest copy's source that
604 we care about -- it's our own path's location in the copy source
605 revision. So we'll tack the relative path that expresses the
606 difference between the copy destination and our path in the copy
607 revision onto the copy source path to determine this information.
608
609 In other words, if our path is "/branches/my-branch/foo/bar", and
610 we know that the closest relevant copy was a copy of "/trunk" to
611 "/branches/my-branch", then that relative path under the copy
612 destination is "/foo/bar". Tacking that onto the copy source
613 path tells us that our path was located at "/trunk/foo/bar"
614 before the copy.
615 */
616 SVN_ERR(svn_fs_copied_from(©_src_rev, ©_src_path,
617 copy_root, copy_path, pool));
618 remainder = svn_fspath__skip_ancestor(copy_path, path);
619 if (prev_path)
620 *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
621 if (appeared_rev)
622 *appeared_rev = svn_fs_revision_root_revision(copy_root);
623 if (prev_rev)
624 *prev_rev = copy_src_rev;
625 return SVN_NO_ERROR;
626 }
627
628
629 svn_error_t *
svn_repos_trace_node_locations(svn_fs_t * fs,apr_hash_t ** locations,const char * fs_path,svn_revnum_t peg_revision,const apr_array_header_t * location_revisions_orig,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,apr_pool_t * pool)630 svn_repos_trace_node_locations(svn_fs_t *fs,
631 apr_hash_t **locations,
632 const char *fs_path,
633 svn_revnum_t peg_revision,
634 const apr_array_header_t *location_revisions_orig,
635 svn_repos_authz_func_t authz_read_func,
636 void *authz_read_baton,
637 apr_pool_t *pool)
638 {
639 apr_array_header_t *location_revisions;
640 svn_revnum_t *revision_ptr, *revision_ptr_end;
641 svn_fs_root_t *root;
642 const char *path;
643 svn_revnum_t revision;
644 svn_boolean_t is_ancestor;
645 apr_pool_t *lastpool, *currpool;
646
647 SVN_ERR_ASSERT(location_revisions_orig->elt_size == sizeof(svn_revnum_t));
648
649 /* Ensure that FS_PATH is absolute, because our path-math below will
650 depend on that being the case. */
651 if (*fs_path != '/')
652 fs_path = apr_pstrcat(pool, "/", fs_path, SVN_VA_NULL);
653
654 /* Another sanity check. */
655 if (authz_read_func)
656 {
657 svn_fs_root_t *peg_root;
658 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
659 SVN_ERR(check_readability(peg_root, fs_path,
660 authz_read_func, authz_read_baton, pool));
661 }
662
663 *locations = apr_hash_make(pool);
664
665 /* We flip between two pools in the second loop below. */
666 lastpool = svn_pool_create(pool);
667 currpool = svn_pool_create(pool);
668
669 /* First - let's sort the array of the revisions from the greatest revision
670 * downward, so it will be easier to search on. */
671 location_revisions = apr_array_copy(pool, location_revisions_orig);
672 qsort(location_revisions->elts, location_revisions->nelts,
673 sizeof(*revision_ptr), svn_sort_compare_revisions);
674
675 revision_ptr = (svn_revnum_t *)location_revisions->elts;
676 revision_ptr_end = revision_ptr + location_revisions->nelts;
677
678 /* Ignore revisions R that are younger than the peg_revisions where
679 path@peg_revision is not an ancestor of path@R. */
680 is_ancestor = FALSE;
681 while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
682 {
683 svn_pool_clear(currpool);
684 SVN_ERR(check_ancestry_of_peg_path(&is_ancestor, fs, fs_path,
685 peg_revision, *revision_ptr,
686 currpool));
687 if (is_ancestor)
688 break;
689 ++revision_ptr;
690 }
691
692 revision = is_ancestor ? *revision_ptr : peg_revision;
693 path = fs_path;
694 if (authz_read_func)
695 {
696 SVN_ERR(svn_fs_revision_root(&root, fs, revision, pool));
697 SVN_ERR(check_readability(root, fs_path, authz_read_func,
698 authz_read_baton, pool));
699 }
700
701 while (revision_ptr < revision_ptr_end)
702 {
703 apr_pool_t *tmppool;
704 svn_revnum_t appeared_rev, prev_rev;
705 const char *prev_path;
706
707 /* Find the target of the innermost copy relevant to path@revision.
708 The copy may be of path itself, or of a parent directory. */
709 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
710 fs, revision, path, currpool));
711 if (! prev_path)
712 break;
713
714 /* Assign the current path to all younger revisions until we reach
715 the copy target rev. */
716 while ((revision_ptr < revision_ptr_end)
717 && (*revision_ptr >= appeared_rev))
718 {
719 /* *revision_ptr is allocated out of pool, so we can point
720 to in the hash table. */
721 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
722 apr_pstrdup(pool, path));
723 revision_ptr++;
724 }
725
726 /* Ignore all revs between the copy target rev and the copy
727 source rev (non-inclusive). */
728 while ((revision_ptr < revision_ptr_end)
729 && (*revision_ptr > prev_rev))
730 revision_ptr++;
731
732 /* State update. */
733 path = prev_path;
734 revision = prev_rev;
735
736 if (authz_read_func)
737 {
738 svn_boolean_t readable;
739 SVN_ERR(svn_fs_revision_root(&root, fs, revision, currpool));
740 SVN_ERR(authz_read_func(&readable, root, path,
741 authz_read_baton, currpool));
742 if (!readable)
743 {
744 svn_pool_destroy(lastpool);
745 svn_pool_destroy(currpool);
746 return SVN_NO_ERROR;
747 }
748 }
749
750 /* Clear last pool and switch. */
751 svn_pool_clear(lastpool);
752 tmppool = lastpool;
753 lastpool = currpool;
754 currpool = tmppool;
755 }
756
757 /* There are no copies relevant to path@revision. So any remaining
758 revisions either predate the creation of path@revision or have
759 the node existing at the same path. We will look up path@lrev
760 for each remaining location-revision and make sure it is related
761 to path@revision. */
762 SVN_ERR(svn_fs_revision_root(&root, fs, revision, lastpool));
763 while (revision_ptr < revision_ptr_end)
764 {
765 svn_node_kind_t kind;
766 svn_fs_node_relation_t node_relation;
767 svn_fs_root_t *cur_rev_root;
768
769 svn_pool_clear(currpool);
770 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs, *revision_ptr,
771 currpool));
772 SVN_ERR(svn_fs_check_path(&kind, cur_rev_root, path, currpool));
773 if (kind == svn_node_none)
774 break;
775 SVN_ERR(svn_fs_node_relation(&node_relation, root, path,
776 cur_rev_root, path, currpool));
777 if (node_relation == svn_fs_node_unrelated)
778 break;
779
780 /* The node exists at the same path; record that and advance. */
781 apr_hash_set(*locations, revision_ptr, sizeof(*revision_ptr),
782 apr_pstrdup(pool, path));
783 revision_ptr++;
784 }
785
786 /* Ignore any remaining location-revisions; they predate the
787 creation of path@revision. */
788
789 svn_pool_destroy(lastpool);
790 svn_pool_destroy(currpool);
791
792 return SVN_NO_ERROR;
793 }
794
795
796 /* Transmit SEGMENT through RECEIVER/RECEIVER_BATON iff a portion of
797 its revision range fits between END_REV and START_REV, possibly
798 cropping the range so that it fits *entirely* in that range. */
799 static svn_error_t *
maybe_crop_and_send_segment(svn_location_segment_t * segment,svn_revnum_t start_rev,svn_revnum_t end_rev,svn_location_segment_receiver_t receiver,void * receiver_baton,apr_pool_t * pool)800 maybe_crop_and_send_segment(svn_location_segment_t *segment,
801 svn_revnum_t start_rev,
802 svn_revnum_t end_rev,
803 svn_location_segment_receiver_t receiver,
804 void *receiver_baton,
805 apr_pool_t *pool)
806 {
807 /* We only want to transmit this segment if some portion of it
808 is between our END_REV and START_REV. */
809 if (! ((segment->range_start > start_rev)
810 || (segment->range_end < end_rev)))
811 {
812 /* Correct our segment range when the range straddles one of
813 our requested revision boundaries. */
814 if (segment->range_start < end_rev)
815 segment->range_start = end_rev;
816 if (segment->range_end > start_rev)
817 segment->range_end = start_rev;
818 SVN_ERR(receiver(segment, receiver_baton, pool));
819 }
820 return SVN_NO_ERROR;
821 }
822
823
824 svn_error_t *
svn_repos_node_location_segments(svn_repos_t * repos,const char * path,svn_revnum_t peg_revision,svn_revnum_t start_rev,svn_revnum_t end_rev,svn_location_segment_receiver_t receiver,void * receiver_baton,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,apr_pool_t * pool)825 svn_repos_node_location_segments(svn_repos_t *repos,
826 const char *path,
827 svn_revnum_t peg_revision,
828 svn_revnum_t start_rev,
829 svn_revnum_t end_rev,
830 svn_location_segment_receiver_t receiver,
831 void *receiver_baton,
832 svn_repos_authz_func_t authz_read_func,
833 void *authz_read_baton,
834 apr_pool_t *pool)
835 {
836 svn_fs_t *fs = svn_repos_fs(repos);
837 svn_stringbuf_t *current_path;
838 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM, current_rev;
839 apr_pool_t *subpool;
840
841 /* No PEG_REVISION? We'll use HEAD. */
842 if (! SVN_IS_VALID_REVNUM(peg_revision))
843 {
844 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
845 peg_revision = youngest_rev;
846 }
847
848 /* No START_REV? We'll use HEAD (which we may have already fetched). */
849 if (! SVN_IS_VALID_REVNUM(start_rev))
850 {
851 if (SVN_IS_VALID_REVNUM(youngest_rev))
852 start_rev = youngest_rev;
853 else
854 SVN_ERR(svn_fs_youngest_rev(&start_rev, fs, pool));
855 }
856
857 /* No END_REV? We'll use 0. */
858 end_rev = SVN_IS_VALID_REVNUM(end_rev) ? end_rev : 0;
859
860 /* Are the revision properly ordered? They better be -- the API
861 demands it. */
862 SVN_ERR_ASSERT(end_rev <= start_rev);
863 SVN_ERR_ASSERT(start_rev <= peg_revision);
864
865 /* Ensure that PATH is absolute, because our path-math will depend
866 on that being the case. */
867 if (*path != '/')
868 path = apr_pstrcat(pool, "/", path, SVN_VA_NULL);
869
870 /* Auth check. */
871 if (authz_read_func)
872 {
873 svn_fs_root_t *peg_root;
874 SVN_ERR(svn_fs_revision_root(&peg_root, fs, peg_revision, pool));
875 SVN_ERR(check_readability(peg_root, path,
876 authz_read_func, authz_read_baton, pool));
877 }
878
879 /* Okay, let's get searching! */
880 subpool = svn_pool_create(pool);
881 current_rev = peg_revision;
882 current_path = svn_stringbuf_create(path, pool);
883 while (current_rev >= end_rev)
884 {
885 svn_revnum_t appeared_rev, prev_rev;
886 const char *cur_path, *prev_path;
887 svn_location_segment_t *segment;
888
889 svn_pool_clear(subpool);
890
891 cur_path = apr_pstrmemdup(subpool, current_path->data,
892 current_path->len);
893 segment = apr_pcalloc(subpool, sizeof(*segment));
894 segment->range_end = current_rev;
895 segment->range_start = end_rev;
896 /* segment path should be absolute without leading '/'. */
897 segment->path = cur_path + 1;
898
899 SVN_ERR(svn_repos__prev_location(&appeared_rev, &prev_path, &prev_rev,
900 fs, current_rev, cur_path, subpool));
901
902 /* If there are no previous locations for this thing (meaning,
903 it originated at the current path), then we simply need to
904 find its revision of origin to populate our final segment.
905 Otherwise, the APPEARED_REV is the start of current segment's
906 range. */
907 if (! prev_path)
908 {
909 svn_fs_root_t *revroot;
910 SVN_ERR(svn_fs_revision_root(&revroot, fs, current_rev, subpool));
911 SVN_ERR(svn_fs_node_origin_rev(&(segment->range_start), revroot,
912 cur_path, subpool));
913 if (segment->range_start < end_rev)
914 segment->range_start = end_rev;
915 current_rev = SVN_INVALID_REVNUM;
916 }
917 else
918 {
919 segment->range_start = appeared_rev;
920 svn_stringbuf_set(current_path, prev_path);
921 current_rev = prev_rev;
922 }
923
924 /* Report our segment, providing it passes authz muster. */
925 if (authz_read_func)
926 {
927 svn_boolean_t readable;
928 svn_fs_root_t *cur_rev_root;
929
930 /* authz_read_func requires path to have a leading slash. */
931 const char *abs_path = apr_pstrcat(subpool, "/", segment->path,
932 SVN_VA_NULL);
933
934 SVN_ERR(svn_fs_revision_root(&cur_rev_root, fs,
935 segment->range_end, subpool));
936 SVN_ERR(authz_read_func(&readable, cur_rev_root, abs_path,
937 authz_read_baton, subpool));
938 if (! readable)
939 return SVN_NO_ERROR;
940 }
941
942 /* Transmit the segment (if it's within the scope of our concern). */
943 SVN_ERR(maybe_crop_and_send_segment(segment, start_rev, end_rev,
944 receiver, receiver_baton, subpool));
945
946 /* If we've set CURRENT_REV to SVN_INVALID_REVNUM, we're done
947 (and didn't ever reach END_REV). */
948 if (! SVN_IS_VALID_REVNUM(current_rev))
949 break;
950
951 /* If there's a gap in the history, we need to report as much
952 (if the gap is within the scope of our concern). */
953 if (segment->range_start - current_rev > 1)
954 {
955 svn_location_segment_t *gap_segment;
956 gap_segment = apr_pcalloc(subpool, sizeof(*gap_segment));
957 gap_segment->range_end = segment->range_start - 1;
958 gap_segment->range_start = current_rev + 1;
959 gap_segment->path = NULL;
960 SVN_ERR(maybe_crop_and_send_segment(gap_segment, start_rev, end_rev,
961 receiver, receiver_baton,
962 subpool));
963 }
964 }
965 svn_pool_destroy(subpool);
966 return SVN_NO_ERROR;
967 }
968
969 static APR_INLINE svn_boolean_t
is_path_in_hash(apr_hash_t * duplicate_path_revs,const char * path,svn_revnum_t revision,apr_pool_t * pool)970 is_path_in_hash(apr_hash_t *duplicate_path_revs,
971 const char *path,
972 svn_revnum_t revision,
973 apr_pool_t *pool)
974 {
975 const char *key = apr_psprintf(pool, "%s:%ld", path, revision);
976 void *ptr;
977
978 ptr = svn_hash_gets(duplicate_path_revs, key);
979 return ptr != NULL;
980 }
981
982 struct path_revision
983 {
984 svn_revnum_t revnum;
985 const char *path;
986
987 /* Does this path_rev have merges to also be included? If so, this is
988 the union of both additions and (negated) deletions of mergeinfo. */
989 apr_hash_t *merged_mergeinfo;
990
991 /* Is this a merged revision? */
992 svn_boolean_t merged;
993 };
994
995 /* Check for merges in OLD_PATH_REV->PATH at OLD_PATH_REV->REVNUM. Store
996 the mergeinfo difference in *MERGED_MERGEINFO, allocated in POOL. The
997 difference is the union of both additions and (negated) deletions. The
998 returned *MERGED_MERGEINFO will be NULL if there are no changes. */
999 static svn_error_t *
get_merged_mergeinfo(apr_hash_t ** merged_mergeinfo,svn_repos_t * repos,struct path_revision * old_path_rev,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1000 get_merged_mergeinfo(apr_hash_t **merged_mergeinfo,
1001 svn_repos_t *repos,
1002 struct path_revision *old_path_rev,
1003 apr_pool_t *result_pool,
1004 apr_pool_t *scratch_pool)
1005 {
1006 apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
1007 svn_error_t *err;
1008 svn_fs_root_t *root, *prev_root;
1009 apr_hash_t *changed_paths;
1010 const char *path = old_path_rev->path;
1011
1012 /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
1013 if there is a property change. */
1014 SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
1015 scratch_pool));
1016 SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
1017 while (1)
1018 {
1019 svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
1020 if (changed_path && changed_path->prop_mod
1021 && changed_path->mergeinfo_mod != svn_tristate_false)
1022 break;
1023 if (svn_fspath__is_root(path, strlen(path)))
1024 {
1025 *merged_mergeinfo = NULL;
1026 return SVN_NO_ERROR;
1027 }
1028 path = svn_fspath__dirname(path, scratch_pool);
1029 }
1030
1031 /* First, find the mergeinfo difference for old_path_rev->revnum, and
1032 old_path_rev->revnum - 1. */
1033 /* We do not need to call svn_repos_fs_get_mergeinfo() (which performs authz)
1034 because we will filter out unreadable revisions in
1035 find_interesting_revision() */
1036 err = svn_fs__get_mergeinfo_for_path(&curr_mergeinfo,
1037 root, old_path_rev->path,
1038 svn_mergeinfo_inherited, TRUE,
1039 scratch_pool, scratch_pool);
1040 if (err)
1041 {
1042 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1043 {
1044 /* Issue #3896: If invalid mergeinfo is encountered the
1045 best we can do is ignore it and act is if there are
1046 no mergeinfo differences. */
1047 svn_error_clear(err);
1048 *merged_mergeinfo = NULL;
1049 return SVN_NO_ERROR;
1050 }
1051 else
1052 {
1053 return svn_error_trace(err);
1054 }
1055 }
1056
1057 SVN_ERR(svn_fs_revision_root(&prev_root, repos->fs, old_path_rev->revnum - 1,
1058 scratch_pool));
1059 err = svn_fs__get_mergeinfo_for_path(&prev_mergeinfo,
1060 prev_root, old_path_rev->path,
1061 svn_mergeinfo_inherited, TRUE,
1062 scratch_pool, scratch_pool);
1063 if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND
1064 || err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR))
1065 {
1066 /* If the path doesn't exist in the previous revision or it does exist
1067 but has invalid mergeinfo (Issue #3896), assume no merges. */
1068 svn_error_clear(err);
1069 *merged_mergeinfo = NULL;
1070 return SVN_NO_ERROR;
1071 }
1072 else
1073 SVN_ERR(err);
1074
1075 /* Then calculate and merge the differences, combining additions and
1076 (negated) deletions as all positive changes in CHANGES. */
1077 SVN_ERR(svn_mergeinfo_diff2(&deleted, &changed, prev_mergeinfo,
1078 curr_mergeinfo, FALSE, result_pool,
1079 scratch_pool));
1080 SVN_ERR(svn_mergeinfo_merge2(changed, deleted, result_pool, scratch_pool));
1081
1082 /* Store the result. */
1083 if (apr_hash_count(changed))
1084 *merged_mergeinfo = changed;
1085 else
1086 *merged_mergeinfo = NULL;
1087
1088 return SVN_NO_ERROR;
1089 }
1090
1091 static svn_error_t *
find_interesting_revisions(apr_array_header_t * path_revisions,svn_repos_t * repos,const char * path,svn_revnum_t start,svn_revnum_t end,svn_boolean_t include_merged_revisions,svn_boolean_t mark_as_merged,apr_hash_t * duplicate_path_revs,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1092 find_interesting_revisions(apr_array_header_t *path_revisions,
1093 svn_repos_t *repos,
1094 const char *path,
1095 svn_revnum_t start,
1096 svn_revnum_t end,
1097 svn_boolean_t include_merged_revisions,
1098 svn_boolean_t mark_as_merged,
1099 apr_hash_t *duplicate_path_revs,
1100 svn_repos_authz_func_t authz_read_func,
1101 void *authz_read_baton,
1102 apr_pool_t *result_pool,
1103 apr_pool_t *scratch_pool)
1104 {
1105 apr_pool_t *iterpool, *last_pool;
1106 svn_fs_history_t *history;
1107 svn_fs_root_t *root;
1108 svn_node_kind_t kind;
1109
1110 /* We switch between two pools while looping, since we need information from
1111 the last iteration to be available. */
1112 iterpool = svn_pool_create(scratch_pool);
1113 last_pool = svn_pool_create(scratch_pool);
1114
1115 /* The path had better be a file in this revision. */
1116 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1117 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1118 if (kind != svn_node_file)
1119 return svn_error_createf
1120 (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file in revision %ld"),
1121 path, end);
1122
1123 /* Open a history object. */
1124 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool,
1125 scratch_pool));
1126 while (1)
1127 {
1128 struct path_revision *path_rev;
1129 svn_revnum_t tmp_revnum;
1130 const char *tmp_path;
1131 apr_pool_t *tmp_pool;
1132
1133 svn_pool_clear(iterpool);
1134
1135 /* Fetch the history object to walk through. */
1136 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
1137 iterpool));
1138 if (!history)
1139 break;
1140 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1141 history, iterpool));
1142
1143 /* Check to see if we already saw this path (and it's ancestors) */
1144 if (include_merged_revisions
1145 && is_path_in_hash(duplicate_path_revs, tmp_path,
1146 tmp_revnum, iterpool))
1147 break;
1148
1149 /* Check authorization. */
1150 if (authz_read_func)
1151 {
1152 svn_boolean_t readable;
1153 svn_fs_root_t *tmp_root;
1154
1155 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1156 iterpool));
1157 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1158 authz_read_baton, iterpool));
1159 if (! readable)
1160 break;
1161 }
1162
1163 /* We didn't break, so we must really want this path-rev. */
1164 path_rev = apr_palloc(result_pool, sizeof(*path_rev));
1165 path_rev->path = apr_pstrdup(result_pool, tmp_path);
1166 path_rev->revnum = tmp_revnum;
1167 path_rev->merged = mark_as_merged;
1168 APR_ARRAY_PUSH(path_revisions, struct path_revision *) = path_rev;
1169
1170 if (include_merged_revisions)
1171 SVN_ERR(get_merged_mergeinfo(&path_rev->merged_mergeinfo, repos,
1172 path_rev, result_pool, iterpool));
1173 else
1174 path_rev->merged_mergeinfo = NULL;
1175
1176 /* Add the path/rev pair to the hash, so we can filter out future
1177 occurrences of it. We only care about this if including merged
1178 revisions, 'cause that's the only time we can have duplicates. */
1179 svn_hash_sets(duplicate_path_revs,
1180 apr_psprintf(result_pool, "%s:%ld", path_rev->path,
1181 path_rev->revnum),
1182 (void *)0xdeadbeef);
1183
1184 if (path_rev->revnum <= start)
1185 break;
1186
1187 /* Swap pools. */
1188 tmp_pool = iterpool;
1189 iterpool = last_pool;
1190 last_pool = tmp_pool;
1191 }
1192
1193 svn_pool_destroy(iterpool);
1194 svn_pool_destroy(last_pool);
1195
1196 return SVN_NO_ERROR;
1197 }
1198
1199 /* Comparison function to sort path/revisions in increasing order */
1200 static int
compare_path_revisions(const void * a,const void * b)1201 compare_path_revisions(const void *a, const void *b)
1202 {
1203 struct path_revision *a_pr = *(struct path_revision *const *)a;
1204 struct path_revision *b_pr = *(struct path_revision *const *)b;
1205
1206 if (a_pr->revnum == b_pr->revnum)
1207 return 0;
1208
1209 return a_pr->revnum < b_pr->revnum ? 1 : -1;
1210 }
1211
1212 static svn_error_t *
find_merged_revisions(apr_array_header_t ** merged_path_revisions_out,svn_revnum_t start,const apr_array_header_t * mainline_path_revisions,svn_repos_t * repos,apr_hash_t * duplicate_path_revs,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1213 find_merged_revisions(apr_array_header_t **merged_path_revisions_out,
1214 svn_revnum_t start,
1215 const apr_array_header_t *mainline_path_revisions,
1216 svn_repos_t *repos,
1217 apr_hash_t *duplicate_path_revs,
1218 svn_repos_authz_func_t authz_read_func,
1219 void *authz_read_baton,
1220 apr_pool_t *result_pool,
1221 apr_pool_t *scratch_pool)
1222 {
1223 const apr_array_header_t *old;
1224 apr_array_header_t *new_merged_path_revs;
1225 apr_pool_t *iterpool, *last_pool;
1226 apr_array_header_t *merged_path_revisions =
1227 apr_array_make(scratch_pool, 0, sizeof(struct path_revision *));
1228
1229 old = mainline_path_revisions;
1230 iterpool = svn_pool_create(scratch_pool);
1231 last_pool = svn_pool_create(scratch_pool);
1232
1233 do
1234 {
1235 int i;
1236 apr_pool_t *temp_pool;
1237
1238 svn_pool_clear(iterpool);
1239 new_merged_path_revs = apr_array_make(iterpool, 0,
1240 sizeof(struct path_revision *));
1241
1242 /* Iterate over OLD, checking for non-empty mergeinfo. If found, gather
1243 path_revisions for any merged revisions, and store those in NEW. */
1244 for (i = 0; i < old->nelts; i++)
1245 {
1246 apr_pool_t *iterpool2;
1247 apr_hash_index_t *hi;
1248 struct path_revision *old_pr = APR_ARRAY_IDX(old, i,
1249 struct path_revision *);
1250 if (!old_pr->merged_mergeinfo)
1251 continue;
1252
1253 iterpool2 = svn_pool_create(iterpool);
1254
1255 /* Determine and trace the merge sources. */
1256 for (hi = apr_hash_first(iterpool, old_pr->merged_mergeinfo); hi;
1257 hi = apr_hash_next(hi))
1258 {
1259 const char *path = apr_hash_this_key(hi);
1260 svn_rangelist_t *rangelist = apr_hash_this_val(hi);
1261 apr_pool_t *iterpool3;
1262 int j;
1263
1264 svn_pool_clear(iterpool2);
1265 iterpool3 = svn_pool_create(iterpool2);
1266
1267 for (j = 0; j < rangelist->nelts; j++)
1268 {
1269 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, j,
1270 svn_merge_range_t *);
1271 svn_node_kind_t kind;
1272 svn_fs_root_t *root;
1273
1274 if (range->end < start)
1275 continue;
1276
1277 svn_pool_clear(iterpool3);
1278
1279 SVN_ERR(svn_fs_revision_root(&root, repos->fs, range->end,
1280 iterpool3));
1281 SVN_ERR(svn_fs_check_path(&kind, root, path, iterpool3));
1282 if (kind != svn_node_file)
1283 continue;
1284
1285 /* Search and find revisions to add to the NEW list. */
1286 SVN_ERR(find_interesting_revisions(new_merged_path_revs,
1287 repos, path,
1288 range->start, range->end,
1289 TRUE, TRUE,
1290 duplicate_path_revs,
1291 authz_read_func,
1292 authz_read_baton,
1293 result_pool, iterpool3));
1294 }
1295 svn_pool_destroy(iterpool3);
1296 }
1297 svn_pool_destroy(iterpool2);
1298 }
1299
1300 /* Append the newly found path revisions with the old ones. */
1301 merged_path_revisions = apr_array_append(iterpool, merged_path_revisions,
1302 new_merged_path_revs);
1303
1304 /* Swap data structures */
1305 old = new_merged_path_revs;
1306 temp_pool = last_pool;
1307 last_pool = iterpool;
1308 iterpool = temp_pool;
1309 }
1310 while (new_merged_path_revs->nelts > 0);
1311
1312 /* Sort MERGED_PATH_REVISIONS in increasing order by REVNUM. */
1313 svn_sort__array(merged_path_revisions, compare_path_revisions);
1314
1315 /* Copy to the output array. */
1316 *merged_path_revisions_out = apr_array_copy(result_pool,
1317 merged_path_revisions);
1318
1319 svn_pool_destroy(iterpool);
1320 svn_pool_destroy(last_pool);
1321
1322 return SVN_NO_ERROR;
1323 }
1324
1325 struct send_baton
1326 {
1327 apr_pool_t *iterpool;
1328 apr_pool_t *last_pool;
1329 apr_hash_t *last_props;
1330 const char *last_path;
1331 svn_fs_root_t *last_root;
1332 svn_boolean_t include_merged_revisions;
1333 };
1334
1335 /* Send PATH_REV to HANDLER and HANDLER_BATON, using information provided by
1336 SB. */
1337 static svn_error_t *
send_path_revision(struct path_revision * path_rev,svn_repos_t * repos,struct send_baton * sb,svn_file_rev_handler_t handler,void * handler_baton)1338 send_path_revision(struct path_revision *path_rev,
1339 svn_repos_t *repos,
1340 struct send_baton *sb,
1341 svn_file_rev_handler_t handler,
1342 void *handler_baton)
1343 {
1344 apr_hash_t *rev_props;
1345 apr_hash_t *props;
1346 apr_array_header_t *prop_diffs;
1347 svn_fs_root_t *root;
1348 svn_txdelta_stream_t *delta_stream;
1349 svn_txdelta_window_handler_t delta_handler = NULL;
1350 void *delta_baton = NULL;
1351 apr_pool_t *tmp_pool; /* For swapping */
1352 svn_boolean_t contents_changed;
1353
1354 svn_pool_clear(sb->iterpool);
1355
1356 /* Get the revision properties. */
1357 SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
1358 path_rev->revnum, sb->iterpool));
1359
1360 /* Open the revision root. */
1361 SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
1362 sb->iterpool));
1363
1364 /* Get the file's properties for this revision and compute the diffs. */
1365 SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
1366 sb->iterpool));
1367 SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
1368 sb->iterpool));
1369
1370 /* Check if the contents *may* have changed. */
1371 if (! sb->last_root)
1372 {
1373 /* Special case: In the first revision, we always provide a delta. */
1374 contents_changed = TRUE;
1375 }
1376 else if (sb->include_merged_revisions
1377 && strcmp(sb->last_path, path_rev->path))
1378 {
1379 /* ### This is a HACK!!!
1380 * Blame -g, in older clients anyways, relies on getting a notification
1381 * whenever the path changes - even if there was no content change.
1382 *
1383 * TODO: A future release should take an extra parameter and depending
1384 * on that either always send a text delta or only send it if there
1385 * is a difference. */
1386 contents_changed = TRUE;
1387 }
1388 else
1389 {
1390 /* Did the file contents actually change?
1391 * It could e.g. be a property-only change. */
1392 SVN_ERR(svn_fs_contents_different(&contents_changed, sb->last_root,
1393 sb->last_path, root, path_rev->path,
1394 sb->iterpool));
1395 }
1396
1397 /* We have all we need, give to the handler. */
1398 SVN_ERR(handler(handler_baton, path_rev->path, path_rev->revnum,
1399 rev_props, path_rev->merged,
1400 contents_changed ? &delta_handler : NULL,
1401 contents_changed ? &delta_baton : NULL,
1402 prop_diffs, sb->iterpool));
1403
1404 /* Compute and send delta if client asked for it.
1405 Note that this was initialized to NULL, so if !contents_changed,
1406 no deltas will be computed. */
1407 if (delta_handler && delta_handler != svn_delta_noop_window_handler)
1408 {
1409 /* Get the content delta. */
1410 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream,
1411 sb->last_root, sb->last_path,
1412 root, path_rev->path,
1413 sb->iterpool));
1414 /* And send. */
1415 SVN_ERR(svn_txdelta_send_txstream(delta_stream,
1416 delta_handler, delta_baton,
1417 sb->iterpool));
1418 }
1419
1420 /* Remember root, path and props for next iteration. */
1421 sb->last_root = root;
1422 sb->last_path = path_rev->path;
1423 sb->last_props = props;
1424
1425 /* Swap the pools. */
1426 tmp_pool = sb->iterpool;
1427 sb->iterpool = sb->last_pool;
1428 sb->last_pool = tmp_pool;
1429
1430 return SVN_NO_ERROR;
1431 }
1432
1433 /* Similar to svn_repos_get_file_revs2() but returns paths while walking
1434 history instead of after collecting all history.
1435
1436 This allows implementing clients to immediately start processing and
1437 stop when they got the information they need. (E.g. all or a specific set
1438 of lines were modified) */
1439 static svn_error_t *
get_file_revs_backwards(svn_repos_t * repos,const char * path,svn_revnum_t start,svn_revnum_t end,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,svn_file_rev_handler_t handler,void * handler_baton,apr_pool_t * scratch_pool)1440 get_file_revs_backwards(svn_repos_t *repos,
1441 const char *path,
1442 svn_revnum_t start,
1443 svn_revnum_t end,
1444 svn_repos_authz_func_t authz_read_func,
1445 void *authz_read_baton,
1446 svn_file_rev_handler_t handler,
1447 void *handler_baton,
1448 apr_pool_t *scratch_pool)
1449 {
1450 apr_pool_t *iterpool, *last_pool;
1451 svn_fs_history_t *history;
1452 svn_fs_root_t *root;
1453 svn_node_kind_t kind;
1454 struct send_baton sb;
1455
1456 /* We switch between two pools while looping and so does the path-rev
1457 handler for actually reported revisions. We do this as we
1458 need just information from last iteration to be available. */
1459
1460 iterpool = svn_pool_create(scratch_pool);
1461 last_pool = svn_pool_create(scratch_pool);
1462 sb.iterpool = svn_pool_create(scratch_pool);
1463 sb.last_pool = svn_pool_create(scratch_pool);
1464 sb.include_merged_revisions = FALSE;
1465
1466 /* We want the first txdelta to be against the empty file. */
1467 sb.last_root = NULL;
1468 sb.last_path = NULL;
1469
1470 /* Create an empty hash table for the first property diff. */
1471 sb.last_props = apr_hash_make(sb.last_pool);
1472
1473 /* The path had better be a file in this revision. */
1474 SVN_ERR(svn_fs_revision_root(&root, repos->fs, end, scratch_pool));
1475 SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
1476 if (kind != svn_node_file)
1477 return svn_error_createf(SVN_ERR_FS_NOT_FILE,
1478 NULL, _("'%s' is not a file in revision %ld"),
1479 path, end);
1480
1481 /* Open a history object. */
1482 SVN_ERR(svn_fs_node_history2(&history, root, path, scratch_pool, iterpool));
1483 while (1)
1484 {
1485 struct path_revision *path_rev;
1486 svn_revnum_t tmp_revnum;
1487 const char *tmp_path;
1488
1489 svn_pool_clear(iterpool);
1490
1491 /* Fetch the history object to walk through. */
1492 SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
1493 iterpool));
1494 if (!history)
1495 break;
1496 SVN_ERR(svn_fs_history_location(&tmp_path, &tmp_revnum,
1497 history, iterpool));
1498
1499 /* Check authorization. */
1500 if (authz_read_func)
1501 {
1502 svn_boolean_t readable;
1503 svn_fs_root_t *tmp_root;
1504
1505 SVN_ERR(svn_fs_revision_root(&tmp_root, repos->fs, tmp_revnum,
1506 iterpool));
1507 SVN_ERR(authz_read_func(&readable, tmp_root, tmp_path,
1508 authz_read_baton, iterpool));
1509 if (! readable)
1510 break;
1511 }
1512
1513 /* We didn't break, so we must really want this path-rev. */
1514 path_rev = apr_palloc(iterpool, sizeof(*path_rev));
1515 path_rev->path = tmp_path;
1516 path_rev->revnum = tmp_revnum;
1517 path_rev->merged = FALSE;
1518
1519 SVN_ERR(send_path_revision(path_rev, repos, &sb,
1520 handler, handler_baton));
1521
1522 if (path_rev->revnum <= start)
1523 break;
1524
1525 /* Swap pools. */
1526 {
1527 apr_pool_t *tmp_pool = iterpool;
1528 iterpool = last_pool;
1529 last_pool = tmp_pool;
1530 }
1531 }
1532
1533 svn_pool_destroy(iterpool);
1534 svn_pool_destroy(last_pool);
1535 svn_pool_destroy(sb.last_pool);
1536 svn_pool_destroy(sb.iterpool);
1537
1538 return SVN_NO_ERROR;
1539
1540 }
1541
1542
1543 /* We don't yet support sending revisions in reverse order; the caller wait
1544 * until we've traced back through the entire history, and then accept
1545 * them from oldest to youngest. Someday this may change, but in the meantime,
1546 * the general algorithm is thus:
1547 *
1548 * 1) Trace back through the history of an object, adding each revision
1549 * found to the MAINLINE_PATH_REVISIONS array, marking any which were
1550 * merges.
1551 * 2) If INCLUDE_MERGED_REVISIONS is TRUE, we repeat Step 1 on each of the
1552 * merged revisions, including them in the MERGED_PATH_REVISIONS, and using
1553 * DUPLICATE_PATH_REVS to avoid tracing the same paths of history multiple
1554 * times.
1555 * 3) Send both MAINLINE_PATH_REVISIONS and MERGED_PATH_REVISIONS from
1556 * oldest to youngest, interleaving as appropriate. This is implemented
1557 * similar to an insertion sort, but instead of inserting into another
1558 * array, we just call the appropriate handler.
1559 *
1560 * 2013-02: Added a very simple reverse for mainline only changes. Before this,
1561 * this would return an error (path not found) or just the first
1562 * revision before end.
1563 */
1564 svn_error_t *
svn_repos_get_file_revs2(svn_repos_t * repos,const char * path,svn_revnum_t start,svn_revnum_t end,svn_boolean_t include_merged_revisions,svn_repos_authz_func_t authz_read_func,void * authz_read_baton,svn_file_rev_handler_t handler,void * handler_baton,apr_pool_t * scratch_pool)1565 svn_repos_get_file_revs2(svn_repos_t *repos,
1566 const char *path,
1567 svn_revnum_t start,
1568 svn_revnum_t end,
1569 svn_boolean_t include_merged_revisions,
1570 svn_repos_authz_func_t authz_read_func,
1571 void *authz_read_baton,
1572 svn_file_rev_handler_t handler,
1573 void *handler_baton,
1574 apr_pool_t *scratch_pool)
1575 {
1576 apr_array_header_t *mainline_path_revisions, *merged_path_revisions;
1577 apr_hash_t *duplicate_path_revs;
1578 struct send_baton sb;
1579 int mainline_pos, merged_pos;
1580
1581 if (!SVN_IS_VALID_REVNUM(start)
1582 || !SVN_IS_VALID_REVNUM(end))
1583 {
1584 svn_revnum_t youngest_rev;
1585 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, repos->fs, scratch_pool));
1586
1587 if (!SVN_IS_VALID_REVNUM(start))
1588 start = youngest_rev;
1589 if (!SVN_IS_VALID_REVNUM(end))
1590 end = youngest_rev;
1591 }
1592
1593 if (end < start)
1594 {
1595 if (include_merged_revisions)
1596 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL);
1597
1598 return svn_error_trace(
1599 get_file_revs_backwards(repos, path,
1600 end, start,
1601 authz_read_func,
1602 authz_read_baton,
1603 handler,
1604 handler_baton,
1605 scratch_pool));
1606 }
1607
1608 /* We switch between two pools while looping, since we need information from
1609 the last iteration to be available. */
1610 sb.iterpool = svn_pool_create(scratch_pool);
1611 sb.last_pool = svn_pool_create(scratch_pool);
1612
1613 /* We want the first txdelta to be against the empty file. */
1614 sb.last_root = NULL;
1615 sb.last_path = NULL;
1616
1617 /* Create an empty hash table for the first property diff. */
1618 sb.last_props = apr_hash_make(sb.last_pool);
1619
1620 /* Inform send_path_revision() whether workarounds / special behavior
1621 * may be needed. */
1622 sb.include_merged_revisions = include_merged_revisions;
1623
1624 /* Get the revisions we are interested in. */
1625 duplicate_path_revs = apr_hash_make(scratch_pool);
1626 mainline_path_revisions = apr_array_make(scratch_pool, 100,
1627 sizeof(struct path_revision *));
1628 SVN_ERR(find_interesting_revisions(mainline_path_revisions, repos, path,
1629 start, end, include_merged_revisions,
1630 FALSE, duplicate_path_revs,
1631 authz_read_func, authz_read_baton,
1632 scratch_pool, sb.iterpool));
1633
1634 /* If we are including merged revisions, go get those, too. */
1635 if (include_merged_revisions)
1636 SVN_ERR(find_merged_revisions(&merged_path_revisions, start,
1637 mainline_path_revisions, repos,
1638 duplicate_path_revs, authz_read_func,
1639 authz_read_baton,
1640 scratch_pool, sb.iterpool));
1641 else
1642 merged_path_revisions = apr_array_make(scratch_pool, 0,
1643 sizeof(struct path_revision *));
1644
1645 /* We must have at least one revision to get. */
1646 SVN_ERR_ASSERT(mainline_path_revisions->nelts > 0);
1647
1648 /* Walk through both mainline and merged revisions, and send them in
1649 reverse chronological order, interleaving as appropriate. */
1650 mainline_pos = mainline_path_revisions->nelts - 1;
1651 merged_pos = merged_path_revisions->nelts - 1;
1652 while (mainline_pos >= 0 && merged_pos >= 0)
1653 {
1654 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1655 mainline_pos,
1656 struct path_revision *);
1657 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1658 merged_pos,
1659 struct path_revision *);
1660
1661 if (main_pr->revnum <= merged_pr->revnum)
1662 {
1663 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler,
1664 handler_baton));
1665 mainline_pos -= 1;
1666 }
1667 else
1668 {
1669 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1670 handler_baton));
1671 merged_pos -= 1;
1672 }
1673 }
1674
1675 /* Send any remaining revisions from the mainline list. */
1676 for (; mainline_pos >= 0; mainline_pos -= 1)
1677 {
1678 struct path_revision *main_pr = APR_ARRAY_IDX(mainline_path_revisions,
1679 mainline_pos,
1680 struct path_revision *);
1681 SVN_ERR(send_path_revision(main_pr, repos, &sb, handler, handler_baton));
1682 }
1683
1684 /* Ditto for the merged list. */
1685 for (; merged_pos >= 0; merged_pos -= 1)
1686 {
1687 struct path_revision *merged_pr = APR_ARRAY_IDX(merged_path_revisions,
1688 merged_pos,
1689 struct path_revision *);
1690 SVN_ERR(send_path_revision(merged_pr, repos, &sb, handler,
1691 handler_baton));
1692 }
1693
1694 svn_pool_destroy(sb.last_pool);
1695 svn_pool_destroy(sb.iterpool);
1696
1697 return SVN_NO_ERROR;
1698 }
1699