1
/* rev_hunt.c --- routines to hunt down particular fs revisions and
4
* ====================================================================
5
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
7
* This software is licensed as described in the file COPYING, which
8
* you should have received as part of this distribution. The terms
9
* are also available at http://subversion.tigris.org/license-1.html.
10
* If newer versions of this license are posted there, you may use a
11
* newer version instead, at your option.
13
* This software consists of voluntary contributions made by many
14
* individuals. For exact contribution history, see the revision
15
* history and logs, available at http://subversion.tigris.org/.
16
* ====================================================================
21
#include "svn_private_config.h"
22
#include "svn_pools.h"
23
#include "svn_error.h"
25
#include "svn_repos.h"
26
#include "svn_string.h"
28
#include "svn_sorts.h"
29
#include "svn_props.h"
35
/* Note: this binary search assumes that the datestamp properties on
36
each revision are in chronological order. That is if revision A >
37
revision B, then A's datestamp is younger then B's datestamp.
39
If some moron comes along and sets a bogus datestamp, this routine
42
### todo: you know, we *could* have svn_fs_change_rev_prop() do
43
some semantic checking when it's asked to change special reserved
44
svn: properties. It could prevent such a problem. */
47
/* helper for svn_repos_dated_revision().
49
Set *TM to the apr_time_t datestamp on revision REV in FS. */
51
get_time (apr_time_t *tm,
56
svn_string_t *date_str;
58
SVN_ERR (svn_fs_revision_prop (&date_str, fs, rev, SVN_PROP_REVISION_DATE,
61
return svn_error_createf
62
(SVN_ERR_FS_GENERAL, NULL,
63
_("Failed to find time on revision %ld"), rev);
65
SVN_ERR (svn_time_from_cstring (tm, date_str->data, pool));
72
svn_repos_dated_revision (svn_revnum_t *revision,
77
svn_revnum_t rev_mid, rev_top, rev_bot, rev_latest;
79
svn_fs_t *fs = repos->fs;
81
/* Initialize top and bottom values of binary search. */
82
SVN_ERR (svn_fs_youngest_rev (&rev_latest, fs, pool));
86
while (rev_bot <= rev_top)
88
rev_mid = (rev_top + rev_bot) / 2;
89
SVN_ERR (get_time (&this_time, fs, rev_mid, pool));
91
if (this_time > tm)/* we've overshot */
93
apr_time_t previous_time;
95
if ((rev_mid - 1) < 0)
101
/* see if time falls between rev_mid and rev_mid-1: */
102
SVN_ERR (get_time (&previous_time, fs, rev_mid - 1, pool));
103
if (previous_time <= tm)
105
*revision = rev_mid - 1;
109
rev_top = rev_mid - 1;
112
else if (this_time < tm) /* we've undershot */
114
apr_time_t next_time;
116
if ((rev_mid + 1) > rev_latest)
118
*revision = rev_latest;
122
/* see if time falls between rev_mid and rev_mid+1: */
123
SVN_ERR (get_time (&next_time, fs, rev_mid + 1, pool));
130
rev_bot = rev_mid + 1;
135
*revision = rev_mid; /* exact match! */
145
svn_repos_get_committed_info (svn_revnum_t *committed_rev,
146
const char **committed_date,
147
const char **last_author,
152
svn_fs_t *fs = svn_fs_root_fs (root);
154
/* ### It might be simpler just to declare that revision
155
properties have char * (i.e., UTF-8) values, not arbitrary
156
binary values, hmmm. */
157
svn_string_t *committed_date_s, *last_author_s;
159
/* Get the CR field out of the node's skel. */
160
SVN_ERR (svn_fs_node_created_rev (committed_rev, root, path, pool));
162
/* Get the date property of this revision. */
163
SVN_ERR (svn_fs_revision_prop (&committed_date_s, fs, *committed_rev,
164
SVN_PROP_REVISION_DATE, pool));
166
/* Get the author property of this revision. */
167
SVN_ERR (svn_fs_revision_prop (&last_author_s, fs, *committed_rev,
168
SVN_PROP_REVISION_AUTHOR, pool));
170
*committed_date = committed_date_s ? committed_date_s->data : NULL;
171
*last_author = last_author_s ? last_author_s->data : NULL;
179
svn_repos_history (svn_fs_t *fs,
181
svn_repos_history_func_t history_func,
185
svn_boolean_t cross_copies,
188
return svn_repos_history2 (fs, path, history_func, history_baton,
190
start, end, cross_copies, pool);
196
svn_repos_history2 (svn_fs_t *fs,
198
svn_repos_history_func_t history_func,
200
svn_repos_authz_func_t authz_read_func,
201
void *authz_read_baton,
204
svn_boolean_t cross_copies,
207
svn_fs_history_t *history;
208
apr_pool_t *oldpool = svn_pool_create (pool);
209
apr_pool_t *newpool = svn_pool_create (pool);
210
const char *history_path;
211
svn_revnum_t history_rev;
214
/* Validate the revisions. */
215
if (! SVN_IS_VALID_REVNUM (start))
216
return svn_error_createf
217
(SVN_ERR_FS_NO_SUCH_REVISION, 0,
218
_("Invalid start revision %ld"), start);
219
if (! SVN_IS_VALID_REVNUM (end))
220
return svn_error_createf
221
(SVN_ERR_FS_NO_SUCH_REVISION, 0,
222
_("Invalid end revision %ld"), end);
224
/* Ensure that the input is ordered. */
227
svn_revnum_t tmprev = start;
232
/* Get a revision root for END, and an initial HISTORY baton. */
233
SVN_ERR (svn_fs_revision_root (&root, fs, end, pool));
237
svn_boolean_t readable;
238
SVN_ERR (authz_read_func (&readable, root, path,
239
authz_read_baton, pool));
241
return svn_error_create (SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
244
SVN_ERR (svn_fs_node_history (&history, root, path, oldpool));
246
/* Now, we loop over the history items, calling svn_fs_history_prev(). */
249
/* Note that we have to do some crazy pool work here. We can't
250
get rid of the old history until we use it to get the new, so
251
we alternate back and forth between our subpools. */
254
SVN_ERR (svn_fs_history_prev (&history, history, cross_copies, newpool));
256
/* Only continue if there is further history to deal with. */
260
/* Fetch the location information for this history step. */
261
SVN_ERR (svn_fs_history_location (&history_path, &history_rev,
264
/* If this history item predates our START revision, quit
266
if (history_rev < start)
269
/* Is the history item readable? If not, quit. */
272
svn_boolean_t readable;
273
svn_fs_root_t *history_root;
274
SVN_ERR (svn_fs_revision_root (&history_root, fs,
275
history_rev, newpool));
276
SVN_ERR (authz_read_func (&readable, history_root, history_path,
277
authz_read_baton, newpool));
282
/* Call the user-provided callback function. */
283
SVN_ERR (history_func (history_baton, history_path,
284
history_rev, newpool));
286
/* We're done with the old history item, so we can clear its
287
pool, and then toggle our notion of "the old pool". */
288
svn_pool_clear (oldpool);
293
while (history); /* shouldn't hit this */
295
svn_pool_destroy (oldpool);
296
svn_pool_destroy (newpool);
301
/* Helper func: return SVN_ERR_AUTHZ_UNREADABLE if ROOT/PATH is
304
check_readability (svn_fs_root_t *root,
306
svn_repos_authz_func_t authz_read_func,
307
void *authz_read_baton,
310
svn_boolean_t readable;
311
SVN_ERR (authz_read_func (&readable, root, path, authz_read_baton, pool));
313
return svn_error_create (SVN_ERR_AUTHZ_UNREADABLE, NULL,
314
_("Unreadable path encountered; access denied"));
319
/* The purpose of this function is to discover if fs_path@future_rev
320
* is derived from fs_path@peg_rev. The return is placed in *is_ancestor. */
323
check_ancestry_of_peg_path (svn_boolean_t *is_ancestor,
326
svn_revnum_t peg_revision,
327
svn_revnum_t future_revision,
331
svn_fs_history_t *history;
333
svn_revnum_t revision;
334
apr_pool_t *lastpool, *currpool;
336
lastpool = svn_pool_create (pool);
337
currpool = svn_pool_create (pool);
339
SVN_ERR (svn_fs_revision_root (&root, fs, future_revision, pool));
341
SVN_ERR (svn_fs_node_history (&history, root, fs_path, lastpool));
343
/* Since paths that are different according to strcmp may still be
344
equivalent (due to number of consecutive slashes and the fact that
345
"" is the same as "/"), we get the "canonical" path in the first
346
iteration below so that the comparison after the loop will work
354
SVN_ERR (svn_fs_history_prev (&history, history, TRUE, currpool));
359
SVN_ERR (svn_fs_history_location (&path, &revision, history, currpool));
362
fs_path = apr_pstrdup (pool, path);
364
if (revision <= peg_revision)
367
/* Clear old pool and flip. */
368
svn_pool_clear (lastpool);
374
/* We must have had at least one iteration above where we
375
reassigned fs_path. Else, the path wouldn't have existed at
376
future_revision and svn_fs_history would have thrown. */
377
assert (fs_path != NULL);
379
*is_ancestor = (history && strcmp (path, fs_path) == 0);
386
svn_repos_trace_node_locations (svn_fs_t *fs,
387
apr_hash_t **locations,
389
svn_revnum_t peg_revision,
390
apr_array_header_t *location_revisions_orig,
391
svn_repos_authz_func_t authz_read_func,
392
void *authz_read_baton,
395
apr_array_header_t *location_revisions;
396
svn_revnum_t *revision_ptr, *revision_ptr_end;
398
svn_fs_history_t *history;
400
svn_revnum_t revision;
401
svn_boolean_t is_ancestor;
402
apr_pool_t *lastpool, *currpool;
405
assert (location_revisions_orig->elt_size == sizeof(svn_revnum_t));
407
/* Another sanity check. */
410
svn_fs_root_t *peg_root;
411
SVN_ERR (svn_fs_revision_root (&peg_root, fs, peg_revision, pool));
412
SVN_ERR (check_readability (peg_root, fs_path,
413
authz_read_func, authz_read_baton, pool));
416
*locations = apr_hash_make (pool);
418
/* We flip between two pools in the second loop below. */
419
lastpool = svn_pool_create (pool);
420
currpool = svn_pool_create (pool);
422
/* First - let's sort the array of the revisions from the greatest revision
423
* downward, so it will be easier to search on. */
424
location_revisions = apr_array_copy (pool, location_revisions_orig);
425
qsort (location_revisions->elts, location_revisions->nelts,
426
sizeof (*revision_ptr), svn_sort_compare_revisions);
428
revision_ptr = (svn_revnum_t *)location_revisions->elts;
429
revision_ptr_end = revision_ptr + location_revisions->nelts;
431
/* Ignore revisions R that are younger than the peg_revisions where
432
path@peg_revision is not an ancestor of path@R. */
434
while (revision_ptr < revision_ptr_end && *revision_ptr > peg_revision)
436
svn_pool_clear (currpool);
437
SVN_ERR (check_ancestry_of_peg_path (&is_ancestor, fs, fs_path,
438
peg_revision, *revision_ptr,
445
SVN_ERR (svn_fs_revision_root (&root, fs,
448
peg_revision), pool));
450
SVN_ERR (check_readability (root, fs_path, authz_read_func,
451
authz_read_baton, pool));
453
SVN_ERR (svn_fs_node_history (&history, root, fs_path, lastpool));
455
while (revision_ptr < revision_ptr_end)
459
SVN_ERR (svn_fs_history_prev (&history, history, TRUE, currpool));
463
SVN_ERR (svn_fs_history_location (&path, &revision, history, currpool));
467
svn_boolean_t readable;
468
svn_fs_root_t *tmp_root;
470
SVN_ERR (svn_fs_revision_root (&tmp_root, fs, revision, currpool));
471
SVN_ERR (authz_read_func (&readable, tmp_root, path,
472
authz_read_baton, currpool));
479
/* Assign the current path to all younger revisions until we reach
481
while ((revision_ptr < revision_ptr_end) && (*revision_ptr >= revision))
483
/* *revision_ptr is allocated out of pool, so we can point
484
to in the hash table. */
485
apr_hash_set (*locations, revision_ptr, sizeof (*revision_ptr),
486
apr_pstrdup (pool, path));
490
/* Clear last pool and switch. */
491
svn_pool_clear (lastpool);
497
svn_pool_destroy (lastpool);
498
svn_pool_destroy (currpool);
504
svn_repos_get_file_revs (svn_repos_t *repos,
508
svn_repos_authz_func_t authz_read_func,
509
void *authz_read_baton,
510
svn_repos_file_rev_handler_t handler,
514
apr_pool_t *iter_pool, *last_pool;
515
svn_fs_history_t *history;
516
apr_array_header_t *revnums = apr_array_make (pool, 0,
517
sizeof (svn_revnum_t));
518
apr_array_header_t *paths = apr_array_make (pool, 0, sizeof (char *));
519
apr_hash_t *last_props;
520
svn_fs_root_t *root, *last_root;
521
const char *last_path;
523
svn_node_kind_t kind;
525
/* We switch betwwen two pools while looping, since we need information from
526
the last iteration to be available. */
527
iter_pool = svn_pool_create (pool);
528
last_pool = svn_pool_create (pool);
530
/* Open revision root for path@end. */
531
/* ### Can we use last_pool for this? How long does the history
532
object need the root? */
533
SVN_ERR (svn_fs_revision_root (&root, repos->fs, end, pool));
535
/* The path had better be a file in this revision. This avoids calling
536
the callback before reporting an uglier error below. */
537
SVN_ERR (svn_fs_check_path (&kind, root, path, pool));
538
if (kind != svn_node_file)
539
return svn_error_createf
540
(SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file"), path);
542
/* Open a history object. */
543
SVN_ERR (svn_fs_node_history (&history, root, path, last_pool));
545
/* Get the revisions we are interested in. */
548
const char* rev_path;
550
apr_pool_t *tmp_pool;
552
svn_pool_clear (iter_pool);
554
SVN_ERR (svn_fs_history_prev (&history, history, TRUE, iter_pool));
557
SVN_ERR (svn_fs_history_location (&rev_path, &rev, history, iter_pool));
560
svn_boolean_t readable;
561
svn_fs_root_t *tmp_root;
563
SVN_ERR (svn_fs_revision_root (&tmp_root, repos->fs, rev, iter_pool));
564
SVN_ERR (authz_read_func (&readable, tmp_root, rev_path,
565
authz_read_baton, iter_pool));
571
*(svn_revnum_t*) apr_array_push (revnums) = rev;
572
*(char **) apr_array_push (paths) = apr_pstrdup (pool, rev_path);
577
tmp_pool = iter_pool;
578
iter_pool = last_pool;
579
last_pool = tmp_pool;
582
/* We must have at least one revision to get. */
583
assert (revnums->nelts > 0);
585
/* We want the first txdelta to be against the empty file. */
589
/* Create an empty hash table for the first property diff. */
590
last_props = apr_hash_make (last_pool);
592
/* Walk through the revisions in chronological order. */
593
for (i = revnums->nelts; i > 0; --i)
595
svn_revnum_t rev = APR_ARRAY_IDX (revnums, i - 1, svn_revnum_t);
596
const char *rev_path = APR_ARRAY_IDX (paths, i - 1, const char *);
597
apr_hash_t *rev_props;
599
apr_array_header_t *prop_diffs;
600
svn_txdelta_stream_t *delta_stream;
601
svn_txdelta_window_handler_t delta_handler = NULL;
602
void *delta_baton = NULL;
603
apr_pool_t *tmp_pool; /* For swapping */
604
svn_boolean_t contents_changed;
606
svn_pool_clear (iter_pool);
608
/* Get the revision properties. */
609
SVN_ERR (svn_fs_revision_proplist (&rev_props, repos->fs,
612
/* Open the revision root. */
613
SVN_ERR (svn_fs_revision_root (&root, repos->fs, rev, iter_pool));
615
/* Get the file's properties for this revision and compute the diffs. */
616
SVN_ERR (svn_fs_node_proplist (&props, root, rev_path, iter_pool));
617
SVN_ERR (svn_prop_diffs (&prop_diffs, props, last_props, pool));
619
/* Check if the contents changed. */
620
/* Special case: In the first revision, we always provide a delta. */
622
SVN_ERR (svn_fs_contents_changed (&contents_changed,
623
last_root, last_path,
624
root, rev_path, iter_pool));
626
contents_changed = TRUE;
628
/* We have all we need, give to the handler. */
629
SVN_ERR (handler (handler_baton, rev_path, rev, rev_props,
630
contents_changed ? &delta_handler : NULL,
631
contents_changed ? &delta_baton : NULL,
632
prop_diffs, iter_pool));
634
/* Compute and send delta if client asked for it.
635
Note that this was initialized to NULL, so if !contents_changed,
636
no deltas will be computed. */
639
/* Get the content delta. */
640
SVN_ERR (svn_fs_get_file_delta_stream (&delta_stream,
641
last_root, last_path,
645
SVN_ERR (svn_txdelta_send_txstream (delta_stream,
646
delta_handler, delta_baton,
650
/* Remember root, path and props for next iteration. */
652
last_path = rev_path;
655
/* Swap the pools. */
656
tmp_pool = iter_pool;
657
iter_pool = last_pool;
658
last_pool = tmp_pool;
661
svn_pool_destroy (last_pool);
662
svn_pool_destroy (iter_pool);