32
33
#include "svn_props.h"
34
35
#include "svn_private_config.h"
35
37
#include "private/svn_dep_compat.h"
36
38
#include "private/svn_fspath.h"
39
#include "private/svn_subr_private.h"
40
#include "private/svn_string_private.h"
38
42
#define NUM_CACHED_SOURCE_ROOTS 4
40
/* Theory of operation: we write report operations out to a temporary
41
file as we receive them. When the report is finished, we read the
44
/* Theory of operation: we write report operations out to a spill-buffer
45
as we receive them. When the report is finished, we read the
42
46
operations back out again, using them to guide the progression of
43
47
the delta between the source and target revs.
45
Temporary file format: we use a simple ad-hoc format to store the
49
Spill-buffer content format: we use a simple ad-hoc format to store the
46
50
report operations. Each report operation is the concatention of
47
51
the following ("+/-" indicates the single character '+' or '-';
48
52
<length> and <revnum> are written out as decimal strings):
100
104
driven by the client as it describes its working copy revisions. */
101
105
typedef struct report_baton_t
103
/* Parameters remembered from svn_repos_begin_report2 */
107
/* Parameters remembered from svn_repos_begin_report3 */
104
108
svn_repos_t *repos;
105
109
const char *fs_base; /* fspath corresponding to wc anchor */
106
110
const char *s_operand; /* anchor-relative wc target (may be empty) */
107
111
svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */
108
112
const char *t_path; /* FS path the edit will bring the wc to */
109
113
svn_boolean_t text_deltas; /* Whether to report text deltas */
114
apr_size_t zero_copy_limit; /* Max item size that will be sent using
115
the zero-copy code path. */
111
117
/* If the client requested a specific depth, record it here; if the
112
118
client did not, then this is svn_depth_unknown, and the depth of
158
166
/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
160
168
static svn_error_t *
161
read_number(apr_uint64_t *num, apr_file_t *temp, apr_pool_t *pool)
169
read_number(apr_uint64_t *num, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
168
SVN_ERR(svn_io_file_getc(&c, temp, pool));
176
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
171
179
*num = *num * 10 + (c - '0');
176
184
static svn_error_t *
177
read_string(const char **str, apr_file_t *temp, apr_pool_t *pool)
185
read_string(const char **str, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
179
187
apr_uint64_t len;
183
SVN_ERR(read_number(&len, temp, pool));
192
SVN_ERR(read_number(&len, reader, pool));
185
194
/* Len can never be less than zero. But could len be so large that
186
195
len + 1 wraps around and we end up passing 0 to apr_palloc(),
202
211
size = (apr_size_t)len;
203
212
buf = apr_palloc(pool, size+1);
204
SVN_ERR(svn_io_file_read_full2(temp, buf, size, NULL, NULL, pool));
215
SVN_ERR(svn_spillbuf__reader_read(&amt, reader, buf, size, pool));
216
SVN_ERR_ASSERT(amt == size);
207
220
return SVN_NO_ERROR;
210
223
static svn_error_t *
211
read_rev(svn_revnum_t *rev, apr_file_t *temp, apr_pool_t *pool)
224
read_rev(svn_revnum_t *rev, svn_spillbuf_reader_t *reader, apr_pool_t *pool)
214
227
apr_uint64_t num;
216
SVN_ERR(svn_io_file_getc(&c, temp, pool));
229
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
219
SVN_ERR(read_number(&num, temp, pool));
232
SVN_ERR(read_number(&num, reader, pool));
220
233
*rev = (svn_revnum_t) num;
227
240
/* Read a single character to set *DEPTH (having already read '+')
228
from TEMP. PATH is the path to which the depth applies, and is
241
from READER. PATH is the path to which the depth applies, and is
229
242
used for error reporting only. */
230
243
static svn_error_t *
231
read_depth(svn_depth_t *depth, apr_file_t *temp, const char *path,
244
read_depth(svn_depth_t *depth, svn_spillbuf_reader_t *reader, const char *path,
232
245
apr_pool_t *pool)
236
SVN_ERR(svn_io_file_getc(&c, temp, pool));
249
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
260
273
return SVN_NO_ERROR;
263
/* Read a report operation *PI out of TEMP. Set *PI to NULL if we
276
/* Read a report operation *PI out of READER. Set *PI to NULL if we
264
277
have reached the end of the report. */
265
278
static svn_error_t *
266
read_path_info(path_info_t **pi, apr_file_t *temp, apr_pool_t *pool)
279
read_path_info(path_info_t **pi,
280
svn_spillbuf_reader_t *reader,
270
SVN_ERR(svn_io_file_getc(&c, temp, pool));
285
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
277
292
*pi = apr_palloc(pool, sizeof(**pi));
278
SVN_ERR(read_string(&(*pi)->path, temp, pool));
279
SVN_ERR(svn_io_file_getc(&c, temp, pool));
293
SVN_ERR(read_string(&(*pi)->path, reader, pool));
294
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
281
SVN_ERR(read_string(&(*pi)->link_path, temp, pool));
296
SVN_ERR(read_string(&(*pi)->link_path, reader, pool));
283
298
(*pi)->link_path = NULL;
284
SVN_ERR(read_rev(&(*pi)->rev, temp, pool));
285
SVN_ERR(svn_io_file_getc(&c, temp, pool));
299
SVN_ERR(read_rev(&(*pi)->rev, reader, pool));
300
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
287
SVN_ERR(read_depth(&((*pi)->depth), temp, (*pi)->path, pool));
302
SVN_ERR(read_depth(&((*pi)->depth), reader, (*pi)->path, pool));
289
304
(*pi)->depth = svn_depth_infinity;
290
SVN_ERR(svn_io_file_getc(&c, temp, pool));
305
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
291
306
(*pi)->start_empty = (c == '+');
292
SVN_ERR(svn_io_file_getc(&c, temp, pool));
307
SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool));
294
SVN_ERR(read_string(&(*pi)->lock_token, temp, pool));
309
SVN_ERR(read_string(&(*pi)->lock_token, reader, pool));
296
311
(*pi)->lock_token = NULL;
297
312
(*pi)->pool = pool;
433
448
change_dir_prop(report_baton_t *b, void *dir_baton, const char *name,
434
449
const svn_string_t *value, apr_pool_t *pool)
436
return b->editor->change_dir_prop(dir_baton, name, value, pool);
451
return svn_error_trace(b->editor->change_dir_prop(dir_baton, name, value,
439
455
/* Call the file property-setting function of B->editor to set the
442
458
change_file_prop(report_baton_t *b, void *file_baton, const char *name,
443
459
const svn_string_t *value, apr_pool_t *pool)
445
return b->editor->change_file_prop(file_baton, name, value, pool);
461
return svn_error_trace(b->editor->change_file_prop(file_baton, name, value,
448
465
/* For the report B, return the relevant revprop data of revision REV in
472
489
/* Extract the committed-date. */
473
cdate = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
474
APR_HASH_KEY_STRING);
490
cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
476
492
/* Extract the last-author. */
477
author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
478
APR_HASH_KEY_STRING);
493
author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR);
480
495
/* Create a result object */
481
496
info = apr_palloc(b->pool, sizeof(*info));
504
519
void *object, apr_pool_t *pool)
506
521
svn_fs_root_t *s_root;
507
apr_hash_t *s_props, *t_props;
522
apr_hash_t *s_props = NULL, *t_props;
508
523
apr_array_header_t *prop_diffs;
510
525
svn_revnum_t crev;
512
svn_string_t *cr_str;
513
revision_info_t* revision_info;
526
revision_info_t *revision_info;
514
527
svn_boolean_t changed;
515
528
const svn_prop_t *pc;
516
529
svn_lock_t *lock;
530
apr_hash_index_t *hi;
518
532
/* Fetch the created-rev and send entry props. */
519
533
SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
520
534
if (SVN_IS_VALID_REVNUM(crev))
536
/* convert committed-rev to string */
537
char buf[SVN_INT64_BUFFER_SIZE];
540
cr_str.len = svn__i64toa(buf, crev);
522
542
/* Transmit the committed-rev. */
523
cr_str = svn_string_createf(pool, "%ld", crev);
524
543
SVN_ERR(change_fn(b, object,
525
SVN_PROP_ENTRY_COMMITTED_REV, cr_str, pool));
544
SVN_PROP_ENTRY_COMMITTED_REV, &cr_str, pool));
527
546
SVN_ERR(get_revision_info(b, crev, &revision_info, pool));
566
584
/* If so, go ahead and get the source path's properties. */
567
585
SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool));
570
s_props = apr_hash_make(pool);
572
588
/* Get the target path's properties */
573
589
SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool));
575
/* Now transmit the differences. */
576
SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
577
for (i = 0; i < prop_diffs->nelts; i++)
579
pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
580
SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
591
if (s_props && apr_hash_count(s_props))
593
/* Now transmit the differences. */
594
SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
595
for (i = 0; i < prop_diffs->nelts; i++)
597
pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
598
SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
601
else if (apr_hash_count(t_props))
603
/* So source, i.e. all new. Transmit all target props. */
604
for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi))
609
apr_hash_this(hi, &key, NULL, &val);
610
SVN_ERR(change_fn(b, object, key, val, pool));
617
/* Baton type to be passed into send_zero_copy_delta.
619
typedef struct zero_copy_baton_t
621
/* don't process data larger than this limit */
622
apr_size_t zero_copy_limit;
624
/* window handler and baton to send the data to */
625
svn_txdelta_window_handler_t dhandler;
628
/* return value: will be set to TRUE, if the data was processed. */
629
svn_boolean_t zero_copy_succeeded;
632
/* Implement svn_fs_process_contents_func_t. If LEN is smaller than the
633
* limit given in *BATON, send the CONTENTS as an delta windows to the
634
* handler given in BATON and set the ZERO_COPY_SUCCEEDED flag in that
635
* BATON. Otherwise, reset it to FALSE.
636
* Use POOL for temporary allocations.
639
send_zero_copy_delta(const unsigned char *contents,
644
zero_copy_baton_t *zero_copy_baton = baton;
646
/* if the item is too large, the caller must revert to traditional
648
if (len > zero_copy_baton->zero_copy_limit)
650
zero_copy_baton->zero_copy_succeeded = FALSE;
654
SVN_ERR(svn_txdelta_send_contents(contents, len,
655
zero_copy_baton->dhandler,
656
zero_copy_baton->dbaton, pool));
659
zero_copy_baton->zero_copy_succeeded = TRUE;
586
664
/* Make the appropriate edits on FILE_BATON to change its contents and
587
665
properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
609
687
SVN_ERR(get_source_root(b, &s_root, s_rev));
611
/* Is this delta calculation worth our time? If we are ignoring
612
ancestry, then our editor implementor isn't concerned by the
613
theoretical differences between "has contents which have not
614
changed with respect to" and "has the same actual contents
615
as". We'll do everything we can to avoid transmitting even
616
an empty text-delta in that case. */
617
if (b->ignore_ancestry)
618
SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
619
s_root, s_path, pool));
621
SVN_ERR(svn_fs_contents_changed(&changed, b->t_root, t_path, s_root,
689
/* We're not interested in the theoretical difference between "has
690
contents which have not changed with respect to" and "has the same
691
actual contents as" when sending text-deltas. If we know the
692
delta is an empty one, we avoiding sending it in either case. */
693
SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
694
s_root, s_path, pool));
624
697
return SVN_NO_ERROR;
631
704
/* Send the delta stream if desired, or just a NULL window if not. */
632
705
SVN_ERR(b->editor->apply_textdelta(file_baton, s_hex_digest, pool,
633
706
&dhandler, &dbaton));
708
if (dhandler != svn_delta_noop_window_handler)
636
SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
637
b->t_root, t_path, pool));
638
return svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool);
712
/* if we send deltas against empty streams, we may use our
714
if (b->zero_copy_limit > 0 && s_path == NULL)
716
zero_copy_baton_t baton;
717
svn_boolean_t called = FALSE;
719
baton.zero_copy_limit = b->zero_copy_limit;
720
baton.dhandler = dhandler;
721
baton.dbaton = dbaton;
722
baton.zero_copy_succeeded = FALSE;
723
SVN_ERR(svn_fs_try_process_file_contents(&called,
725
send_zero_copy_delta,
728
/* data has been available and small enough,
729
i.e. been processed? */
730
if (called && baton.zero_copy_succeeded)
734
SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
735
b->t_root, t_path, pool));
736
SVN_ERR(svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool));
739
SVN_ERR(dhandler(NULL, dbaton));
641
return dhandler(NULL, dbaton);
644
745
/* Determine if the user is authorized to view B->t_root/PATH. */
889
990
SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path,
890
991
s_rev, b->t_rev, &deleted_rev,
994
if (!SVN_IS_VALID_REVNUM(deleted_rev))
996
/* Two possibilities: either the thing doesn't exist in S_REV; or
997
it wasn't deleted between S_REV and B->T_REV. In the first case,
998
I think we should leave DELETED_REV as SVN_INVALID_REVNUM, but
999
in the second, it should be set to B->T_REV-1 for the call to
1000
delete_entry() below. */
1001
svn_node_kind_t kind;
1003
SVN_ERR(svn_fs_check_path(&kind, b->t_root, t_path, pool));
1004
if (kind != svn_node_none)
1005
deleted_rev = b->t_rev - 1;
892
1008
SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton,
922
1038
SVN_ERR(delta_dirs(b, s_rev, s_path, t_path, new_baton, e_path,
923
1039
info ? info->start_empty : FALSE,
924
1040
wc_depth, requested_depth, pool));
925
return b->editor->close_directory(new_baton, pool);
1041
return svn_error_trace(b->editor->close_directory(new_baton, pool));
1072
1189
item is a delete, remove the entry from the source hash,
1073
1190
but don't update the entry yet. */
1075
apr_hash_set(s_entries, name, APR_HASH_KEY_STRING, NULL);
1192
svn_hash_sets(s_entries, name, NULL);
1079
1196
e_fullpath = svn_relpath_join(e_path, name, subpool);
1080
1197
t_fullpath = svn_fspath__join(t_path, name, subpool);
1081
t_entry = apr_hash_get(t_entries, name, APR_HASH_KEY_STRING);
1198
t_entry = svn_hash_gets(t_entries, name);
1082
1199
s_fullpath = s_path ? svn_fspath__join(s_path, name, subpool) : NULL;
1083
1200
s_entry = s_entries ?
1084
apr_hash_get(s_entries, name, APR_HASH_KEY_STRING) : NULL;
1201
svn_hash_gets(s_entries, name) : NULL;
1086
1203
/* The only special cases here are
1102
1219
DEPTH_BELOW_HERE(requested_depth), subpool));
1104
1221
/* Don't revisit this name in the target or source entries. */
1105
apr_hash_set(t_entries, name, APR_HASH_KEY_STRING, NULL);
1222
svn_hash_sets(t_entries, name, NULL);
1107
1224
/* Keep the entry for later process if it is reported as
1108
1225
excluded and got deleted in repos. */
1109
1226
&& (! info || info->depth != svn_depth_exclude || t_entry))
1110
apr_hash_set(s_entries, name, APR_HASH_KEY_STRING, NULL);
1227
svn_hash_sets(s_entries, name, NULL);
1112
1229
/* pathinfo entries live in their own subpools due to lookahead,
1113
1230
so we need to clear each one out as we finish with it. */
1270
1386
t_entry, root_baton, b->s_operand, info,
1271
1387
info->depth, b->requested_depth, pool));
1273
return b->editor->close_directory(root_baton, pool);
1389
return svn_error_trace(b->editor->close_directory(root_baton, pool));
1276
1392
/* Initialize the baton fields for editor-driving, and drive the editor. */
1277
1393
static svn_error_t *
1278
1394
finish_report(report_baton_t *b, apr_pool_t *pool)
1281
1396
path_info_t *info;
1282
1397
apr_pool_t *subpool;
1283
1398
svn_revnum_t s_rev;
1286
1401
/* Save our pool to manage the lookahead and fs_root cache with. */
1287
1402
b->pool = pool;
1289
/* Add an end marker and rewind the temporary file. */
1290
SVN_ERR(svn_io_file_write_full(b->tempfile, "-", 1, NULL, pool));
1292
SVN_ERR(svn_io_file_seek(b->tempfile, APR_SET, &offset, pool));
1404
/* Add the end marker. */
1405
SVN_ERR(svn_spillbuf__reader_write(b->reader, "-", 1, pool));
1294
1407
/* Read the first pathinfo from the report and verify that it is a top-level
1295
1408
set_path entry. */
1296
SVN_ERR(read_path_info(&info, b->tempfile, pool));
1409
SVN_ERR(read_path_info(&info, b->reader, pool));
1297
1410
if (!info || strcmp(info->path, b->s_operand) != 0
1298
1411
|| info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
1299
1412
return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
1381
1495
rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s",
1382
1496
strlen(path), path, lrep, rrep, drep,
1383
1497
start_empty ? '+' : '-', ltrep);
1384
return svn_io_file_write_full(b->tempfile, rep, strlen(rep), NULL, pool);
1498
return svn_error_trace(
1499
svn_spillbuf__reader_write(b->reader, rep, strlen(rep), pool));
1389
1504
svn_depth_t depth, svn_boolean_t start_empty,
1390
1505
const char *lock_token, apr_pool_t *pool)
1392
return write_path_info(baton, path, NULL, rev, depth, start_empty,
1507
return svn_error_trace(
1508
write_path_info(baton, path, NULL, rev, depth, start_empty,
1403
1519
return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1404
1520
_("Depth 'exclude' not supported for link"));
1406
return write_path_info(baton, path, link_path, rev, depth,
1407
start_empty, lock_token, pool);
1522
return svn_error_trace(
1523
write_path_info(baton, path, link_path, rev, depth,
1524
start_empty, lock_token, pool));
1413
1530
/* We pass svn_depth_infinity because deletion of a path always
1414
1531
deletes everything underneath it. */
1415
return write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
1416
svn_depth_infinity, FALSE, NULL, pool);
1532
return svn_error_trace(
1533
write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
1534
svn_depth_infinity, FALSE, NULL, pool));
1420
1538
svn_repos_finish_report(void *baton, apr_pool_t *pool)
1422
1540
report_baton_t *b = baton;
1423
svn_error_t *finish_err, *close_err;
1425
finish_err = finish_report(b, pool);
1426
close_err = svn_io_file_close(b->tempfile, pool);
1428
return svn_error_trace(svn_error_compose_create(finish_err, close_err));
1542
return svn_error_trace(finish_report(b, pool));
1432
1546
svn_repos_abort_report(void *baton, apr_pool_t *pool)
1434
report_baton_t *b = baton;
1436
return svn_error_trace(svn_io_file_close(b->tempfile, pool));
1548
return SVN_NO_ERROR;
1439
1551
/* --- BEGINNING THE REPORT --- */
1443
svn_repos_begin_report2(void **report_baton,
1555
svn_repos_begin_report3(void **report_baton,
1444
1556
svn_revnum_t revnum,
1445
1557
svn_repos_t *repos,
1446
1558
const char *fs_base,
1454
1566
void *edit_baton,
1455
1567
svn_repos_authz_func_t authz_read_func,
1456
1568
void *authz_read_baton,
1569
apr_size_t zero_copy_limit,
1457
1570
apr_pool_t *pool)
1459
1572
report_baton_t *b;
1461
1575
if (depth == svn_depth_exclude)
1462
1576
return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1463
1577
_("Request depth 'exclude' not supported"));
1579
SVN_ERR(svn_fs_get_uuid(repos->fs, &uuid, pool));
1465
1581
/* Build a reporter baton. Copy strings in case the caller doesn't
1466
1582
keep track of them. */
1467
1583
b = apr_palloc(pool, sizeof(*b));