2
* reporter.c : `reporter' vtable routines for updates.
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_repos.h"
22
#include "svn_pools.h"
24
#include "svn_props.h"
26
#include "svn_private_config.h"
28
#define NUM_CACHED_SOURCE_ROOTS 4
30
/* Theory of operation: we write report operations out to a temporary
31
file as we receive them. When the report is finished, we read the
32
operations back out again, using them to guide the progression of
33
the delta between the source and target revs.
35
Temporary file format: we use a simple ad-hoc format to store the
36
report operations. Each report operation is the concatention of
37
the following ("+/-" indicates the single character '+' or '-';
38
<length> and <revnum> are written out as decimal strings):
40
+/- '-' marks the end of the report
42
<length>:<bytes> Length-counted path string
43
+/- '+' indicates the presence of link_path
45
<length>:<bytes> Length-counted link_path string
46
+/- '+' indicates presence of revnum
48
<revnum>: Revnum of set_path or link_path
49
+/- '+' indicates start_empty field set
50
+/- '+' indicates presence of lock_token field.
52
<length>:<bytes> Length-counted lock_token string
54
Terminology: for brevity, this file frequently uses the prefixes
55
"s_" for source, "t_" for target, and "e_" for editor. Also, to
56
avoid overloading the word "target", we talk about the source
57
"anchor and operand", rather than the usual "anchor and target". */
59
/* Describes the state of a working copy subtree, as given by a
60
report. Because we keep a lookahead pathinfo, we need to allocate
61
each one of these things in a subpool of the report baton and free
63
typedef struct path_info_t
65
const char *path; /* path, munged to be anchor-relative */
66
const char *link_path; /* NULL for set_path or delete_path */
67
svn_revnum_t rev; /* SVN_INVALID_REVNUM for delete_path */
68
svn_boolean_t start_empty; /* Meaningless for delete_path */
69
const char *lock_token; /* NULL if no token */
70
apr_pool_t *pool; /* Container pool */
73
/* A structure used by the routines within the `reporter' vtable,
74
driven by the client as it describes its working copy revisions. */
75
typedef struct report_baton_t
77
/* Parameters remembered from svn_repos_begin_report */
79
const char *fs_base; /* FS path corresponding to wc anchor */
80
const char *s_operand; /* Anchor-relative wc target (may be empty) */
81
svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */
82
const char *t_path; /* FS path the edit will bring the wc to */
83
svn_boolean_t text_deltas; /* Whether to report text deltas */
84
svn_boolean_t recurse;
85
svn_boolean_t ignore_ancestry;
86
svn_boolean_t is_switch;
87
const svn_delta_editor_t *editor;
89
svn_repos_authz_func_t authz_read_func;
90
void *authz_read_baton;
92
/* The temporary file in which we are stashing the report. */
95
/* For the actual editor drive, we'll need a lookahead path info
96
entry, a cache of FS roots, and a pool to store them. */
97
path_info_t *lookahead;
98
svn_fs_root_t *t_root;
99
svn_fs_root_t *s_roots[NUM_CACHED_SOURCE_ROOTS];
103
/* The type of a function that accepts changes to an object's property
104
list. OBJECT is the object whose properties are being changed.
105
NAME is the name of the property to change. VALUE is the new value
106
for the property, or zero if the property should be deleted. */
107
typedef svn_error_t *proplist_change_fn_t (report_baton_t *b, void *object,
109
const svn_string_t *value,
112
static svn_error_t *delta_dirs (report_baton_t *b, svn_revnum_t s_rev,
113
const char *s_path, const char *t_path,
114
void *dir_baton, const char *e_path,
115
svn_boolean_t start_empty, apr_pool_t *pool);
117
/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
120
read_number (apr_uint64_t *num, apr_file_t *temp, apr_pool_t *pool)
127
SVN_ERR (svn_io_file_getc (&c, temp, pool));
130
*num = *num * 10 + (c - '0');
136
read_string (const char **str, apr_file_t *temp, apr_pool_t *pool)
141
SVN_ERR (read_number (&len, temp, pool));
142
buf = apr_palloc (pool, len + 1);
143
SVN_ERR (svn_io_file_read_full (temp, buf, len, NULL, pool));
150
read_rev (svn_revnum_t *rev, apr_file_t *temp, apr_pool_t *pool)
155
SVN_ERR (svn_io_file_getc (&c, temp, pool));
158
SVN_ERR (read_number (&num, temp, pool));
162
*rev = SVN_INVALID_REVNUM;
166
/* Read a report operation *PI out of TEMP. Set *PI to NULL if we
167
have reached the end of the report. */
169
read_path_info (path_info_t **pi, apr_file_t *temp, apr_pool_t *pool)
173
SVN_ERR (svn_io_file_getc (&c, temp, pool));
180
*pi = apr_palloc (pool, sizeof (**pi));
181
SVN_ERR (read_string (&(*pi)->path, temp, pool));
182
SVN_ERR (svn_io_file_getc (&c, temp, pool));
184
SVN_ERR (read_string (&(*pi)->link_path, temp, pool));
186
(*pi)->link_path = NULL;
187
SVN_ERR (read_rev (&(*pi)->rev, temp, pool));
188
SVN_ERR (svn_io_file_getc (&c, temp, pool));
189
(*pi)->start_empty = (c == '+');
190
SVN_ERR (svn_io_file_getc (&c, temp, pool));
192
SVN_ERR (read_string (&(*pi)->lock_token, temp, pool));
194
(*pi)->lock_token = NULL;
199
/* Return true if PI's path is a child of PREFIX (which has length PLEN). */
201
relevant (path_info_t *pi, const char *prefix, apr_size_t plen)
203
return (pi && strncmp (pi->path, prefix, plen) == 0 &&
204
(!*prefix || pi->path[plen] == '/'));
207
/* Fetch the next pathinfo from B->tempfile for a descendent of
208
PREFIX. If the next pathinfo is for an immediate child of PREFIX,
209
set *ENTRY to the path component of the report information and
210
*INFO to the path information for that entry. If the next pathinfo
211
is for a grandchild or other more remote descendent of PREFIX, set
212
*ENTRY to the immediate child corresponding to that descendent and
213
set *INFO to NULL. If the next pathinfo is not for a descendent of
214
PREFIX, or if we reach the end of the report, set both *ENTRY and
217
At all times, B->lookahead is presumed to be the next pathinfo not
218
yet returned as an immediate child, or NULL if we have reached the
219
end of the report. Because we use a lookahead element, we can't
220
rely on the usual nested pool lifetimes, so allocate each pathinfo
221
in a subpool of the report baton's pool. The caller should delete
222
(*INFO)->pool when it is done with the information. */
224
fetch_path_info (report_baton_t *b, const char **entry, path_info_t **info,
225
const char *prefix, apr_pool_t *pool)
227
apr_size_t plen = strlen (prefix);
228
const char *relpath, *sep;
231
if (!relevant (b->lookahead, prefix, plen))
233
/* No more entries relevant to prefix. */
239
/* Take a look at the prefix-relative part of the path. */
240
relpath = b->lookahead->path + (*prefix ? plen + 1 : 0);
241
sep = strchr (relpath, '/');
244
/* Return the immediate child part; do not advance. */
245
*entry = apr_pstrmemdup (pool, relpath, sep - relpath);
250
/* This is an immediate child; return it and advance. */
252
*info = b->lookahead;
253
subpool = svn_pool_create (b->pool);
254
SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
260
/* Skip all path info entries relevant to *PREFIX. Call this when the
261
editor drive skips a directory. */
263
skip_path_info (report_baton_t *b, const char *prefix)
265
apr_size_t plen = strlen (prefix);
268
while (relevant (b->lookahead, prefix, plen))
270
svn_pool_destroy (b->lookahead->pool);
271
subpool = svn_pool_create (b->pool);
272
SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
277
/* Return true if there is at least one path info entry relevant to *PREFIX. */
279
any_path_info (report_baton_t *b, const char *prefix)
281
return relevant (b->lookahead, prefix, strlen(prefix));
284
/* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */
286
/* While driving the editor, the target root will remain constant, but
287
we may have to jump around between source roots depending on the
288
state of the working copy. If we were to open a root each time we
289
revisit a rev, we would get no benefit from node-id caching; on the
290
other hand, if we hold open all the roots we ever visit, we'll use
291
an unbounded amount of memory. As a compromise, we maintain a
292
fixed-size LRU cache of source roots. get_source_root retrieves a
293
root from the cache, using POOL to allocate the new root if
294
necessary. Be careful not to hold onto the root for too long,
295
particularly after recursing, since another call to get_source_root
298
get_source_root (report_baton_t *b, svn_fs_root_t **s_root, svn_revnum_t rev)
301
svn_fs_root_t *root, *prev = NULL;
303
/* Look for the desired root in the cache, sliding all the unmatched
304
entries backwards a slot to make room for the right one. */
305
for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
307
root = b->s_roots[i];
308
b->s_roots[i] = prev;
309
if (root && svn_fs_revision_root_revision (root) == rev)
314
/* If we didn't find it, throw out the oldest root and open a new one. */
315
if (i == NUM_CACHED_SOURCE_ROOTS)
318
svn_fs_close_root (prev);
319
SVN_ERR (svn_fs_revision_root (&root, b->repos->fs, rev, b->pool));
322
/* Assign the desired root to the first cache slot and hand it back. */
323
b->s_roots[0] = root;
328
/* Call the directory property-setting function of B->editor to set
329
the property NAME to VALUE on DIR_BATON. */
331
change_dir_prop (report_baton_t *b, void *dir_baton, const char *name,
332
const svn_string_t *value, apr_pool_t *pool)
334
return b->editor->change_dir_prop (dir_baton, name, value, pool);
337
/* Call the file property-setting function of B->editor to set the
338
property NAME to VALUE on FILE_BATON. */
340
change_file_prop (report_baton_t *b, void *file_baton, const char *name,
341
const svn_string_t *value, apr_pool_t *pool)
343
return b->editor->change_file_prop (file_baton, name, value, pool);
346
/* Generate the appropriate property editing calls to turn the
347
properties of S_REV/S_PATH into those of B->t_root/T_PATH. If
348
S_PATH is NULL, this is an add, so assume the target starts with no
349
properties. Pass OBJECT on to the editor function wrapper
352
delta_proplists (report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
353
const char *t_path, const char *lock_token,
354
proplist_change_fn_t *change_fn,
355
void *object, apr_pool_t *pool)
357
svn_fs_root_t *s_root;
358
apr_hash_t *s_props, *t_props, *r_props;
359
apr_array_header_t *prop_diffs;
363
svn_string_t *cr_str, *cdate, *last_author;
364
svn_boolean_t changed;
365
const svn_prop_t *pc;
368
/* Fetch the created-rev and send entry props. */
369
SVN_ERR (svn_fs_node_created_rev (&crev, b->t_root, t_path, pool));
370
if (SVN_IS_VALID_REVNUM (crev))
372
/* Transmit the committed-rev. */
373
cr_str = svn_string_createf (pool, "%ld", crev);
374
SVN_ERR (change_fn (b, object,
375
SVN_PROP_ENTRY_COMMITTED_REV, cr_str, pool));
377
SVN_ERR (svn_fs_revision_proplist (&r_props, b->repos->fs, crev, pool));
379
/* Transmit the committed-date. */
380
cdate = apr_hash_get (r_props, SVN_PROP_REVISION_DATE,
381
APR_HASH_KEY_STRING);
383
SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_COMMITTED_DATE,
386
/* Transmit the last-author. */
387
last_author = apr_hash_get (r_props, SVN_PROP_REVISION_AUTHOR,
388
APR_HASH_KEY_STRING);
389
if (last_author || s_path)
390
SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_LAST_AUTHOR,
393
/* Transmit the UUID. */
394
SVN_ERR (svn_fs_get_uuid (b->repos->fs, &uuid, pool));
396
SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_UUID,
397
svn_string_create (uuid, pool), pool));
400
/* Update lock properties. */
403
SVN_ERR (svn_fs_get_lock (&lock, b->repos->fs, t_path, pool));
405
/* Delete a defunct lock. */
406
if (! lock || strcmp (lock_token, lock->token) != 0)
407
SVN_ERR (change_fn (b, object, SVN_PROP_ENTRY_LOCK_TOKEN,
413
SVN_ERR (get_source_root (b, &s_root, s_rev));
415
/* Is this deltification worth our time? */
416
SVN_ERR (svn_fs_props_changed (&changed, b->t_root, t_path, s_root,
421
/* If so, go ahead and get the source path's properties. */
422
SVN_ERR (svn_fs_node_proplist (&s_props, s_root, s_path, pool));
425
s_props = apr_hash_make (pool);
427
/* Get the target path's properties */
428
SVN_ERR (svn_fs_node_proplist (&t_props, b->t_root, t_path, pool));
430
/* Now transmit the differences. */
431
SVN_ERR (svn_prop_diffs (&prop_diffs, t_props, s_props, pool));
432
for (i = 0; i < prop_diffs->nelts; i++)
434
pc = &APR_ARRAY_IDX (prop_diffs, i, svn_prop_t);
435
SVN_ERR (change_fn (b, object, pc->name, pc->value, pool));
441
/* Set *CHANGED_P to TRUE if ROOT1/PATH1 and ROOT2/PATH2 have
442
different contents, FALSE if they have the same contents. */
444
compare_files (svn_boolean_t *changed_p, svn_fs_root_t *root1,
445
const char *path1, svn_fs_root_t *root2, const char *path2,
448
svn_filesize_t size1, size2;
449
unsigned char digest1[APR_MD5_DIGESTSIZE], digest2[APR_MD5_DIGESTSIZE];
450
svn_stream_t *stream1, *stream2;
451
char buf1[SVN_STREAM_CHUNK_SIZE], buf2[SVN_STREAM_CHUNK_SIZE];
452
apr_size_t len1, len2;
454
/* If the filesystem claims the things haven't changed, then they
456
SVN_ERR (svn_fs_contents_changed (changed_p, root1, path1,
457
root2, path2, pool));
461
/* From this point on, assume things haven't changed. */
464
/* So, things have changed. But we need to know if the two sets of
465
file contents are actually different. If they have differing
466
sizes, then we know they differ. */
467
SVN_ERR (svn_fs_file_length (&size1, root1, path1, pool));
468
SVN_ERR (svn_fs_file_length (&size2, root2, path2, pool));
475
/* Same sizes, huh? Well, if their checksums differ, we know they
477
SVN_ERR (svn_fs_file_md5_checksum (digest1, root1, path1, pool));
478
SVN_ERR (svn_fs_file_md5_checksum (digest2, root2, path2, pool));
479
if (!svn_md5_digests_match (digest1, digest2))
485
/* Same sizes, same checksums. Chances are reallllly good that they
486
don't differ, but to be absolute sure, we need to compare bytes. */
487
SVN_ERR (svn_fs_file_contents (&stream1, root1, path1, pool));
488
SVN_ERR (svn_fs_file_contents (&stream2, root2, path2, pool));
492
len1 = len2 = SVN_STREAM_CHUNK_SIZE;
493
SVN_ERR (svn_stream_read (stream1, buf1, &len1));
494
SVN_ERR (svn_stream_read (stream2, buf2, &len2));
496
if (len1 != len2 || memcmp (buf1, buf2, len1))
507
/* Make the appropriate edits on FILE_BATON to change its contents and
508
properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
509
possibly using LOCK_TOKEN to determine if the client's lock on the file
512
delta_files (report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
513
const char *s_path, const char *t_path, const char *lock_token,
516
svn_boolean_t changed;
517
svn_fs_root_t *s_root = NULL;
518
svn_txdelta_stream_t *dstream = NULL;
519
unsigned char s_digest[APR_MD5_DIGESTSIZE];
520
const char *s_hex_digest = NULL;
521
svn_txdelta_window_handler_t dhandler;
524
/* Compare the files' property lists. */
525
SVN_ERR (delta_proplists (b, s_rev, s_path, t_path, lock_token,
526
change_file_prop, file_baton, pool));
530
SVN_ERR (get_source_root (b, &s_root, s_rev));
532
/* Is this delta calculation worth our time? If we are ignoring
533
ancestry, then our editor implementor isn't concerned by the
534
theoretical differences between "has contents which have not
535
changed with respect to" and "has the same actual contents
536
as". We'll do everything we can to avoid transmitting even
537
an empty text-delta in that case. */
538
if (b->ignore_ancestry)
539
SVN_ERR (compare_files (&changed, b->t_root, t_path, s_root, s_path,
542
SVN_ERR (svn_fs_contents_changed (&changed, b->t_root, t_path, s_root,
547
SVN_ERR (svn_fs_file_md5_checksum (s_digest, s_root, s_path, pool));
548
s_hex_digest = svn_md5_digest_to_cstring (s_digest, pool);
551
/* Send the delta stream if desired, or just a NULL window if not. */
552
SVN_ERR (b->editor->apply_textdelta (file_baton, s_hex_digest, pool,
553
&dhandler, &dbaton));
556
SVN_ERR (svn_fs_get_file_delta_stream (&dstream, s_root, s_path,
557
b->t_root, t_path, pool));
558
return svn_txdelta_send_txstream (dstream, dhandler, dbaton, pool);
561
return dhandler (NULL, dbaton);
564
/* Determine if the user is authorized to view B->t_root/PATH. */
566
check_auth (report_baton_t *b, svn_boolean_t *allowed, const char *path,
569
if (b->authz_read_func)
570
return b->authz_read_func (allowed, b->t_root, path,
571
b->authz_read_baton, pool);
576
/* Create a dirent in *ENTRY for the given ROOT and PATH. We use this to
577
replace the source or target dirent when a report pathinfo tells us to
578
change paths or revisions. */
580
fake_dirent (const svn_fs_dirent_t **entry, svn_fs_root_t *root,
581
const char *path, apr_pool_t *pool)
583
svn_node_kind_t kind;
584
svn_fs_dirent_t *ent;
586
SVN_ERR (svn_fs_check_path (&kind, root, path, pool));
587
if (kind == svn_node_none)
591
ent = apr_palloc (pool, sizeof (**entry));
592
ent->name = svn_path_basename (path, pool);
593
SVN_ERR (svn_fs_node_id (&ent->id, root, path, pool));
601
/* Emit a series of editing operations to transform a source entry to
604
S_REV and S_PATH specify the source entry. S_ENTRY contains the
605
already-looked-up information about the node-revision existing at
608
B->t_root and T_PATH specify the target entry. T_ENTRY contains
609
the already-looked-up information about the node-revision existing
612
DIR_BATON and E_PATH contain the parameters which should be passed
613
to the editor calls--DIR_BATON for the parent directory baton and
614
E_PATH for the pathname. (E_PATH is the anchor-relative working
615
copy pathname, which may differ from the source and target
616
pathnames if the report contains a link_path.)
618
INFO contains the report information for this working copy path.
619
This function will internally modify the source and target entries
620
as appropriate based on the report information.
622
If RECURSE is not set, avoid operating on directories. (Normally
623
RECURSE is simply taken from B->recurse, but drive() needs to force
624
us to recurse into the target even if that flag is not set.) */
626
update_entry (report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
627
const svn_fs_dirent_t *s_entry, const char *t_path,
628
const svn_fs_dirent_t *t_entry, void *dir_baton,
629
const char *e_path, path_info_t *info, svn_boolean_t recurse,
632
svn_fs_root_t *s_root;
633
svn_boolean_t allowed, related;
635
unsigned char digest[APR_MD5_DIGESTSIZE];
636
const char *hex_digest;
639
/* For non-switch operations, follow link_path in the target. */
640
if (info && info->link_path && !b->is_switch)
642
t_path = info->link_path;
643
SVN_ERR (fake_dirent (&t_entry, b->t_root, t_path, pool));
646
if (info && !SVN_IS_VALID_REVNUM (info->rev))
648
/* Delete this entry in the source. */
652
else if (info && s_path)
654
/* Follow the rev and possibly path in this entry. */
655
s_path = (info->link_path) ? info->link_path : s_path;
657
SVN_ERR (get_source_root (b, &s_root, s_rev));
658
SVN_ERR (fake_dirent (&s_entry, s_root, s_path, pool));
661
/* Don't let the report carry us somewhere nonexistent. */
662
if (s_path && !s_entry)
663
return svn_error_createf (SVN_ERR_FS_NOT_FOUND, NULL,
664
_("Working copy path '%s' does not exist in "
665
"repository"), e_path);
667
if (!recurse && ((s_entry && s_entry->kind == svn_node_dir)
668
|| (t_entry && t_entry->kind == svn_node_dir)))
669
return skip_path_info (b, e_path);
671
/* If the source and target both exist and are of the same kind,
672
then find out whether they're related. If they're exactly the
673
same, then we don't have to do anything (unless the report has
674
changes to the source). If we're ignoring ancestry, then any two
675
nodes of the same type are related enough for us. */
677
if (s_entry && t_entry && s_entry->kind == t_entry->kind)
679
distance = svn_fs_compare_ids (s_entry->id, t_entry->id);
680
if (distance == 0 && !any_path_info (b, e_path)
681
&& (!info || (!info->start_empty && !info->lock_token)))
683
else if (distance != -1 || b->ignore_ancestry)
687
/* If there's a source and it's not related to the target, nuke it. */
688
if (s_entry && !related)
690
SVN_ERR (b->editor->delete_entry (e_path, SVN_INVALID_REVNUM, dir_baton,
695
/* If there's no target, we have nothing more to do. */
697
return skip_path_info (b, e_path);
699
/* Check if the user is authorized to find out about the target. */
700
SVN_ERR (check_auth (b, &allowed, t_path, pool));
703
if (t_entry->kind == svn_node_dir)
704
SVN_ERR (b->editor->absent_directory (e_path, dir_baton, pool));
706
SVN_ERR (b->editor->absent_file (e_path, dir_baton, pool));
707
return skip_path_info (b, e_path);
710
if (t_entry->kind == svn_node_dir)
713
SVN_ERR (b->editor->open_directory (e_path, dir_baton, s_rev, pool,
716
SVN_ERR (b->editor->add_directory (e_path, dir_baton, NULL,
717
SVN_INVALID_REVNUM, pool,
719
SVN_ERR (delta_dirs (b, s_rev, s_path, t_path, new_baton, e_path,
720
info ? info->start_empty : FALSE, pool));
721
return b->editor->close_directory (new_baton, pool);
726
SVN_ERR (b->editor->open_file (e_path, dir_baton, s_rev, pool,
729
SVN_ERR (b->editor->add_file (e_path, dir_baton, NULL,
730
SVN_INVALID_REVNUM, pool, &new_baton));
731
SVN_ERR (delta_files (b, new_baton, s_rev, s_path, t_path,
732
info ? info->lock_token : NULL, pool));
733
SVN_ERR (svn_fs_file_md5_checksum (digest, b->t_root, t_path, pool));
734
hex_digest = svn_md5_digest_to_cstring (digest, pool);
735
return b->editor->close_file (new_baton, hex_digest, pool);
740
/* Emit edits within directory DIR_BATON (with corresponding path
741
E_PATH) with the changes from the directory S_REV/S_PATH to the
742
directory B->t_rev/T_PATH. S_PATH may be NULL if the entry does
743
not exist in the source. */
745
delta_dirs (report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
746
const char *t_path, void *dir_baton, const char *e_path,
747
svn_boolean_t start_empty, apr_pool_t *pool)
749
svn_fs_root_t *s_root;
750
apr_hash_t *s_entries = NULL, *t_entries;
751
apr_hash_index_t *hi;
753
const svn_fs_dirent_t *s_entry, *t_entry;
755
const char *name, *s_fullpath, *t_fullpath, *e_fullpath;
758
/* Compare the property lists. If we're starting empty, pass a NULL
759
source path so that we add all the properties.
761
When we support directory locks, we must pass the lock token here. */
762
SVN_ERR (delta_proplists (b, s_rev, start_empty ? NULL : s_path, t_path,
763
NULL, change_dir_prop, dir_baton, pool));
765
/* Get the list of entries in each of source and target. */
766
if (s_path && !start_empty)
768
SVN_ERR (get_source_root (b, &s_root, s_rev));
769
SVN_ERR (svn_fs_dir_entries (&s_entries, s_root, s_path, pool));
771
SVN_ERR (svn_fs_dir_entries (&t_entries, b->t_root, t_path, pool));
773
/* Iterate over the report information for this directory. */
774
subpool = svn_pool_create (pool);
778
svn_pool_clear (subpool);
779
SVN_ERR (fetch_path_info (b, &name, &info, e_path, subpool));
783
if (info && !SVN_IS_VALID_REVNUM (info->rev))
785
/* We want to perform deletes before non-replacement adds,
786
for graceful handling of case-only renames on
787
case-insensitive client filesystems. So, if the report
788
item is a delete, remove the entry from the source hash,
789
but don't update the entry yet. */
791
apr_hash_set (s_entries, name, APR_HASH_KEY_STRING, NULL);
795
e_fullpath = svn_path_join (e_path, name, subpool);
796
t_fullpath = svn_path_join (t_path, name, subpool);
797
t_entry = apr_hash_get (t_entries, name, APR_HASH_KEY_STRING);
798
s_fullpath = s_path ? svn_path_join (s_path, name, subpool) : NULL;
799
s_entry = s_entries ?
800
apr_hash_get (s_entries, name, APR_HASH_KEY_STRING) : NULL;
802
SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, t_fullpath,
803
t_entry, dir_baton, e_fullpath, info,
804
b->recurse, subpool));
806
/* Don't revisit this name in the target or source entries. */
807
apr_hash_set (t_entries, name, APR_HASH_KEY_STRING, NULL);
809
apr_hash_set (s_entries, name, APR_HASH_KEY_STRING, NULL);
811
/* pathinfo entries live in their own subpools due to lookahead,
812
so we need to clear each one out as we finish with it. */
814
svn_pool_destroy (info->pool);
817
/* Remove any deleted entries. Do this before processing the
818
target, for graceful handling of case-only renames. */
821
for (hi = apr_hash_first (pool, s_entries); hi; hi = apr_hash_next (hi))
823
svn_pool_clear (subpool);
824
apr_hash_this (hi, NULL, NULL, &val);
827
if (apr_hash_get (t_entries, s_entry->name,
828
APR_HASH_KEY_STRING) == NULL)
830
/* There is no corresponding target entry, so delete. */
831
e_fullpath = svn_path_join (e_path, s_entry->name, subpool);
832
if (b->recurse || s_entry->kind != svn_node_dir)
833
SVN_ERR (b->editor->delete_entry (e_fullpath,
835
dir_baton, subpool));
840
/* Loop over the dirents in the target. */
841
for (hi = apr_hash_first (pool, t_entries); hi; hi = apr_hash_next (hi))
843
svn_pool_clear (subpool);
844
apr_hash_this (hi, NULL, NULL, &val);
847
/* Compose the report, editor, and target paths for this entry. */
848
e_fullpath = svn_path_join (e_path, t_entry->name, subpool);
849
t_fullpath = svn_path_join (t_path, t_entry->name, subpool);
851
/* Look for an entry with the same name in the source dirents. */
852
s_entry = s_entries ?
853
apr_hash_get (s_entries, t_entry->name, APR_HASH_KEY_STRING) : NULL;
854
s_fullpath = s_entry ? svn_path_join (s_path, t_entry->name, subpool)
857
SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, t_fullpath,
858
t_entry, dir_baton, e_fullpath, NULL,
859
b->recurse, subpool));
862
/* Destroy iteration subpool. */
863
svn_pool_destroy (subpool);
869
drive (report_baton_t *b, svn_revnum_t s_rev, path_info_t *info,
872
const char *t_anchor, *s_fullpath;
873
svn_boolean_t allowed, info_is_set_path;
874
svn_fs_root_t *s_root;
875
const svn_fs_dirent_t *s_entry, *t_entry;
878
/* Compute the target path corresponding to the working copy anchor,
879
and check its authorization. */
880
t_anchor = *b->s_operand ? svn_path_dirname (b->t_path, pool) : b->t_path;
881
SVN_ERR (check_auth (b, &allowed, t_anchor, pool));
883
return svn_error_create
884
(SVN_ERR_AUTHZ_ROOT_UNREADABLE, NULL,
885
_("Not authorized to open root of edit operation"));
887
SVN_ERR (b->editor->set_target_revision (b->edit_baton, b->t_rev, pool));
889
/* Collect information about the source and target nodes. */
890
s_fullpath = svn_path_join (b->fs_base, b->s_operand, pool);
891
SVN_ERR (get_source_root (b, &s_root, s_rev));
892
SVN_ERR (fake_dirent (&s_entry, s_root, s_fullpath, pool));
893
SVN_ERR (fake_dirent (&t_entry, b->t_root, b->t_path, pool));
895
/* If the operand is a locally added file or directory, it won't
896
exist in the source, so accept that. */
897
info_is_set_path = (SVN_IS_VALID_REVNUM (info->rev) && !info->link_path);
898
if (info_is_set_path && !s_entry)
901
/* If the anchor is the operand, the source and target must be dirs.
902
Check this before opening the root to avoid modifying the wc. */
903
if (!*b->s_operand && (!s_entry || s_entry->kind != svn_node_dir
904
|| !t_entry || t_entry->kind != svn_node_dir))
905
return svn_error_create (SVN_ERR_FS_PATH_SYNTAX, NULL,
906
_("Cannot replace a directory from within"));
908
SVN_ERR (b->editor->open_root (b->edit_baton, s_rev, pool, &root_baton));
910
/* If the anchor is the operand, diff the two directories; otherwise
911
update the operand within the anchor directory. */
913
SVN_ERR (delta_dirs (b, s_rev, s_fullpath, b->t_path, root_baton,
914
"", info->start_empty, pool));
916
SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, b->t_path,
917
t_entry, root_baton, b->s_operand, info,
920
SVN_ERR (b->editor->close_directory (root_baton, pool));
921
SVN_ERR (b->editor->close_edit (b->edit_baton, pool));
925
/* Initialize the baton fields for editor-driving, and drive the editor. */
927
finish_report (report_baton_t *b, apr_pool_t *pool)
935
/* Save our pool to manage the lookahead and fs_root cache with. */
938
/* Add an end marker and rewind the temporary file. */
939
SVN_ERR (svn_io_file_write_full (b->tempfile, "-", 1, NULL, pool));
941
SVN_ERR (svn_io_file_seek (b->tempfile, APR_SET, &offset, pool));
943
/* Read the first pathinfo from the report and verify that it is a top-level
945
SVN_ERR (read_path_info (&info, b->tempfile, pool));
946
if (!info || strcmp (info->path, b->s_operand) != 0
947
|| info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
948
return svn_error_create (SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
949
_("Invalid report for top level of working copy"));
952
/* Initialize the lookahead pathinfo. */
953
subpool = svn_pool_create (pool);
954
SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
956
if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0)
958
/* If the operand of the wc operation is switched or deleted,
959
then info above is just a place-holder, and the only thing we
960
have to do is pass the revision it contains to open_root.
961
The next pathinfo actually describes the target. */
963
return svn_error_create (SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
964
_("Two top-level reports with no target"));
966
SVN_ERR (read_path_info (&b->lookahead, b->tempfile, subpool));
969
/* Open the target root and initialize the source root cache. */
970
SVN_ERR (svn_fs_revision_root (&b->t_root, b->repos->fs, b->t_rev, pool));
971
for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
972
b->s_roots[i] = NULL;
974
return drive (b, s_rev, info, pool);
977
/* --- COLLECTING THE REPORT INFORMATION --- */
979
/* Record a report operation into the temporary file. */
981
write_path_info (report_baton_t *b, const char *path, const char *lpath,
982
svn_revnum_t rev, svn_boolean_t start_empty,
983
const char *lock_token, apr_pool_t *pool)
985
const char *lrep, *rrep, *ltrep, *rep;
987
/* Munge the path to be anchor-relative, so that we can use edit paths
989
path = svn_path_join (b->s_operand, path, pool);
991
lrep = lpath ? apr_psprintf (pool, "+%" APR_SIZE_T_FMT ":%s",
992
strlen(lpath), lpath) : "-";
993
rrep = (SVN_IS_VALID_REVNUM (rev)) ?
994
apr_psprintf (pool, "+%ld:", rev) : "-";
995
ltrep = lock_token ? apr_psprintf (pool, "+%" APR_SIZE_T_FMT ":%s",
996
strlen(lock_token), lock_token) : "-";
997
rep = apr_psprintf (pool, "+%" APR_SIZE_T_FMT ":%s%s%s%c%s",
998
strlen(path), path, lrep, rrep, start_empty ? '+' : '-',
1000
return svn_io_file_write_full (b->tempfile, rep, strlen(rep), NULL, pool);
1004
svn_repos_set_path2 (void *baton, const char *path, svn_revnum_t rev,
1005
svn_boolean_t start_empty, const char *lock_token,
1008
return write_path_info (baton, path, NULL, rev, start_empty,
1013
svn_repos_set_path (void *baton, const char *path, svn_revnum_t rev,
1014
svn_boolean_t start_empty, apr_pool_t *pool)
1016
return svn_repos_set_path2 (baton, path, rev, start_empty, NULL, pool);
1020
svn_repos_link_path2 (void *baton, const char *path, const char *link_path,
1021
svn_revnum_t rev, svn_boolean_t start_empty,
1022
const char *lock_token, apr_pool_t *pool)
1024
return write_path_info (baton, path, link_path, rev, start_empty, lock_token,
1029
svn_repos_link_path (void *baton, const char *path, const char *link_path,
1030
svn_revnum_t rev, svn_boolean_t start_empty,
1033
return svn_repos_link_path2 (baton, path, link_path, rev, start_empty,
1038
svn_repos_delete_path (void *baton, const char *path, apr_pool_t *pool)
1040
return write_path_info (baton, path, NULL, SVN_INVALID_REVNUM, FALSE, NULL,
1045
svn_repos_finish_report (void *baton, apr_pool_t *pool)
1047
report_baton_t *b = baton;
1048
svn_error_t *finish_err, *close_err;
1050
finish_err = finish_report (b, pool);
1051
close_err = svn_io_file_close (b->tempfile, pool);
1053
svn_error_clear (close_err);
1054
return finish_err ? finish_err : close_err;
1058
svn_repos_abort_report (void *baton, apr_pool_t *pool)
1060
report_baton_t *b = baton;
1062
return svn_io_file_close (b->tempfile, pool);
1065
/* --- BEGINNING THE REPORT --- */
1068
svn_repos_begin_report (void **report_baton,
1069
svn_revnum_t revnum,
1070
const char *username,
1072
const char *fs_base,
1073
const char *s_operand,
1074
const char *switch_path,
1075
svn_boolean_t text_deltas,
1076
svn_boolean_t recurse,
1077
svn_boolean_t ignore_ancestry,
1078
const svn_delta_editor_t *editor,
1080
svn_repos_authz_func_t authz_read_func,
1081
void *authz_read_baton,
1085
const char *tempdir, *dummy;
1087
/* Build a reporter baton. Copy strings in case the caller doesn't
1088
keep track of them. */
1089
b = apr_palloc (pool, sizeof (*b));
1091
b->fs_base = apr_pstrdup (pool, fs_base);
1092
b->s_operand = apr_pstrdup (pool, s_operand);
1094
b->t_path = switch_path ? switch_path
1095
: svn_path_join (fs_base, s_operand, pool);
1096
b->text_deltas = text_deltas;
1097
b->recurse = recurse;
1098
b->ignore_ancestry = ignore_ancestry;
1099
b->is_switch = (switch_path != NULL);
1101
b->edit_baton = edit_baton;
1102
b->authz_read_func = authz_read_func;
1103
b->authz_read_baton = authz_read_baton;
1105
SVN_ERR (svn_io_temp_dir (&tempdir, pool));
1106
SVN_ERR (svn_io_open_unique_file (&b->tempfile, &dummy,
1107
apr_psprintf (pool, "%s/report", tempdir),
1108
".tmp", TRUE, pool));
1110
/* Hand reporter back to client. */
1112
return SVN_NO_ERROR;