Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_repos / reporter.c
blob65680d266a6bb97cc895552ed7400aaf11781a20
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 return b->editor->close_directory(root_baton, pool);
1185 /* Initialize the baton fields for editor-driving, and drive the editor. */
1186 static svn_error_t *
1187 finish_report(report_baton_t *b, apr_pool_t *pool)
1189 apr_off_t offset;
1190 path_info_t *info;
1191 apr_pool_t *subpool;
1192 svn_revnum_t s_rev;
1193 int i;
1195 /* Save our pool to manage the lookahead and fs_root cache with. */
1196 b->pool = pool;
1198 /* Add an end marker and rewind the temporary file. */
1199 SVN_ERR(svn_io_file_write_full(b->tempfile, "-", 1, NULL, pool));
1200 offset = 0;
1201 SVN_ERR(svn_io_file_seek(b->tempfile, APR_SET, &offset, pool));
1203 /* Read the first pathinfo from the report and verify that it is a top-level
1204 set_path entry. */
1205 SVN_ERR(read_path_info(&info, b->tempfile, pool));
1206 if (!info || strcmp(info->path, b->s_operand) != 0
1207 || info->link_path || !SVN_IS_VALID_REVNUM(info->rev))
1208 return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
1209 _("Invalid report for top level of working copy"));
1210 s_rev = info->rev;
1212 /* Initialize the lookahead pathinfo. */
1213 subpool = svn_pool_create(pool);
1214 SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
1216 if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0)
1218 /* If the operand of the wc operation is switched or deleted,
1219 then info above is just a place-holder, and the only thing we
1220 have to do is pass the revision it contains to open_root.
1221 The next pathinfo actually describes the target. */
1222 if (!*b->s_operand)
1223 return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL,
1224 _("Two top-level reports with no target"));
1225 /* If the client issued a set-path followed by a delete-path, we need
1226 to respect the depth set by the initial set-path. */
1227 if (! SVN_IS_VALID_REVNUM(b->lookahead->rev))
1229 b->lookahead->depth = info->depth;
1231 info = b->lookahead;
1232 SVN_ERR(read_path_info(&b->lookahead, b->tempfile, subpool));
1235 /* Open the target root and initialize the source root cache. */
1236 SVN_ERR(svn_fs_revision_root(&b->t_root, b->repos->fs, b->t_rev, pool));
1237 for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++)
1238 b->s_roots[i] = NULL;
1241 svn_error_t *err = drive(b, s_rev, info, pool);
1242 if (err == SVN_NO_ERROR)
1243 return b->editor->close_edit(b->edit_baton, pool);
1244 svn_error_clear(b->editor->abort_edit(b->edit_baton, pool));
1245 return err;
1249 /* --- COLLECTING THE REPORT INFORMATION --- */
1251 /* Record a report operation into the temporary file. Return an error
1252 if DEPTH is svn_depth_unknown. */
1253 static svn_error_t *
1254 write_path_info(report_baton_t *b, const char *path, const char *lpath,
1255 svn_revnum_t rev, svn_depth_t depth,
1256 svn_boolean_t start_empty,
1257 const char *lock_token, apr_pool_t *pool)
1259 const char *lrep, *rrep, *drep, *ltrep, *rep;
1261 /* Munge the path to be anchor-relative, so that we can use edit paths
1262 as report paths. */
1263 path = svn_path_join(b->s_operand, path, pool);
1265 lrep = lpath ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
1266 strlen(lpath), lpath) : "-";
1267 rrep = (SVN_IS_VALID_REVNUM(rev)) ?
1268 apr_psprintf(pool, "+%ld:", rev) : "-";
1270 if (depth == svn_depth_exclude)
1271 drep = "+X";
1272 else if (depth == svn_depth_empty)
1273 drep = "+E";
1274 else if (depth == svn_depth_files)
1275 drep = "+F";
1276 else if (depth == svn_depth_immediates)
1277 drep = "+M";
1278 else if (depth == svn_depth_infinity)
1279 drep = "-";
1280 else
1281 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1282 _("Unsupported report depth '%s'"),
1283 svn_depth_to_word(depth));
1285 ltrep = lock_token ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s",
1286 strlen(lock_token), lock_token) : "-";
1287 rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s",
1288 strlen(path), path, lrep, rrep, drep,
1289 start_empty ? '+' : '-', ltrep);
1290 return svn_io_file_write_full(b->tempfile, rep, strlen(rep), NULL, pool);
1293 svn_error_t *
1294 svn_repos_set_path3(void *baton, const char *path, svn_revnum_t rev,
1295 svn_depth_t depth, svn_boolean_t start_empty,
1296 const char *lock_token, apr_pool_t *pool)
1298 return write_path_info(baton, path, NULL, rev, depth, start_empty,
1299 lock_token, pool);
1302 svn_error_t *
1303 svn_repos_set_path2(void *baton, const char *path, svn_revnum_t rev,
1304 svn_boolean_t start_empty, const char *lock_token,
1305 apr_pool_t *pool)
1307 return svn_repos_set_path3(baton, path, rev, svn_depth_infinity,
1308 start_empty, lock_token, pool);
1311 svn_error_t *
1312 svn_repos_set_path(void *baton, const char *path, svn_revnum_t rev,
1313 svn_boolean_t start_empty, apr_pool_t *pool)
1315 return svn_repos_set_path2(baton, path, rev, start_empty, NULL, pool);
1318 svn_error_t *
1319 svn_repos_link_path3(void *baton, const char *path, const char *link_path,
1320 svn_revnum_t rev, svn_depth_t depth,
1321 svn_boolean_t start_empty,
1322 const char *lock_token, apr_pool_t *pool)
1324 if (depth == svn_depth_exclude)
1325 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1326 _("Depth 'exclude' not supported for link"));
1328 return write_path_info(baton, path, link_path, rev, depth,
1329 start_empty, lock_token, pool);
1332 svn_error_t *
1333 svn_repos_link_path2(void *baton, const char *path, const char *link_path,
1334 svn_revnum_t rev, svn_boolean_t start_empty,
1335 const char *lock_token, apr_pool_t *pool)
1337 return svn_repos_link_path3(baton, path, link_path, rev, svn_depth_infinity,
1338 start_empty, lock_token, pool);
1341 svn_error_t *
1342 svn_repos_link_path(void *baton, const char *path, const char *link_path,
1343 svn_revnum_t rev, svn_boolean_t start_empty,
1344 apr_pool_t *pool)
1346 return svn_repos_link_path2(baton, path, link_path, rev, start_empty,
1347 NULL, pool);
1350 svn_error_t *
1351 svn_repos_delete_path(void *baton, const char *path, apr_pool_t *pool)
1353 /* We pass svn_depth_infinity because deletion of a path always
1354 deletes everything underneath it. */
1355 return write_path_info(baton, path, NULL, SVN_INVALID_REVNUM,
1356 svn_depth_infinity, FALSE, NULL, pool);
1359 svn_error_t *
1360 svn_repos_finish_report(void *baton, apr_pool_t *pool)
1362 report_baton_t *b = baton;
1363 svn_error_t *finish_err, *close_err;
1365 finish_err = finish_report(b, pool);
1366 close_err = svn_io_file_close(b->tempfile, pool);
1367 if (finish_err)
1368 svn_error_clear(close_err);
1369 return finish_err ? finish_err : close_err;
1372 svn_error_t *
1373 svn_repos_abort_report(void *baton, apr_pool_t *pool)
1375 report_baton_t *b = baton;
1377 return svn_io_file_close(b->tempfile, pool);
1380 /* --- BEGINNING THE REPORT --- */
1383 svn_error_t *
1384 svn_repos_begin_report2(void **report_baton,
1385 svn_revnum_t revnum,
1386 svn_repos_t *repos,
1387 const char *fs_base,
1388 const char *s_operand,
1389 const char *switch_path,
1390 svn_boolean_t text_deltas,
1391 svn_depth_t depth,
1392 svn_boolean_t ignore_ancestry,
1393 svn_boolean_t send_copyfrom_args,
1394 const svn_delta_editor_t *editor,
1395 void *edit_baton,
1396 svn_repos_authz_func_t authz_read_func,
1397 void *authz_read_baton,
1398 apr_pool_t *pool)
1400 report_baton_t *b;
1401 const char *tempdir;
1403 if (depth == svn_depth_exclude)
1404 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL,
1405 _("Request depth 'exclude' not supported"));
1407 /* Build a reporter baton. Copy strings in case the caller doesn't
1408 keep track of them. */
1409 b = apr_palloc(pool, sizeof(*b));
1410 b->repos = repos;
1411 b->fs_base = apr_pstrdup(pool, fs_base);
1412 b->s_operand = apr_pstrdup(pool, s_operand);
1413 b->t_rev = revnum;
1414 b->t_path = switch_path ? switch_path
1415 : svn_path_join(fs_base, s_operand, pool);
1416 b->text_deltas = text_deltas;
1417 b->requested_depth = depth;
1418 b->ignore_ancestry = ignore_ancestry;
1419 b->send_copyfrom_args = send_copyfrom_args;
1420 b->is_switch = (switch_path != NULL);
1421 b->editor = editor;
1422 b->edit_baton = edit_baton;
1423 b->authz_read_func = authz_read_func;
1424 b->authz_read_baton = authz_read_baton;
1426 SVN_ERR(svn_io_temp_dir(&tempdir, pool));
1427 SVN_ERR(svn_io_open_unique_file2(&b->tempfile, NULL,
1428 apr_psprintf(pool, "%s/report", tempdir),
1429 ".tmp", svn_io_file_del_on_close, pool));
1431 /* Hand reporter back to client. */
1432 *report_baton = b;
1433 return SVN_NO_ERROR;
1437 svn_error_t *
1438 svn_repos_begin_report(void **report_baton,
1439 svn_revnum_t revnum,
1440 const char *username,
1441 svn_repos_t *repos,
1442 const char *fs_base,
1443 const char *s_operand,
1444 const char *switch_path,
1445 svn_boolean_t text_deltas,
1446 svn_boolean_t recurse,
1447 svn_boolean_t ignore_ancestry,
1448 const svn_delta_editor_t *editor,
1449 void *edit_baton,
1450 svn_repos_authz_func_t authz_read_func,
1451 void *authz_read_baton,
1452 apr_pool_t *pool)
1454 return svn_repos_begin_report2(report_baton,
1455 revnum,
1456 repos,
1457 fs_base,
1458 s_operand,
1459 switch_path,
1460 text_deltas,
1461 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1462 ignore_ancestry,
1463 FALSE, /* don't send copyfrom args */
1464 editor,
1465 edit_baton,
1466 authz_read_func,
1467 authz_read_baton,
1468 pool);