Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_repos / reporter.c
blob55f7ccfddd000314afeeda9b715c0d60060d5707
1 /*
2 * reporter.c : `reporter' vtable routines for updates.
4 * ====================================================================
5 * Copyright (c) 2000-2006 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 * ====================================================================
19 #include "svn_path.h"
20 #include "svn_types.h"
21 #include "svn_error.h"
22 #include "svn_error_codes.h"
23 #include "svn_fs.h"
24 #include "svn_repos.h"
25 #include "svn_pools.h"
26 #include "svn_md5.h"
27 #include "svn_props.h"
28 #include "repos.h"
29 #include "svn_private_config.h"
31 #define NUM_CACHED_SOURCE_ROOTS 4
33 /* Theory of operation: we write report operations out to a temporary
34 file as we receive them. When the report is finished, we read the
35 operations back out again, using them to guide the progression of
36 the delta between the source and target revs.
38 Temporary file format: we use a simple ad-hoc format to store the
39 report operations. Each report operation is the concatention of
40 the following ("+/-" indicates the single character '+' or '-';
41 <length> and <revnum> are written out as decimal strings):
43 +/- '-' marks the end of the report
44 If previous is +:
45 <length>:<bytes> Length-counted path string
46 +/- '+' indicates the presence of link_path
47 If previous is +:
48 <length>:<bytes> Length-counted link_path string
49 +/- '+' indicates presence of revnum
50 If previous is +:
51 <revnum>: Revnum of set_path or link_path
52 +/- '+' indicates depth other than svn_depth_infinity
53 If previous is +:
54 <depth>: "X","E","F","M" =>
55 svn_depth_{exclude,empty,files,immediates}
56 +/- '+' indicates start_empty field set
57 +/- '+' indicates presence of lock_token field.
58 If previous is +:
59 <length>:<bytes> Length-counted lock_token string
61 Terminology: for brevity, this file frequently uses the prefixes
62 "s_" for source, "t_" for target, and "e_" for editor. Also, to
63 avoid overloading the word "target", we talk about the source
64 "anchor and operand", rather than the usual "anchor and target". */
66 /* Describes the state of a working copy subtree, as given by a
67 report. Because we keep a lookahead pathinfo, we need to allocate
68 each one of these things in a subpool of the report baton and free
69 it when done. */
70 typedef struct path_info_t
72 const char *path; /* path, munged to be anchor-relative */
73 const char *link_path; /* NULL for set_path or delete_path */
74 svn_revnum_t rev; /* SVN_INVALID_REVNUM for delete_path */
75 svn_depth_t depth; /* Depth of this path, meaningless for files */
76 svn_boolean_t start_empty; /* Meaningless for delete_path */
77 const char *lock_token; /* NULL if no token */
78 apr_pool_t *pool; /* Container pool */
79 } path_info_t;
81 /* A structure used by the routines within the `reporter' vtable,
82 driven by the client as it describes its working copy revisions. */
83 typedef struct report_baton_t
85 /* Parameters remembered from svn_repos_begin_report2 */
86 svn_repos_t *repos;
87 const char *fs_base; /* FS path corresponding to wc anchor */
88 const char *s_operand; /* Anchor-relative wc target (may be empty) */
89 svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */
90 const char *t_path; /* FS path the edit will bring the wc to */
91 svn_boolean_t text_deltas; /* Whether to report text deltas */
93 /* If the client requested a specific depth, record it here; if the
94 client did not, then this is svn_depth_unknown, and the depth of
95 information transmitted from server to client will be governed
96 strictly by the path-associated depths recorded in the report. */
97 svn_depth_t requested_depth;
99 svn_boolean_t ignore_ancestry;
100 svn_boolean_t send_copyfrom_args;
101 svn_boolean_t is_switch;
102 const svn_delta_editor_t *editor;
103 void *edit_baton;
104 svn_repos_authz_func_t authz_read_func;
105 void *authz_read_baton;
107 /* The temporary file in which we are stashing the report. */
108 apr_file_t *tempfile;
110 /* For the actual editor drive, we'll need a lookahead path info
111 entry, a cache of FS roots, and a pool to store them. */
112 path_info_t *lookahead;
113 svn_fs_root_t *t_root;
114 svn_fs_root_t *s_roots[NUM_CACHED_SOURCE_ROOTS];
115 apr_pool_t *pool;
116 } report_baton_t;
118 /* The type of a function that accepts changes to an object's property
119 list. OBJECT is the object whose properties are being changed.
120 NAME is the name of the property to change. VALUE is the new value
121 for the property, or zero if the property should be deleted. */
122 typedef svn_error_t *proplist_change_fn_t(report_baton_t *b, void *object,
123 const char *name,
124 const svn_string_t *value,
125 apr_pool_t *pool);
127 static svn_error_t *delta_dirs(report_baton_t *b, svn_revnum_t s_rev,
128 const char *s_path, const char *t_path,
129 void *dir_baton, const char *e_path,
130 svn_boolean_t start_empty,
131 svn_depth_t wc_depth,
132 svn_depth_t requested_depth,
133 apr_pool_t *pool);
135 /* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
137 static svn_error_t *
138 read_number(apr_uint64_t *num, apr_file_t *temp, apr_pool_t *pool)
140 char c;
142 *num = 0;
143 while (1)
145 SVN_ERR(svn_io_file_getc(&c, temp, pool));
146 if (c == ':')
147 break;
148 *num = *num * 10 + (c - '0');
150 return SVN_NO_ERROR;
153 static svn_error_t *
154 read_string(const char **str, apr_file_t *temp, apr_pool_t *pool)
156 apr_uint64_t len;
157 char *buf;
159 SVN_ERR(read_number(&len, temp, pool));
161 /* Len can never be less than zero. But could len be so large that
162 len + 1 wraps around and we end up passing 0 to apr_palloc(),
163 thus getting a pointer to no storage? Probably not (16 exabyte
164 string, anyone?) but let's be future-proof anyway. */
165 if (len + 1 < len)
167 /* xgettext doesn't expand preprocessor definitions, so we must
168 pass translatable string to apr_psprintf() function to create
169 intermediate string with appropriate format specifier. */
170 return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
171 apr_psprintf(pool,
172 _("Invalid length (%%%s) when "
173 "about to read a string"),
174 APR_UINT64_T_FMT),
175 len);
178 buf = apr_palloc(pool, len + 1);
179 SVN_ERR(svn_io_file_read_full(temp, buf, len, NULL, pool));
180 buf[len] = 0;
181 *str = buf;
182 return SVN_NO_ERROR;
185 static svn_error_t *
186 read_rev(svn_revnum_t *rev, apr_file_t *temp, apr_pool_t *pool)
188 char c;
189 apr_uint64_t num;
191 SVN_ERR(svn_io_file_getc(&c, temp, pool));
192 if (c == '+')
194 SVN_ERR(read_number(&num, temp, pool));
195 *rev = (svn_revnum_t) num;
197 else
198 *rev = SVN_INVALID_REVNUM;
199 return SVN_NO_ERROR;
202 /* Read a single character to set *DEPTH (having already read '+')
203 from TEMP. PATH is the path to which the depth applies, and is
204 used for error reporting only. */
205 static svn_error_t *
206 read_depth(svn_depth_t *depth, apr_file_t *temp, const char *path,
207 apr_pool_t *pool)
209 char c;
211 SVN_ERR(svn_io_file_getc(&c, temp, pool));
212 switch (c)
214 case 'X':
215 *depth = svn_depth_exclude;
216 break;
217 case 'E':
218 *depth = svn_depth_empty;
219 break;
220 case 'F':
221 *depth = svn_depth_files;
222 break;
223 case 'M':
224 *depth = svn_depth_immediates;
225 break;
227 /* Note that we do not tolerate explicit representation of
228 svn_depth_infinity here, because that's not how
229 write_path_info() writes it. */
230 default:
231 return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
232 _("Invalid depth (%c) for path '%s'"), c, path);
235 return SVN_NO_ERROR;
238 /* Read a report operation *PI out of TEMP. Set *PI to NULL if we
239 have reached the end of the report. */
240 static svn_error_t *
241 read_path_info(path_info_t **pi, apr_file_t *temp, apr_pool_t *pool)
243 char c;
245 SVN_ERR(svn_io_file_getc(&c, temp, pool));
246 if (c == '-')
248 *pi = NULL;
249 return SVN_NO_ERROR;
252 *pi = apr_palloc(pool, sizeof(**pi));
253 SVN_ERR(read_string(&(*pi)->path, temp, pool));
254 SVN_ERR(svn_io_file_getc(&c, temp, pool));
255 if (c == '+')
256 SVN_ERR(read_string(&(*pi)->link_path, temp, pool));
257 else
258 (*pi)->link_path = NULL;
259 SVN_ERR(read_rev(&(*pi)->rev, temp, pool));
260 SVN_ERR(svn_io_file_getc(&c, temp, pool));
261 if (c == '+')
262 SVN_ERR(read_depth(&((*pi)->depth), temp, (*pi)->path, pool));
263 else
264 (*pi)->depth = svn_depth_infinity;
265 SVN_ERR(svn_io_file_getc(&c, temp, pool));
266 (*pi)->start_empty = (c == '+');
267 SVN_ERR(svn_io_file_getc(&c, temp, pool));
268 if (c == '+')
269 SVN_ERR(read_string(&(*pi)->lock_token, temp, pool));
270 else
271 (*pi)->lock_token = NULL;
272 (*pi)->pool = pool;
273 return SVN_NO_ERROR;
276 /* Return true if PI's path is a child of PREFIX (which has length PLEN). */
277 static svn_boolean_t
278 relevant(path_info_t *pi, const char *prefix, apr_size_t plen)
280 return (pi && strncmp(pi->path, prefix, plen) == 0 &&
281 (!*prefix || pi->path[plen] == '/'));
284 /* Fetch the next pathinfo from B->tempfile for a descendant of
285 PREFIX. If the next pathinfo is for an immediate child of PREFIX,
286 set *ENTRY to the path component of the report information and
287 *INFO to the path information for that entry. If the next pathinfo
288 is for a grandchild or other more remote descendant of PREFIX, set
289 *ENTRY to the immediate child corresponding to that descendant and
290 set *INFO to NULL. If the next pathinfo is not for a descendant of
291 PREFIX, or if we reach the end of the report, set both *ENTRY and
292 *INFO to NULL.
294 At all times, B->lookahead is presumed to be the next pathinfo not
295 yet returned as an immediate child, or NULL if we have reached the
296 end of the report. Because we use a lookahead element, we can't
297 rely on the usual nested pool lifetimes, so allocate each pathinfo
298 in a subpool of the report baton's pool. The caller should delete
299 (*INFO)->pool when it is done with the information. */
300 static svn_error_t *
301 fetch_path_info(report_baton_t *b, const char **entry, path_info_t **info,
302 const char *prefix, apr_pool_t *pool)
304 apr_size_t plen = strlen(prefix);
305 const char *relpath, *sep;
306 apr_pool_t *subpool;
308 if (!relevant(b->lookahead, prefix, plen))
310 /* No more entries relevant to prefix. */
311 *entry = NULL;
312 *info = NULL;
314 else
316 /* Take a look at the prefix-relative part of the path. */
317 relpath = b->lookahead->path + (*prefix ? plen + 1 : 0);
318 sep = strchr(relpath, '/');
319 if (sep)
321 /* Return the immediate child part; do not advance. */
322 *entry = apr_pstrmemdup(pool, relpath, sep - relpath);
323 *info = NULL;
325 else
327 /* This is an immediate child; return it and advance. */
328 *entry = relpath;
329 *info = b->lookahead;
330 subpool = svn_pool_create(b->pool);
331 SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
334 return SVN_NO_ERROR;
337 /* Skip all path info entries relevant to *PREFIX. Call this when the
338 editor drive skips a directory. */
339 static svn_error_t *
340 skip_path_info(report_baton_t *b, const char *prefix)
342 apr_size_t plen = strlen(prefix);
343 apr_pool_t *subpool;
345 while (relevant(b->lookahead, prefix, plen))
347 svn_pool_destroy(b->lookahead->pool);
348 subpool = svn_pool_create(b->pool);
349 SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
351 return SVN_NO_ERROR;
354 /* Return true if there is at least one path info entry relevant to *PREFIX. */
355 static svn_boolean_t
356 any_path_info(report_baton_t *b, const char *prefix)
358 return relevant(b->lookahead, prefix, strlen(prefix));
361 /* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */
363 /* While driving the editor, the target root will remain constant, but
364 we may have to jump around between source roots depending on the
365 state of the working copy. If we were to open a root each time we
366 revisit a rev, we would get no benefit from node-id caching; on the
367 other hand, if we hold open all the roots we ever visit, we'll use
368 an unbounded amount of memory. As a compromise, we maintain a
369 fixed-size LRU cache of source roots. get_source_root retrieves a
370 root from the cache, using POOL to allocate the new root if
371 necessary. Be careful not to hold onto the root for too long,
372 particularly after recursing, since another call to get_source_root
373 can close it. */
374 static svn_error_t *
375 get_source_root(report_baton_t *b, svn_fs_root_t **s_root, svn_revnum_t rev)
377 int i;
378 svn_fs_root_t *root, *prev = NULL;
380 /* Look for the desired root in the cache, sliding all the unmatched
381 entries backwards a slot to make room for the right one. */
382 for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
384 root = b->s_roots[i];
385 b->s_roots[i] = prev;
386 if (root && svn_fs_revision_root_revision(root) == rev)
387 break;
388 prev = root;
391 /* If we didn't find it, throw out the oldest root and open a new one. */
392 if (i == NUM_CACHED_SOURCE_ROOTS)
394 if (prev)
395 svn_fs_close_root(prev);
396 SVN_ERR(svn_fs_revision_root(&root, b->repos->fs, rev, b->pool));
399 /* Assign the desired root to the first cache slot and hand it back. */
400 b->s_roots[0] = root;
401 *s_root = root;
402 return SVN_NO_ERROR;
405 /* Call the directory property-setting function of B->editor to set
406 the property NAME to VALUE on DIR_BATON. */
407 static svn_error_t *
408 change_dir_prop(report_baton_t *b, void *dir_baton, const char *name,
409 const svn_string_t *value, apr_pool_t *pool)
411 return b->editor->change_dir_prop(dir_baton, name, value, pool);
414 /* Call the file property-setting function of B->editor to set the
415 property NAME to VALUE on FILE_BATON. */
416 static svn_error_t *
417 change_file_prop(report_baton_t *b, void *file_baton, const char *name,
418 const svn_string_t *value, apr_pool_t *pool)
420 return b->editor->change_file_prop(file_baton, name, value, pool);
423 /* Generate the appropriate property editing calls to turn the
424 properties of S_REV/S_PATH into those of B->t_root/T_PATH. If
425 S_PATH is NULL, this is an add, so assume the target starts with no
426 properties. Pass OBJECT on to the editor function wrapper
427 CHANGE_FN. */
428 static svn_error_t *
429 delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
430 const char *t_path, const char *lock_token,
431 proplist_change_fn_t *change_fn,
432 void *object, apr_pool_t *pool)
434 svn_fs_root_t *s_root;
435 apr_hash_t *s_props, *t_props, *r_props;
436 apr_array_header_t *prop_diffs;
437 int i;
438 svn_revnum_t crev;
439 const char *uuid;
440 svn_string_t *cr_str, *cdate, *last_author;
441 svn_boolean_t changed;
442 const svn_prop_t *pc;
443 svn_lock_t *lock;
445 /* Fetch the created-rev and send entry props. */
446 SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
447 if (SVN_IS_VALID_REVNUM(crev))
449 /* Transmit the committed-rev. */
450 cr_str = svn_string_createf(pool, "%ld", crev);
451 SVN_ERR(change_fn(b, object,
452 SVN_PROP_ENTRY_COMMITTED_REV, cr_str, pool));
454 SVN_ERR(svn_fs_revision_proplist(&r_props, b->repos->fs, crev, pool));
456 /* Transmit the committed-date. */
457 cdate = apr_hash_get(r_props, SVN_PROP_REVISION_DATE,
458 APR_HASH_KEY_STRING);
459 if (cdate || s_path)
460 SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_COMMITTED_DATE,
461 cdate, pool));
463 /* Transmit the last-author. */
464 last_author = apr_hash_get(r_props, SVN_PROP_REVISION_AUTHOR,
465 APR_HASH_KEY_STRING);
466 if (last_author || s_path)
467 SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LAST_AUTHOR,
468 last_author, pool));
470 /* Transmit the UUID. */
471 SVN_ERR(svn_fs_get_uuid(b->repos->fs, &uuid, pool));
472 SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_UUID,
473 svn_string_create(uuid, pool), pool));
476 /* Update lock properties. */
477 if (lock_token)
479 SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool));
481 /* Delete a defunct lock. */
482 if (! lock || strcmp(lock_token, lock->token) != 0)
483 SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LOCK_TOKEN,
484 NULL, pool));
487 if (s_path)
489 SVN_ERR(get_source_root(b, &s_root, s_rev));
491 /* Is this deltification worth our time? */
492 SVN_ERR(svn_fs_props_changed(&changed, b->t_root, t_path, s_root,
493 s_path, pool));
494 if (! changed)
495 return SVN_NO_ERROR;
497 /* If so, go ahead and get the source path's properties. */
498 SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool));
500 else
501 s_props = apr_hash_make(pool);
503 /* Get the target path's properties */
504 SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool));
506 /* Now transmit the differences. */
507 SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
508 for (i = 0; i < prop_diffs->nelts; i++)
510 pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
511 SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
514 return SVN_NO_ERROR;
517 /* Make the appropriate edits on FILE_BATON to change its contents and
518 properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
519 possibly using LOCK_TOKEN to determine if the client's lock on the file
520 is defunct. */
521 static svn_error_t *
522 delta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev,
523 const char *s_path, const char *t_path, const char *lock_token,
524 apr_pool_t *pool)
526 svn_boolean_t changed;
527 svn_fs_root_t *s_root = NULL;
528 svn_txdelta_stream_t *dstream = NULL;
529 unsigned char s_digest[APR_MD5_DIGESTSIZE];
530 const char *s_hex_digest = NULL;
531 svn_txdelta_window_handler_t dhandler;
532 void *dbaton;
534 /* Compare the files' property lists. */
535 SVN_ERR(delta_proplists(b, s_rev, s_path, t_path, lock_token,
536 change_file_prop, file_baton, pool));
538 if (s_path)
540 SVN_ERR(get_source_root(b, &s_root, s_rev));
542 /* Is this delta calculation worth our time? If we are ignoring
543 ancestry, then our editor implementor isn't concerned by the
544 theoretical differences between "has contents which have not
545 changed with respect to" and "has the same actual contents
546 as". We'll do everything we can to avoid transmitting even
547 an empty text-delta in that case. */
548 if (b->ignore_ancestry)
549 SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
550 s_root, s_path, pool));
551 else
552 SVN_ERR(svn_fs_contents_changed(&changed, b->t_root, t_path, s_root,
553 s_path, pool));
554 if (!changed)
555 return SVN_NO_ERROR;
557 SVN_ERR(svn_fs_file_md5_checksum(s_digest, s_root, s_path, pool));
558 s_hex_digest = svn_md5_digest_to_cstring(s_digest, pool);
561 /* Send the delta stream if desired, or just a NULL window if not. */
562 SVN_ERR(b->editor->apply_textdelta(file_baton, s_hex_digest, pool,
563 &dhandler, &dbaton));
564 if (b->text_deltas)
566 SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path,
567 b->t_root, t_path, pool));
568 return svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool);
570 else
571 return dhandler(NULL, dbaton);
574 /* Determine if the user is authorized to view B->t_root/PATH. */
575 static svn_error_t *
576 check_auth(report_baton_t *b, svn_boolean_t *allowed, const char *path,
577 apr_pool_t *pool)
579 if (b->authz_read_func)
580 return b->authz_read_func(allowed, b->t_root, path,
581 b->authz_read_baton, pool);
582 *allowed = TRUE;
583 return SVN_NO_ERROR;
586 /* Create a dirent in *ENTRY for the given ROOT and PATH. We use this to
587 replace the source or target dirent when a report pathinfo tells us to
588 change paths or revisions. */
589 static svn_error_t *
590 fake_dirent(const svn_fs_dirent_t **entry, svn_fs_root_t *root,
591 const char *path, apr_pool_t *pool)
593 svn_node_kind_t kind;
594 svn_fs_dirent_t *ent;
596 SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
597 if (kind == svn_node_none)
598 *entry = NULL;
599 else
601 ent = apr_palloc(pool, sizeof(**entry));
602 ent->name = svn_path_basename(path, pool);
603 SVN_ERR(svn_fs_node_id(&ent->id, root, path, pool));
604 ent->kind = kind;
605 *entry = ent;
607 return SVN_NO_ERROR;
611 /* Given REQUESTED_DEPTH, WC_DEPTH and the current entry's KIND,
612 determine whether we need to send the whole entry, not just deltas.
613 Please refer to delta_dirs' docstring for an explanation of the
614 conditionals below. */
615 static svn_boolean_t
616 is_depth_upgrade(svn_depth_t wc_depth,
617 svn_depth_t requested_depth,
618 svn_node_kind_t kind)
620 if (requested_depth == svn_depth_unknown
621 || requested_depth <= wc_depth
622 || wc_depth == svn_depth_immediates)
623 return FALSE;
625 if (kind == svn_node_file
626 && wc_depth == svn_depth_files)
627 return FALSE;
629 if (kind == svn_node_dir
630 && wc_depth == svn_depth_empty
631 && requested_depth == svn_depth_files)
632 return FALSE;
634 return TRUE;
638 /* Call the B->editor's add_file() function to create PATH as a child
639 of PARENT_BATON, returning a new baton in *NEW_FILE_BATON.
640 However, make an attempt to send 'copyfrom' arguments if they're
641 available, by examining the closest copy of the original file
642 O_PATH within B->t_root. If any copyfrom args are discovered,
643 return those in *COPYFROM_PATH and *COPYFROM_REV; otherwise leave
644 those return args untouched. */
645 static svn_error_t *
646 add_file_smartly(report_baton_t *b,
647 const char *path,
648 void *parent_baton,
649 const char *o_path,
650 void **new_file_baton,
651 const char **copyfrom_path,
652 svn_revnum_t *copyfrom_rev,
653 apr_pool_t *pool)
655 /* ### TODO: use a subpool to do this work, clear it at the end? */
656 svn_fs_t *fs = svn_repos_fs(b->repos);
657 svn_fs_root_t *closest_copy_root = NULL;
658 const char *closest_copy_path = NULL;
660 /* Pre-emptively assume no copyfrom args exist. */
661 *copyfrom_path = NULL;
662 *copyfrom_rev = SVN_INVALID_REVNUM;
664 if (b->send_copyfrom_args)
666 /* Find the destination of the nearest 'copy event' which may have
667 caused o_path@t_root to exist. svn_fs_closest_copy only returns paths
668 starting with '/', so make sure o_path always starts with a '/'
669 too. */
670 if (*o_path != '/')
671 o_path = apr_pstrcat(pool, "/", o_path, NULL);
673 SVN_ERR(svn_fs_closest_copy(&closest_copy_root, &closest_copy_path,
674 b->t_root, o_path, pool));
675 if (closest_copy_root != NULL)
677 /* If the destination of the copy event is the same path as
678 o_path, then we've found something interesting that should
679 have 'copyfrom' history. */
680 if (strcmp(closest_copy_path, o_path) == 0)
682 SVN_ERR(svn_fs_copied_from(copyfrom_rev, copyfrom_path,
683 closest_copy_root, closest_copy_path,
684 pool));
685 if (b->authz_read_func)
687 svn_boolean_t allowed;
688 svn_fs_root_t *copyfrom_root;
689 SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
690 *copyfrom_rev, pool));
691 SVN_ERR(b->authz_read_func(&allowed, copyfrom_root,
692 *copyfrom_path, b->authz_read_baton,
693 pool));
694 if (! allowed)
696 *copyfrom_path = NULL;
697 *copyfrom_rev = SVN_INVALID_REVNUM;
704 SVN_ERR(b->editor->add_file(path, parent_baton,
705 *copyfrom_path, *copyfrom_rev,
706 pool, new_file_baton));
707 return SVN_NO_ERROR;
711 /* Emit a series of editing operations to transform a source entry to
712 a target entry.
714 S_REV and S_PATH specify the source entry. S_ENTRY contains the
715 already-looked-up information about the node-revision existing at
716 that location. S_PATH and S_ENTRY may be NULL if the entry does
717 not exist in the source. S_PATH may be non-NULL and S_ENTRY may be
718 NULL if the caller expects INFO to modify the source to an existing
719 location.
721 B->t_root and T_PATH specify the target entry. T_ENTRY contains
722 the already-looked-up information about the node-revision existing
723 at that location. T_PATH and T_ENTRY may be NULL if the entry does
724 not exist in the target.
726 DIR_BATON and E_PATH contain the parameters which should be passed
727 to the editor calls--DIR_BATON for the parent directory baton and
728 E_PATH for the pathname. (E_PATH is the anchor-relative working
729 copy pathname, which may differ from the source and target
730 pathnames if the report contains a link_path.)
732 INFO contains the report information for this working copy path, or
733 NULL if there is none. This function will internally modify the
734 source and target entries as appropriate based on the report
735 information.
737 WC_DEPTH and REQUESTED_DEPTH are propagated to delta_dirs() if
738 necessary. Refer to delta_dirs' docstring to find out what
739 should happen for various combinations of WC_DEPTH/REQUESTED_DEPTH. */
740 static svn_error_t *
741 update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
742 const svn_fs_dirent_t *s_entry, const char *t_path,
743 const svn_fs_dirent_t *t_entry, void *dir_baton,
744 const char *e_path, path_info_t *info, svn_depth_t wc_depth,
745 svn_depth_t requested_depth, apr_pool_t *pool)
747 svn_fs_root_t *s_root;
748 svn_boolean_t allowed, related;
749 void *new_baton;
750 unsigned char digest[APR_MD5_DIGESTSIZE];
751 const char *hex_digest;
752 int distance;
754 /* For non-switch operations, follow link_path in the target. */
755 if (info && info->link_path && !b->is_switch)
757 t_path = info->link_path;
758 SVN_ERR(fake_dirent(&t_entry, b->t_root, t_path, pool));
761 if (info && !SVN_IS_VALID_REVNUM(info->rev))
763 /* Delete this entry in the source. */
764 s_path = NULL;
765 s_entry = NULL;
767 else if (info && s_path)
769 /* Follow the rev and possibly path in this entry. */
770 s_path = (info->link_path) ? info->link_path : s_path;
771 s_rev = info->rev;
772 SVN_ERR(get_source_root(b, &s_root, s_rev));
773 SVN_ERR(fake_dirent(&s_entry, s_root, s_path, pool));
776 /* Don't let the report carry us somewhere nonexistent. */
777 if (s_path && !s_entry)
778 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
779 _("Working copy path '%s' does not exist in "
780 "repository"), e_path);
782 /* If the source and target both exist and are of the same kind,
783 then find out whether they're related. If they're exactly the
784 same, then we don't have to do anything (unless the report has
785 changes to the source). If we're ignoring ancestry, then any two
786 nodes of the same type are related enough for us. */
787 related = FALSE;
788 if (s_entry && t_entry && s_entry->kind == t_entry->kind)
790 distance = svn_fs_compare_ids(s_entry->id, t_entry->id);
791 if (distance == 0 && !any_path_info(b, e_path)
792 && (!info || (!info->start_empty && !info->lock_token))
793 && (requested_depth <= wc_depth || t_entry->kind == svn_node_file))
794 return SVN_NO_ERROR;
795 else if (distance != -1 || b->ignore_ancestry)
796 related = TRUE;
799 /* If there's a source and it's not related to the target, nuke it. */
800 if (s_entry && !related)
802 svn_revnum_t deleted_rev;
804 SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path,
805 s_rev, b->t_rev, &deleted_rev,
806 pool));
807 SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton,
808 pool));
809 s_path = NULL;
812 /* If there's no target, we have nothing more to do. */
813 if (!t_entry)
814 return skip_path_info(b, e_path);
816 /* Check if the user is authorized to find out about the target. */
817 SVN_ERR(check_auth(b, &allowed, t_path, pool));
818 if (!allowed)
820 if (t_entry->kind == svn_node_dir)
821 SVN_ERR(b->editor->absent_directory(e_path, dir_baton, pool));
822 else
823 SVN_ERR(b->editor->absent_file(e_path, dir_baton, pool));
824 return skip_path_info(b, e_path);
827 if (t_entry->kind == svn_node_dir)
829 if (related)
830 SVN_ERR(b->editor->open_directory(e_path, dir_baton, s_rev, pool,
831 &new_baton));
832 else
833 SVN_ERR(b->editor->add_directory(e_path, dir_baton, NULL,
834 SVN_INVALID_REVNUM, pool,
835 &new_baton));
837 SVN_ERR(delta_dirs(b, s_rev, s_path, t_path, new_baton, e_path,
838 info ? info->start_empty : FALSE,
839 wc_depth, requested_depth, pool));
840 return b->editor->close_directory(new_baton, pool);
842 else
844 if (related)
846 SVN_ERR(b->editor->open_file(e_path, dir_baton, s_rev, pool,
847 &new_baton));
848 SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path,
849 info ? info->lock_token : NULL, pool));
851 else
853 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
854 const char *copyfrom_path = NULL;
855 SVN_ERR(add_file_smartly(b, e_path, dir_baton, t_path, &new_baton,
856 &copyfrom_path, &copyfrom_rev, pool));
857 if (! copyfrom_path)
858 /* Send txdelta between empty file (s_path@s_rev doesn't
859 exist) and added file (t_path@t_root). */
860 SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path,
861 info ? info->lock_token : NULL, pool));
862 else
863 /* Send txdelta between copied file (copyfrom_path@copyfrom_rev)
864 and added file (tpath@t_root). */
865 SVN_ERR(delta_files(b, new_baton, copyfrom_rev, copyfrom_path,
866 t_path, info ? info->lock_token : NULL, pool));
869 SVN_ERR(svn_fs_file_md5_checksum(digest, b->t_root, t_path, pool));
870 hex_digest = svn_md5_digest_to_cstring(digest, pool);
871 return b->editor->close_file(new_baton, hex_digest, pool);
875 /* A helper macro for when we have to recurse into subdirectories. */
876 #define DEPTH_BELOW_HERE(depth) ((depth) == svn_depth_immediates) ? \
877 svn_depth_empty : (depth)
879 /* Emit edits within directory DIR_BATON (with corresponding path
880 E_PATH) with the changes from the directory S_REV/S_PATH to the
881 directory B->t_rev/T_PATH. S_PATH may be NULL if the entry does
882 not exist in the source.
884 WC_DEPTH is this path's depth as reported by set_path/link_path.
885 REQUESTED_DEPTH is derived from the depth set by
886 svn_repos_begin_report().
888 When iterating over this directory's entries, the following tables
889 describe what happens for all possible combinations
890 of WC_DEPTH/REQUESTED_DEPTH (rows represent WC_DEPTH, columns
891 represent REQUESTED_DEPTH):
893 Legend:
894 X: ignore this entry (it's either below the requested depth, or
895 if the requested depth is svn_depth_unknown, below the working
896 copy depth)
897 o: handle this entry normally
898 U: handle the entry as if it were a newly added repository path
899 (the client is upgrading to a deeper wc and doesn't currently
900 have this entry, but it should be there after the upgrade, so we
901 need to send the whole thing, not just deltas)
903 For files:
904 ______________________________________________________________
905 | req. depth| unknown | empty | files | immediates | infinity |
906 |wc. depth | | | | | |
907 |___________|_________|_______|_______|____________|__________|
908 |empty | X | X | U | U | U |
909 |___________|_________|_______|_______|____________|__________|
910 |files | o | X | o | o | o |
911 |___________|_________|_______|_______|____________|__________|
912 |immediates | o | X | o | o | o |
913 |___________|_________|_______|_______|____________|__________|
914 |infinity | o | X | o | o | o |
915 |___________|_________|_______|_______|____________|__________|
917 For directories:
918 ______________________________________________________________
919 | req. depth| unknown | empty | files | immediates | infinity |
920 |wc. depth | | | | | |
921 |___________|_________|_______|_______|____________|__________|
922 |empty | X | X | X | U | U |
923 |___________|_________|_______|_______|____________|__________|
924 |files | X | X | X | U | U |
925 |___________|_________|_______|_______|____________|__________|
926 |immediates | o | X | X | o | o |
927 |___________|_________|_______|_______|____________|__________|
928 |infinity | o | X | X | o | o |
929 |___________|_________|_______|_______|____________|__________|
931 These rules are enforced by the is_depth_upgrade() function and by
932 various other checks below.
934 static svn_error_t *
935 delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path,
936 const char *t_path, void *dir_baton, const char *e_path,
937 svn_boolean_t start_empty, svn_depth_t wc_depth,
938 svn_depth_t requested_depth, apr_pool_t *pool)
940 svn_fs_root_t *s_root;
941 apr_hash_t *s_entries = NULL, *t_entries;
942 apr_hash_index_t *hi;
943 apr_pool_t *subpool;
944 const svn_fs_dirent_t *s_entry, *t_entry;
945 void *val;
946 const char *name, *s_fullpath, *t_fullpath, *e_fullpath;
947 path_info_t *info;
949 /* Compare the property lists. If we're starting empty, pass a NULL
950 source path so that we add all the properties.
952 When we support directory locks, we must pass the lock token here. */
953 SVN_ERR(delta_proplists(b, s_rev, start_empty ? NULL : s_path, t_path,
954 NULL, change_dir_prop, dir_baton, pool));
956 if (requested_depth > svn_depth_empty
957 || requested_depth == svn_depth_unknown)
959 /* Get the list of entries in each of source and target. */
960 if (s_path && !start_empty)
962 SVN_ERR(get_source_root(b, &s_root, s_rev));
963 SVN_ERR(svn_fs_dir_entries(&s_entries, s_root, s_path, pool));
965 SVN_ERR(svn_fs_dir_entries(&t_entries, b->t_root, t_path, pool));
967 /* Iterate over the report information for this directory. */
968 subpool = svn_pool_create(pool);
970 while (1)
972 svn_pool_clear(subpool);
973 SVN_ERR(fetch_path_info(b, &name, &info, e_path, subpool));
974 if (!name)
975 break;
977 /* Invalid revnum means we should delete, unless this is
978 just an excluded subpath. */
979 if (info
980 && !SVN_IS_VALID_REVNUM(info->rev)
981 && info->depth != svn_depth_exclude)
983 /* We want to perform deletes before non-replacement adds,
984 for graceful handling of case-only renames on
985 case-insensitive client filesystems. So, if the report
986 item is a delete, remove the entry from the source hash,
987 but don't update the entry yet. */
988 if (s_entries)
989 apr_hash_set(s_entries, name, APR_HASH_KEY_STRING, NULL);
990 continue;
993 e_fullpath = svn_path_join(e_path, name, subpool);
994 t_fullpath = svn_path_join(t_path, name, subpool);
995 t_entry = apr_hash_get(t_entries, name, APR_HASH_KEY_STRING);
996 s_fullpath = s_path ? svn_path_join(s_path, name, subpool) : NULL;
997 s_entry = s_entries ?
998 apr_hash_get(s_entries, name, APR_HASH_KEY_STRING) : NULL;
1000 /* The only special cases here are
1002 - When requested_depth is files but the reported path is
1003 a directory. This is technically a client error, but we
1004 handle it anyway, by skipping the entry.
1006 - When the reported depth is svn_depth_exclude.
1008 if ((! info || info->depth != svn_depth_exclude)
1009 && (requested_depth != svn_depth_files
1010 || ((! t_entry || t_entry->kind != svn_node_dir)
1011 && (! s_entry || s_entry->kind != svn_node_dir))))
1012 SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
1013 t_entry, dir_baton, e_fullpath, info,
1014 info ? info->depth
1015 : DEPTH_BELOW_HERE(wc_depth),
1016 DEPTH_BELOW_HERE(requested_depth), subpool));
1018 /* Don't revisit this name in the target or source entries. */
1019 apr_hash_set(t_entries, name, APR_HASH_KEY_STRING, NULL);
1020 if (s_entries)
1021 apr_hash_set(s_entries, name, APR_HASH_KEY_STRING, NULL);
1023 /* pathinfo entries live in their own subpools due to lookahead,
1024 so we need to clear each one out as we finish with it. */
1025 if (info)
1026 svn_pool_destroy(info->pool);
1029 /* Remove any deleted entries. Do this before processing the
1030 target, for graceful handling of case-only renames. */
1031 if (s_entries)
1033 for (hi = apr_hash_first(pool, s_entries);
1035 hi = apr_hash_next(hi))
1037 svn_pool_clear(subpool);
1038 apr_hash_this(hi, NULL, NULL, &val);
1039 s_entry = val;
1041 if (apr_hash_get(t_entries, s_entry->name,
1042 APR_HASH_KEY_STRING) == NULL)
1044 svn_revnum_t deleted_rev;
1046 if (s_entry->kind == svn_node_file
1047 && wc_depth < svn_depth_files)
1048 continue;
1050 if (s_entry->kind == svn_node_dir
1051 && (wc_depth < svn_depth_immediates
1052 || requested_depth == svn_depth_files))
1053 continue;
1055 /* There is no corresponding target entry, so delete. */
1056 e_fullpath = svn_path_join(e_path, s_entry->name, subpool);
1057 SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root),
1058 svn_path_join(t_path,
1059 s_entry->name,
1060 subpool),
1061 s_rev, b->t_rev,
1062 &deleted_rev, subpool));
1064 SVN_ERR(b->editor->delete_entry(e_fullpath,
1065 deleted_rev,
1066 dir_baton, subpool));
1071 /* Loop over the dirents in the target. */
1072 for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi))
1074 svn_pool_clear(subpool);
1075 apr_hash_this(hi, NULL, NULL, &val);
1076 t_entry = val;
1078 if (is_depth_upgrade(wc_depth, requested_depth, t_entry->kind))
1080 /* We're making the working copy deeper, pretend the source
1081 doesn't exist. */
1082 s_entry = NULL;
1083 s_fullpath = NULL;
1085 else
1087 if (t_entry->kind == svn_node_file
1088 && requested_depth == svn_depth_unknown
1089 && wc_depth < svn_depth_files)
1090 continue;
1092 if (t_entry->kind == svn_node_dir
1093 && (wc_depth < svn_depth_immediates
1094 || requested_depth == svn_depth_files))
1095 continue;
1097 /* Look for an entry with the same name
1098 in the source dirents. */
1099 s_entry = s_entries ?
1100 apr_hash_get(s_entries, t_entry->name, APR_HASH_KEY_STRING)
1101 : NULL;
1102 s_fullpath = s_entry ?
1103 svn_path_join(s_path, t_entry->name, subpool) : NULL;
1106 /* Compose the report, editor, and target paths for this entry. */
1107 e_fullpath = svn_path_join(e_path, t_entry->name, subpool);
1108 t_fullpath = svn_path_join(t_path, t_entry->name, subpool);
1110 SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath,
1111 t_entry, dir_baton, e_fullpath, NULL,
1112 DEPTH_BELOW_HERE(wc_depth),
1113 DEPTH_BELOW_HERE(requested_depth),
1114 subpool));
1118 /* Destroy iteration subpool. */
1119 svn_pool_destroy(subpool);
1121 return SVN_NO_ERROR;
1124 static svn_error_t *
1125 drive(report_baton_t *b, svn_revnum_t s_rev, path_info_t *info,
1126 apr_pool_t *pool)
1128 const char *t_anchor, *s_fullpath;
1129 svn_boolean_t allowed, info_is_set_path;
1130 svn_fs_root_t *s_root;
1131 const svn_fs_dirent_t *s_entry, *t_entry;
1132 void *root_baton;
1134 /* Compute the target path corresponding to the working copy anchor,
1135 and check its authorization. */
1136 t_anchor = *b->s_operand ? svn_path_dirname(b->t_path, pool) : b->t_path;
1137 SVN_ERR(check_auth(b, &allowed, t_anchor, pool));
1138 if (!allowed)
1139 return svn_error_create
1140 (SVN_ERR_AUTHZ_ROOT_UNREADABLE, NULL,
1141 _("Not authorized to open root of edit operation"));
1143 SVN_ERR(b->editor->set_target_revision(b->edit_baton, b->t_rev, pool));
1145 /* Collect information about the source and target nodes. */
1146 s_fullpath = svn_path_join(b->fs_base, b->s_operand, pool);
1147 SVN_ERR(get_source_root(b, &s_root, s_rev));
1148 SVN_ERR(fake_dirent(&s_entry, s_root, s_fullpath, pool));
1149 SVN_ERR(fake_dirent(&t_entry, b->t_root, b->t_path, pool));
1151 /* If the operand is a locally added file or directory, it won't
1152 exist in the source, so accept that. */
1153 info_is_set_path = (SVN_IS_VALID_REVNUM(info->rev) && !info->link_path);
1154 if (info_is_set_path && !s_entry)
1155 s_fullpath = NULL;
1157 /* Check if the target path exists first. */
1158 if (!*b->s_operand && !(t_entry))
1159 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, NULL,
1160 _("Target path does not exist"));
1162 /* If the anchor is the operand, the source and target must be dirs.
1163 Check this before opening the root to avoid modifying the wc. */
1164 else if (!*b->s_operand && (!s_entry || s_entry->kind != svn_node_dir
1165 || t_entry->kind != svn_node_dir))
1166 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, NULL,
1167 _("Cannot replace a directory from within"));
1169 SVN_ERR(b->editor->open_root(b->edit_baton, s_rev, pool, &root_baton));
1171 /* If the anchor is the operand, diff the two directories; otherwise
1172 update the operand within the anchor directory. */
1173 if (!*b->s_operand)
1174 SVN_ERR(delta_dirs(b, s_rev, s_fullpath, b->t_path, root_baton,
1175 "", info->start_empty, info->depth, b->requested_depth,
1176 pool));
1177 else
1178 SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, b->t_path,
1179 t_entry, root_baton, b->s_operand, info,
1180 info->depth, b->requested_depth, pool));
1182 SVN_ERR(b->editor->close_directory(root_baton, pool));
1183 SVN_ERR(b->editor->close_edit(b->edit_baton, pool));
1184 return SVN_NO_ERROR;
1187 /* Initialize the baton fields for editor-driving, and drive the editor. */
1188 static svn_error_t *
1189 finish_report(report_baton_t *b, apr_pool_t *pool)
1191 apr_off_t offset;
1192 path_info_t *info;
1193 apr_pool_t *subpool;
1194 svn_revnum_t s_rev;
1195 int i;
1197 /* Save our pool to manage the lookahead and fs_root cache with. */
1198 b->pool = pool;
1200 /* Add an end marker and rewind the temporary file. */
1201 SVN_ERR(svn_io_file_write_full(b->tempfile, "-", 1, NULL, pool));
1202 offset = 0;
1203 SVN_ERR(svn_io_file_seek(b->tempfile, APR_SET, &offset, pool));
1205 /* Read the first pathinfo from the report and verify that it is a top-level
1206 set_path entry. */
1207 SVN_ERR(read_path_info(&info, b->tempfile, pool));
1208 if (!info || strcmp(info->path, b->s_operand) != 0
1209 || info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
1210 return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
1211 _("Invalid report for top level of working copy"));
1212 s_rev = info->rev;
1214 /* Initialize the lookahead pathinfo. */
1215 subpool = svn_pool_create(pool);
1216 SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
1218 if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0)
1220 /* If the operand of the wc operation is switched or deleted,
1221 then info above is just a place-holder, and the only thing we
1222 have to do is pass the revision it contains to open_root.
1223 The next pathinfo actually describes the target. */
1224 if (!*b->s_operand)
1225 return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
1226 _("Two top-level reports with no target"));
1227 /* If the client issued a set-path followed by a delete-path, we need
1228 to respect the depth set by the initial set-path. */
1229 if (! SVN_IS_VALID_REVNUM(b->lookahead->rev))
1231 b->lookahead->depth = info->depth;
1233 info = b->lookahead;
1234 SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
1237 /* Open the target root and initialize the source root cache. */
1238 SVN_ERR(svn_fs_revision_root(&b->t_root, b->repos->fs, b->t_rev, pool));
1239 for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
1240 b->s_roots[i] = NULL;
1242 return drive(b, s_rev, info, pool);
1245 /* --- COLLECTING THE REPORT INFORMATION --- */
1247 /* Record a report operation into the temporary file. Return an error
1248 if DEPTH is svn_depth_unknown. */
1249 static svn_error_t *
1250 write_path_info(report_baton_t *b, const char *path, const char *lpath,
1251 svn_revnum_t rev, svn_depth_t depth,
1252 svn_boolean_t start_empty,
1253 const char *lock_token, apr_pool_t *pool)
1255 const char *lrep, *rrep, *drep, *ltrep, *rep;
1257 /* Munge the path to be anchor-relative, so that we can use edit paths
1258 as report paths. */
1259 path = svn_path_join(b->s_operand, path, pool);
1261 lrep = lpath ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
1262 strlen(lpath), lpath) : "-";
1263 rrep = (SVN_IS_VALID_REVNUM(rev)) ?
1264 apr_psprintf(pool, "+%ld:", rev) : "-";
1266 if (depth == svn_depth_exclude)
1267 drep = "+X";
1268 else if (depth == svn_depth_empty)
1269 drep = "+E";
1270 else if (depth == svn_depth_files)
1271 drep = "+F";
1272 else if (depth == svn_depth_immediates)
1273 drep = "+M";
1274 else if (depth == svn_depth_infinity)
1275 drep = "-";
1276 else
1277 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1278 _("Unsupported report depth '%s'"),
1279 svn_depth_to_word(depth));
1281 ltrep = lock_token ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
1282 strlen(lock_token), lock_token) : "-";
1283 rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s",
1284 strlen(path), path, lrep, rrep, drep,
1285 start_empty ? '+' : '-', ltrep);
1286 return svn_io_file_write_full(b->tempfile, rep, strlen(rep), NULL, pool);
1289 svn_error_t *
1290 svn_repos_set_path3(void *baton, const char *path, svn_revnum_t rev,
1291 svn_depth_t depth, svn_boolean_t start_empty,
1292 const char *lock_token, apr_pool_t *pool)
1294 return write_path_info(baton, path, NULL, rev, depth, start_empty,
1295 lock_token, pool);
1298 svn_error_t *
1299 svn_repos_set_path2(void *baton, const char *path, svn_revnum_t rev,
1300 svn_boolean_t start_empty, const char *lock_token,
1301 apr_pool_t *pool)
1303 return svn_repos_set_path3(baton, path, rev, svn_depth_infinity,
1304 start_empty, lock_token, pool);
1307 svn_error_t *
1308 svn_repos_set_path(void *baton, const char *path, svn_revnum_t rev,
1309 svn_boolean_t start_empty, apr_pool_t *pool)
1311 return svn_repos_set_path2(baton, path, rev, start_empty, NULL, pool);
1314 svn_error_t *
1315 svn_repos_link_path3(void *baton, const char *path, const char *link_path,
1316 svn_revnum_t rev, svn_depth_t depth,
1317 svn_boolean_t start_empty,
1318 const char *lock_token, apr_pool_t *pool)
1320 if (depth == svn_depth_exclude)
1321 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1322 _("Depth 'exclude' not supported for link"));
1324 return write_path_info(baton, path, link_path, rev, depth,
1325 start_empty, lock_token, pool);
1328 svn_error_t *
1329 svn_repos_link_path2(void *baton, const char *path, const char *link_path,
1330 svn_revnum_t rev, svn_boolean_t start_empty,
1331 const char *lock_token, apr_pool_t *pool)
1333 return svn_repos_link_path3(baton, path, link_path, rev, svn_depth_infinity,
1334 start_empty, lock_token, pool);
1337 svn_error_t *
1338 svn_repos_link_path(void *baton, const char *path, const char *link_path,
1339 svn_revnum_t rev, svn_boolean_t start_empty,
1340 apr_pool_t *pool)
1342 return svn_repos_link_path2(baton, path, link_path, rev, start_empty,
1343 NULL, pool);
1346 svn_error_t *
1347 svn_repos_delete_path(void *baton, const char *path, apr_pool_t *pool)
1349 /* We pass svn_depth_infinity because deletion of a path always
1350 deletes everything underneath it. */
1351 return write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
1352 svn_depth_infinity, FALSE, NULL, pool);
1355 svn_error_t *
1356 svn_repos_finish_report(void *baton, apr_pool_t *pool)
1358 report_baton_t *b = baton;
1359 svn_error_t *finish_err, *close_err;
1361 finish_err = finish_report(b, pool);
1362 close_err = svn_io_file_close(b->tempfile, pool);
1363 if (finish_err)
1364 svn_error_clear(close_err);
1365 return finish_err ? finish_err : close_err;
1368 svn_error_t *
1369 svn_repos_abort_report(void *baton, apr_pool_t *pool)
1371 report_baton_t *b = baton;
1373 return svn_io_file_close(b->tempfile, pool);
1376 /* --- BEGINNING THE REPORT --- */
1379 svn_error_t *
1380 svn_repos_begin_report2(void **report_baton,
1381 svn_revnum_t revnum,
1382 svn_repos_t *repos,
1383 const char *fs_base,
1384 const char *s_operand,
1385 const char *switch_path,
1386 svn_boolean_t text_deltas,
1387 svn_depth_t depth,
1388 svn_boolean_t ignore_ancestry,
1389 svn_boolean_t send_copyfrom_args,
1390 const svn_delta_editor_t *editor,
1391 void *edit_baton,
1392 svn_repos_authz_func_t authz_read_func,
1393 void *authz_read_baton,
1394 apr_pool_t *pool)
1396 report_baton_t *b;
1397 const char *tempdir;
1399 if (depth == svn_depth_exclude)
1400 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1401 _("Request depth 'exclude' not supported"));
1403 /* Build a reporter baton. Copy strings in case the caller doesn't
1404 keep track of them. */
1405 b = apr_palloc(pool, sizeof(*b));
1406 b->repos = repos;
1407 b->fs_base = apr_pstrdup(pool, fs_base);
1408 b->s_operand = apr_pstrdup(pool, s_operand);
1409 b->t_rev = revnum;
1410 b->t_path = switch_path ? switch_path
1411 : svn_path_join(fs_base, s_operand, pool);
1412 b->text_deltas = text_deltas;
1413 b->requested_depth = depth;
1414 b->ignore_ancestry = ignore_ancestry;
1415 b->send_copyfrom_args = send_copyfrom_args;
1416 b->is_switch = (switch_path != NULL);
1417 b->editor = editor;
1418 b->edit_baton = edit_baton;
1419 b->authz_read_func = authz_read_func;
1420 b->authz_read_baton = authz_read_baton;
1422 SVN_ERR(svn_io_temp_dir(&tempdir, pool));
1423 SVN_ERR(svn_io_open_unique_file2(&b->tempfile, NULL,
1424 apr_psprintf(pool, "%s/report", tempdir),
1425 ".tmp", svn_io_file_del_on_close, pool));
1427 /* Hand reporter back to client. */
1428 *report_baton = b;
1429 return SVN_NO_ERROR;
1433 svn_error_t *
1434 svn_repos_begin_report(void **report_baton,
1435 svn_revnum_t revnum,
1436 const char *username,
1437 svn_repos_t *repos,
1438 const char *fs_base,
1439 const char *s_operand,
1440 const char *switch_path,
1441 svn_boolean_t text_deltas,
1442 svn_boolean_t recurse,
1443 svn_boolean_t ignore_ancestry,
1444 const svn_delta_editor_t *editor,
1445 void *edit_baton,
1446 svn_repos_authz_func_t authz_read_func,
1447 void *authz_read_baton,
1448 apr_pool_t *pool)
1450 return svn_repos_begin_report2(report_baton,
1451 revnum,
1452 repos,
1453 fs_base,
1454 s_operand,
1455 switch_path,
1456 text_deltas,
1457 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1458 ignore_ancestry,
1459 FALSE, /* don't send copyfrom args */
1460 editor,
1461 edit_baton,
1462 authz_read_func,
1463 authz_read_baton,
1464 pool);