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 * ====================================================================
20 #include "svn_types.h"
21 #include "svn_error.h"
22 #include "svn_error_codes.h"
24 #include "svn_repos.h"
25 #include "svn_pools.h"
27 #include "svn_props.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
45 <length>:<bytes> Length-counted path string
46 +/- '+' indicates the presence of link_path
48 <length>:<bytes> Length-counted link_path string
49 +/- '+' indicates presence of revnum
51 <revnum>: Revnum of set_path or link_path
52 +/- '+' indicates depth other than svn_depth_infinity
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.
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
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 */
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 */
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
;
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
];
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
,
124 const svn_string_t
*value
,
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
,
135 /* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */
138 read_number(apr_uint64_t
*num
, apr_file_t
*temp
, apr_pool_t
*pool
)
145 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
148 *num
= *num
* 10 + (c
- '0');
154 read_string(const char **str
, apr_file_t
*temp
, apr_pool_t
*pool
)
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. */
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
,
172 _("Invalid length (%%%s) when "
173 "about to read a string"),
178 buf
= apr_palloc(pool
, len
+ 1);
179 SVN_ERR(svn_io_file_read_full(temp
, buf
, len
, NULL
, pool
));
186 read_rev(svn_revnum_t
*rev
, apr_file_t
*temp
, apr_pool_t
*pool
)
191 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
194 SVN_ERR(read_number(&num
, temp
, pool
));
195 *rev
= (svn_revnum_t
) num
;
198 *rev
= SVN_INVALID_REVNUM
;
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. */
206 read_depth(svn_depth_t
*depth
, apr_file_t
*temp
, const char *path
,
211 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
215 *depth
= svn_depth_exclude
;
218 *depth
= svn_depth_empty
;
221 *depth
= svn_depth_files
;
224 *depth
= svn_depth_immediates
;
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. */
231 return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT
, NULL
,
232 _("Invalid depth (%c) for path '%s'"), c
, path
);
238 /* Read a report operation *PI out of TEMP. Set *PI to NULL if we
239 have reached the end of the report. */
241 read_path_info(path_info_t
**pi
, apr_file_t
*temp
, apr_pool_t
*pool
)
245 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
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
));
256 SVN_ERR(read_string(&(*pi
)->link_path
, temp
, pool
));
258 (*pi
)->link_path
= NULL
;
259 SVN_ERR(read_rev(&(*pi
)->rev
, temp
, pool
));
260 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
262 SVN_ERR(read_depth(&((*pi
)->depth
), temp
, (*pi
)->path
, pool
));
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
));
269 SVN_ERR(read_string(&(*pi
)->lock_token
, temp
, pool
));
271 (*pi
)->lock_token
= NULL
;
276 /* Return true if PI's path is a child of PREFIX (which has length PLEN). */
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
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. */
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
;
308 if (!relevant(b
->lookahead
, prefix
, plen
))
310 /* No more entries relevant to prefix. */
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
, '/');
321 /* Return the immediate child part; do not advance. */
322 *entry
= apr_pstrmemdup(pool
, relpath
, sep
- relpath
);
327 /* This is an immediate child; return it and advance. */
329 *info
= b
->lookahead
;
330 subpool
= svn_pool_create(b
->pool
);
331 SVN_ERR(read_path_info(&b
->lookahead
, b
->tempfile
, subpool
));
337 /* Skip all path info entries relevant to *PREFIX. Call this when the
338 editor drive skips a directory. */
340 skip_path_info(report_baton_t
*b
, const char *prefix
)
342 apr_size_t plen
= strlen(prefix
);
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
));
354 /* Return true if there is at least one path info entry relevant to *PREFIX. */
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
375 get_source_root(report_baton_t
*b
, svn_fs_root_t
**s_root
, svn_revnum_t rev
)
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
)
391 /* If we didn't find it, throw out the oldest root and open a new one. */
392 if (i
== NUM_CACHED_SOURCE_ROOTS
)
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
;
405 /* Call the directory property-setting function of B->editor to set
406 the property NAME to VALUE on DIR_BATON. */
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. */
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
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
;
440 svn_string_t
*cr_str
, *cdate
, *last_author
;
441 svn_boolean_t changed
;
442 const svn_prop_t
*pc
;
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
);
460 SVN_ERR(change_fn(b
, object
, SVN_PROP_ENTRY_COMMITTED_DATE
,
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
,
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. */
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
,
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
,
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
));
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
));
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
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
,
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
;
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
));
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
));
552 SVN_ERR(svn_fs_contents_changed(&changed
, b
->t_root
, t_path
, s_root
,
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
));
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
);
571 return dhandler(NULL
, dbaton
);
574 /* Determine if the user is authorized to view B->t_root/PATH. */
576 check_auth(report_baton_t
*b
, svn_boolean_t
*allowed
, const char *path
,
579 if (b
->authz_read_func
)
580 return b
->authz_read_func(allowed
, b
->t_root
, path
,
581 b
->authz_read_baton
, pool
);
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. */
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
)
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
));
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. */
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
)
625 if (kind
== svn_node_file
626 && wc_depth
== svn_depth_files
)
629 if (kind
== svn_node_dir
630 && wc_depth
== svn_depth_empty
631 && requested_depth
== svn_depth_files
)
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. */
646 add_file_smartly(report_baton_t
*b
,
650 void **new_file_baton
,
651 const char **copyfrom_path
,
652 svn_revnum_t
*copyfrom_rev
,
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 '/'
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
,
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(©from_root
, fs
,
690 *copyfrom_rev
, pool
));
691 SVN_ERR(b
->authz_read_func(&allowed
, copyfrom_root
,
692 *copyfrom_path
, b
->authz_read_baton
,
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
));
711 /* Emit a series of editing operations to transform a source entry to
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
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
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. */
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
;
750 unsigned char digest
[APR_MD5_DIGESTSIZE
];
751 const char *hex_digest
;
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. */
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
;
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. */
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
))
795 else if (distance
!= -1 || b
->ignore_ancestry
)
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
,
807 SVN_ERR(b
->editor
->delete_entry(e_path
, deleted_rev
, dir_baton
,
812 /* If there's no target, we have nothing more to do. */
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
));
820 if (t_entry
->kind
== svn_node_dir
)
821 SVN_ERR(b
->editor
->absent_directory(e_path
, dir_baton
, pool
));
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
)
830 SVN_ERR(b
->editor
->open_directory(e_path
, dir_baton
, s_rev
, pool
,
833 SVN_ERR(b
->editor
->add_directory(e_path
, dir_baton
, NULL
,
834 SVN_INVALID_REVNUM
, pool
,
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
);
846 SVN_ERR(b
->editor
->open_file(e_path
, dir_baton
, s_rev
, pool
,
848 SVN_ERR(delta_files(b
, new_baton
, s_rev
, s_path
, t_path
,
849 info
? info
->lock_token
: NULL
, pool
));
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 ©from_path
, ©from_rev
, pool
));
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
));
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):
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
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)
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 |___________|_________|_______|_______|____________|__________|
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.
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
;
944 const svn_fs_dirent_t
*s_entry
, *t_entry
;
946 const char *name
, *s_fullpath
, *t_fullpath
, *e_fullpath
;
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
);
972 svn_pool_clear(subpool
);
973 SVN_ERR(fetch_path_info(b
, &name
, &info
, e_path
, subpool
));
977 /* Invalid revnum means we should delete, unless this is
978 just an excluded subpath. */
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. */
989 apr_hash_set(s_entries
, name
, APR_HASH_KEY_STRING
, NULL
);
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
,
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
);
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. */
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. */
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
);
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
)
1050 if (s_entry
->kind
== svn_node_dir
1051 && (wc_depth
< svn_depth_immediates
1052 || requested_depth
== svn_depth_files
))
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
,
1062 &deleted_rev
, subpool
));
1064 SVN_ERR(b
->editor
->delete_entry(e_fullpath
,
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
);
1078 if (is_depth_upgrade(wc_depth
, requested_depth
, t_entry
->kind
))
1080 /* We're making the working copy deeper, pretend the source
1087 if (t_entry
->kind
== svn_node_file
1088 && requested_depth
== svn_depth_unknown
1089 && wc_depth
< svn_depth_files
)
1092 if (t_entry
->kind
== svn_node_dir
1093 && (wc_depth
< svn_depth_immediates
1094 || requested_depth
== svn_depth_files
))
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
)
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
),
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
,
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
;
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
));
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
)
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. */
1174 SVN_ERR(delta_dirs(b
, s_rev
, s_fullpath
, b
->t_path
, root_baton
,
1175 "", info
->start_empty
, info
->depth
, b
->requested_depth
,
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
)
1193 apr_pool_t
*subpool
;
1197 /* Save our pool to manage the lookahead and fs_root cache with. */
1200 /* Add an end marker and rewind the temporary file. */
1201 SVN_ERR(svn_io_file_write_full(b
->tempfile
, "-", 1, NULL
, pool
));
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
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"));
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. */
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
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
)
1268 else if (depth
== svn_depth_empty
)
1270 else if (depth
== svn_depth_files
)
1272 else if (depth
== svn_depth_immediates
)
1274 else if (depth
== svn_depth_infinity
)
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
);
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
,
1299 svn_repos_set_path2(void *baton
, const char *path
, svn_revnum_t rev
,
1300 svn_boolean_t start_empty
, const char *lock_token
,
1303 return svn_repos_set_path3(baton
, path
, rev
, svn_depth_infinity
,
1304 start_empty
, lock_token
, pool
);
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
);
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
);
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
);
1338 svn_repos_link_path(void *baton
, const char *path
, const char *link_path
,
1339 svn_revnum_t rev
, svn_boolean_t start_empty
,
1342 return svn_repos_link_path2(baton
, path
, link_path
, rev
, start_empty
,
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
);
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
);
1364 svn_error_clear(close_err
);
1365 return finish_err
? finish_err
: close_err
;
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 --- */
1380 svn_repos_begin_report2(void **report_baton
,
1381 svn_revnum_t revnum
,
1383 const char *fs_base
,
1384 const char *s_operand
,
1385 const char *switch_path
,
1386 svn_boolean_t text_deltas
,
1388 svn_boolean_t ignore_ancestry
,
1389 svn_boolean_t send_copyfrom_args
,
1390 const svn_delta_editor_t
*editor
,
1392 svn_repos_authz_func_t authz_read_func
,
1393 void *authz_read_baton
,
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
));
1407 b
->fs_base
= apr_pstrdup(pool
, fs_base
);
1408 b
->s_operand
= apr_pstrdup(pool
, s_operand
);
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
);
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. */
1429 return SVN_NO_ERROR
;
1434 svn_repos_begin_report(void **report_baton
,
1435 svn_revnum_t revnum
,
1436 const char *username
,
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
,
1446 svn_repos_authz_func_t authz_read_func
,
1447 void *authz_read_baton
,
1450 return svn_repos_begin_report2(report_baton
,
1457 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1459 FALSE
, /* don't send copyfrom args */