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 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
)
1191 apr_pool_t
*subpool
;
1195 /* Save our pool to manage the lookahead and fs_root cache with. */
1198 /* Add an end marker and rewind the temporary file. */
1199 SVN_ERR(svn_io_file_write_full(b
->tempfile
, "-", 1, NULL
, pool
));
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
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"));
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. */
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
));
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
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
)
1272 else if (depth
== svn_depth_empty
)
1274 else if (depth
== svn_depth_files
)
1276 else if (depth
== svn_depth_immediates
)
1278 else if (depth
== svn_depth_infinity
)
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
);
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
,
1303 svn_repos_set_path2(void *baton
, const char *path
, svn_revnum_t rev
,
1304 svn_boolean_t start_empty
, const char *lock_token
,
1307 return svn_repos_set_path3(baton
, path
, rev
, svn_depth_infinity
,
1308 start_empty
, lock_token
, pool
);
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
);
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
);
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
);
1342 svn_repos_link_path(void *baton
, const char *path
, const char *link_path
,
1343 svn_revnum_t rev
, svn_boolean_t start_empty
,
1346 return svn_repos_link_path2(baton
, path
, link_path
, rev
, start_empty
,
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
);
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
);
1368 svn_error_clear(close_err
);
1369 return finish_err
? finish_err
: close_err
;
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 --- */
1384 svn_repos_begin_report2(void **report_baton
,
1385 svn_revnum_t revnum
,
1387 const char *fs_base
,
1388 const char *s_operand
,
1389 const char *switch_path
,
1390 svn_boolean_t text_deltas
,
1392 svn_boolean_t ignore_ancestry
,
1393 svn_boolean_t send_copyfrom_args
,
1394 const svn_delta_editor_t
*editor
,
1396 svn_repos_authz_func_t authz_read_func
,
1397 void *authz_read_baton
,
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
));
1411 b
->fs_base
= apr_pstrdup(pool
, fs_base
);
1412 b
->s_operand
= apr_pstrdup(pool
, s_operand
);
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
);
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. */
1433 return SVN_NO_ERROR
;
1438 svn_repos_begin_report(void **report_baton
,
1439 svn_revnum_t revnum
,
1440 const char *username
,
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
,
1450 svn_repos_authz_func_t authz_read_func
,
1451 void *authz_read_baton
,
1454 return svn_repos_begin_report2(report_baton
,
1461 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1463 FALSE
, /* don't send copyfrom args */