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 return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT
, NULL
,
168 _("Invalid length (%" APR_UINT64_T_FMT
") "
169 "when about to read a string"), len
);
172 buf
= apr_palloc(pool
, len
+ 1);
173 SVN_ERR(svn_io_file_read_full(temp
, buf
, len
, NULL
, pool
));
180 read_rev(svn_revnum_t
*rev
, apr_file_t
*temp
, apr_pool_t
*pool
)
185 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
188 SVN_ERR(read_number(&num
, temp
, pool
));
189 *rev
= (svn_revnum_t
) num
;
192 *rev
= SVN_INVALID_REVNUM
;
196 /* Read a single character to set *DEPTH (having already read '+')
197 from TEMP. PATH is the path to which the depth applies, and is
198 used for error reporting only. */
200 read_depth(svn_depth_t
*depth
, apr_file_t
*temp
, const char *path
,
205 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
209 *depth
= svn_depth_exclude
;
212 *depth
= svn_depth_empty
;
215 *depth
= svn_depth_files
;
218 *depth
= svn_depth_immediates
;
221 /* Note that we do not tolerate explicit representation of
222 svn_depth_infinity here, because that's not how
223 write_path_info() writes it. */
225 return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT
, NULL
,
226 _("Invalid depth (%c) for path '%s'"), c
, path
);
232 /* Read a report operation *PI out of TEMP. Set *PI to NULL if we
233 have reached the end of the report. */
235 read_path_info(path_info_t
**pi
, apr_file_t
*temp
, apr_pool_t
*pool
)
239 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
246 *pi
= apr_palloc(pool
, sizeof(**pi
));
247 SVN_ERR(read_string(&(*pi
)->path
, temp
, pool
));
248 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
250 SVN_ERR(read_string(&(*pi
)->link_path
, temp
, pool
));
252 (*pi
)->link_path
= NULL
;
253 SVN_ERR(read_rev(&(*pi
)->rev
, temp
, pool
));
254 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
256 SVN_ERR(read_depth(&((*pi
)->depth
), temp
, (*pi
)->path
, pool
));
258 (*pi
)->depth
= svn_depth_infinity
;
259 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
260 (*pi
)->start_empty
= (c
== '+');
261 SVN_ERR(svn_io_file_getc(&c
, temp
, pool
));
263 SVN_ERR(read_string(&(*pi
)->lock_token
, temp
, pool
));
265 (*pi
)->lock_token
= NULL
;
270 /* Return true if PI's path is a child of PREFIX (which has length PLEN). */
272 relevant(path_info_t
*pi
, const char *prefix
, apr_size_t plen
)
274 return (pi
&& strncmp(pi
->path
, prefix
, plen
) == 0 &&
275 (!*prefix
|| pi
->path
[plen
] == '/'));
278 /* Fetch the next pathinfo from B->tempfile for a descendent of
279 PREFIX. If the next pathinfo is for an immediate child of PREFIX,
280 set *ENTRY to the path component of the report information and
281 *INFO to the path information for that entry. If the next pathinfo
282 is for a grandchild or other more remote descendent of PREFIX, set
283 *ENTRY to the immediate child corresponding to that descendent and
284 set *INFO to NULL. If the next pathinfo is not for a descendent of
285 PREFIX, or if we reach the end of the report, set both *ENTRY and
288 At all times, B->lookahead is presumed to be the next pathinfo not
289 yet returned as an immediate child, or NULL if we have reached the
290 end of the report. Because we use a lookahead element, we can't
291 rely on the usual nested pool lifetimes, so allocate each pathinfo
292 in a subpool of the report baton's pool. The caller should delete
293 (*INFO)->pool when it is done with the information. */
295 fetch_path_info(report_baton_t
*b
, const char **entry
, path_info_t
**info
,
296 const char *prefix
, apr_pool_t
*pool
)
298 apr_size_t plen
= strlen(prefix
);
299 const char *relpath
, *sep
;
302 if (!relevant(b
->lookahead
, prefix
, plen
))
304 /* No more entries relevant to prefix. */
310 /* Take a look at the prefix-relative part of the path. */
311 relpath
= b
->lookahead
->path
+ (*prefix
? plen
+ 1 : 0);
312 sep
= strchr(relpath
, '/');
315 /* Return the immediate child part; do not advance. */
316 *entry
= apr_pstrmemdup(pool
, relpath
, sep
- relpath
);
321 /* This is an immediate child; return it and advance. */
323 *info
= b
->lookahead
;
324 subpool
= svn_pool_create(b
->pool
);
325 SVN_ERR(read_path_info(&b
->lookahead
, b
->tempfile
, subpool
));
331 /* Skip all path info entries relevant to *PREFIX. Call this when the
332 editor drive skips a directory. */
334 skip_path_info(report_baton_t
*b
, const char *prefix
)
336 apr_size_t plen
= strlen(prefix
);
339 while (relevant(b
->lookahead
, prefix
, plen
))
341 svn_pool_destroy(b
->lookahead
->pool
);
342 subpool
= svn_pool_create(b
->pool
);
343 SVN_ERR(read_path_info(&b
->lookahead
, b
->tempfile
, subpool
));
348 /* Return true if there is at least one path info entry relevant to *PREFIX. */
350 any_path_info(report_baton_t
*b
, const char *prefix
)
352 return relevant(b
->lookahead
, prefix
, strlen(prefix
));
355 /* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */
357 /* While driving the editor, the target root will remain constant, but
358 we may have to jump around between source roots depending on the
359 state of the working copy. If we were to open a root each time we
360 revisit a rev, we would get no benefit from node-id caching; on the
361 other hand, if we hold open all the roots we ever visit, we'll use
362 an unbounded amount of memory. As a compromise, we maintain a
363 fixed-size LRU cache of source roots. get_source_root retrieves a
364 root from the cache, using POOL to allocate the new root if
365 necessary. Be careful not to hold onto the root for too long,
366 particularly after recursing, since another call to get_source_root
369 get_source_root(report_baton_t
*b
, svn_fs_root_t
**s_root
, svn_revnum_t rev
)
372 svn_fs_root_t
*root
, *prev
= NULL
;
374 /* Look for the desired root in the cache, sliding all the unmatched
375 entries backwards a slot to make room for the right one. */
376 for (i
= 0; i
< NUM_CACHED_SOURCE_ROOTS
; i
++)
378 root
= b
->s_roots
[i
];
379 b
->s_roots
[i
] = prev
;
380 if (root
&& svn_fs_revision_root_revision(root
) == rev
)
385 /* If we didn't find it, throw out the oldest root and open a new one. */
386 if (i
== NUM_CACHED_SOURCE_ROOTS
)
389 svn_fs_close_root(prev
);
390 SVN_ERR(svn_fs_revision_root(&root
, b
->repos
->fs
, rev
, b
->pool
));
393 /* Assign the desired root to the first cache slot and hand it back. */
394 b
->s_roots
[0] = root
;
399 /* Call the directory property-setting function of B->editor to set
400 the property NAME to VALUE on DIR_BATON. */
402 change_dir_prop(report_baton_t
*b
, void *dir_baton
, const char *name
,
403 const svn_string_t
*value
, apr_pool_t
*pool
)
405 return b
->editor
->change_dir_prop(dir_baton
, name
, value
, pool
);
408 /* Call the file property-setting function of B->editor to set the
409 property NAME to VALUE on FILE_BATON. */
411 change_file_prop(report_baton_t
*b
, void *file_baton
, const char *name
,
412 const svn_string_t
*value
, apr_pool_t
*pool
)
414 return b
->editor
->change_file_prop(file_baton
, name
, value
, pool
);
417 /* Generate the appropriate property editing calls to turn the
418 properties of S_REV/S_PATH into those of B->t_root/T_PATH. If
419 S_PATH is NULL, this is an add, so assume the target starts with no
420 properties. Pass OBJECT on to the editor function wrapper
423 delta_proplists(report_baton_t
*b
, svn_revnum_t s_rev
, const char *s_path
,
424 const char *t_path
, const char *lock_token
,
425 proplist_change_fn_t
*change_fn
,
426 void *object
, apr_pool_t
*pool
)
428 svn_fs_root_t
*s_root
;
429 apr_hash_t
*s_props
, *t_props
, *r_props
;
430 apr_array_header_t
*prop_diffs
;
434 svn_string_t
*cr_str
, *cdate
, *last_author
;
435 svn_boolean_t changed
;
436 const svn_prop_t
*pc
;
439 /* Fetch the created-rev and send entry props. */
440 SVN_ERR(svn_fs_node_created_rev(&crev
, b
->t_root
, t_path
, pool
));
441 if (SVN_IS_VALID_REVNUM(crev
))
443 /* Transmit the committed-rev. */
444 cr_str
= svn_string_createf(pool
, "%ld", crev
);
445 SVN_ERR(change_fn(b
, object
,
446 SVN_PROP_ENTRY_COMMITTED_REV
, cr_str
, pool
));
448 SVN_ERR(svn_fs_revision_proplist(&r_props
, b
->repos
->fs
, crev
, pool
));
450 /* Transmit the committed-date. */
451 cdate
= apr_hash_get(r_props
, SVN_PROP_REVISION_DATE
,
452 APR_HASH_KEY_STRING
);
454 SVN_ERR(change_fn(b
, object
, SVN_PROP_ENTRY_COMMITTED_DATE
,
457 /* Transmit the last-author. */
458 last_author
= apr_hash_get(r_props
, SVN_PROP_REVISION_AUTHOR
,
459 APR_HASH_KEY_STRING
);
460 if (last_author
|| s_path
)
461 SVN_ERR(change_fn(b
, object
, SVN_PROP_ENTRY_LAST_AUTHOR
,
464 /* Transmit the UUID. */
465 SVN_ERR(svn_fs_get_uuid(b
->repos
->fs
, &uuid
, pool
));
466 SVN_ERR(change_fn(b
, object
, SVN_PROP_ENTRY_UUID
,
467 svn_string_create(uuid
, pool
), pool
));
470 /* Update lock properties. */
473 SVN_ERR(svn_fs_get_lock(&lock
, b
->repos
->fs
, t_path
, pool
));
475 /* Delete a defunct lock. */
476 if (! lock
|| strcmp(lock_token
, lock
->token
) != 0)
477 SVN_ERR(change_fn(b
, object
, SVN_PROP_ENTRY_LOCK_TOKEN
,
483 SVN_ERR(get_source_root(b
, &s_root
, s_rev
));
485 /* Is this deltification worth our time? */
486 SVN_ERR(svn_fs_props_changed(&changed
, b
->t_root
, t_path
, s_root
,
491 /* If so, go ahead and get the source path's properties. */
492 SVN_ERR(svn_fs_node_proplist(&s_props
, s_root
, s_path
, pool
));
495 s_props
= apr_hash_make(pool
);
497 /* Get the target path's properties */
498 SVN_ERR(svn_fs_node_proplist(&t_props
, b
->t_root
, t_path
, pool
));
500 /* Now transmit the differences. */
501 SVN_ERR(svn_prop_diffs(&prop_diffs
, t_props
, s_props
, pool
));
502 for (i
= 0; i
< prop_diffs
->nelts
; i
++)
504 pc
= &APR_ARRAY_IDX(prop_diffs
, i
, svn_prop_t
);
505 SVN_ERR(change_fn(b
, object
, pc
->name
, pc
->value
, pool
));
511 /* Make the appropriate edits on FILE_BATON to change its contents and
512 properties from those in S_REV/S_PATH to those in B->t_root/T_PATH,
513 possibly using LOCK_TOKEN to determine if the client's lock on the file
516 delta_files(report_baton_t
*b
, void *file_baton
, svn_revnum_t s_rev
,
517 const char *s_path
, const char *t_path
, const char *lock_token
,
520 svn_boolean_t changed
;
521 svn_fs_root_t
*s_root
= NULL
;
522 svn_txdelta_stream_t
*dstream
= NULL
;
523 unsigned char s_digest
[APR_MD5_DIGESTSIZE
];
524 const char *s_hex_digest
= NULL
;
525 svn_txdelta_window_handler_t dhandler
;
528 /* Compare the files' property lists. */
529 SVN_ERR(delta_proplists(b
, s_rev
, s_path
, t_path
, lock_token
,
530 change_file_prop
, file_baton
, pool
));
534 SVN_ERR(get_source_root(b
, &s_root
, s_rev
));
536 /* Is this delta calculation worth our time? If we are ignoring
537 ancestry, then our editor implementor isn't concerned by the
538 theoretical differences between "has contents which have not
539 changed with respect to" and "has the same actual contents
540 as". We'll do everything we can to avoid transmitting even
541 an empty text-delta in that case. */
542 if (b
->ignore_ancestry
)
543 SVN_ERR(svn_repos__compare_files(&changed
, b
->t_root
, t_path
,
544 s_root
, s_path
, pool
));
546 SVN_ERR(svn_fs_contents_changed(&changed
, b
->t_root
, t_path
, s_root
,
551 SVN_ERR(svn_fs_file_md5_checksum(s_digest
, s_root
, s_path
, pool
));
552 s_hex_digest
= svn_md5_digest_to_cstring(s_digest
, pool
);
555 /* Send the delta stream if desired, or just a NULL window if not. */
556 SVN_ERR(b
->editor
->apply_textdelta(file_baton
, s_hex_digest
, pool
,
557 &dhandler
, &dbaton
));
560 SVN_ERR(svn_fs_get_file_delta_stream(&dstream
, s_root
, s_path
,
561 b
->t_root
, t_path
, pool
));
562 return svn_txdelta_send_txstream(dstream
, dhandler
, dbaton
, pool
);
565 return dhandler(NULL
, dbaton
);
568 /* Determine if the user is authorized to view B->t_root/PATH. */
570 check_auth(report_baton_t
*b
, svn_boolean_t
*allowed
, const char *path
,
573 if (b
->authz_read_func
)
574 return b
->authz_read_func(allowed
, b
->t_root
, path
,
575 b
->authz_read_baton
, pool
);
580 /* Create a dirent in *ENTRY for the given ROOT and PATH. We use this to
581 replace the source or target dirent when a report pathinfo tells us to
582 change paths or revisions. */
584 fake_dirent(const svn_fs_dirent_t
**entry
, svn_fs_root_t
*root
,
585 const char *path
, apr_pool_t
*pool
)
587 svn_node_kind_t kind
;
588 svn_fs_dirent_t
*ent
;
590 SVN_ERR(svn_fs_check_path(&kind
, root
, path
, pool
));
591 if (kind
== svn_node_none
)
595 ent
= apr_palloc(pool
, sizeof(**entry
));
596 ent
->name
= svn_path_basename(path
, pool
);
597 SVN_ERR(svn_fs_node_id(&ent
->id
, root
, path
, pool
));
605 /* Given REQUESTED_DEPTH, WC_DEPTH and the current entry's KIND,
606 determine whether we need to send the whole entry, not just deltas.
607 Please refer to delta_dirs' docstring for an explanation of the
608 conditionals below. */
610 is_depth_upgrade(svn_depth_t wc_depth
,
611 svn_depth_t requested_depth
,
612 svn_node_kind_t kind
)
614 if (requested_depth
== svn_depth_unknown
615 || requested_depth
<= wc_depth
616 || wc_depth
== svn_depth_immediates
)
619 if (kind
== svn_node_file
620 && wc_depth
== svn_depth_files
)
623 if (kind
== svn_node_dir
624 && wc_depth
== svn_depth_empty
625 && requested_depth
== svn_depth_files
)
632 /* Call the B->editor's add_file() function to create PATH as a child
633 of PARENT_BATON, returning a new baton in *NEW_FILE_BATON.
634 However, make an attempt to send 'copyfrom' arguments if they're
635 available, by examining the closest copy of the original file
636 O_PATH within B->t_root. If any copyfrom args are discovered,
637 return those in *COPYFROM_PATH and *COPYFROM_REV; otherwise leave
638 those return args untouched. */
640 add_file_smartly(report_baton_t
*b
,
644 void **new_file_baton
,
645 const char **copyfrom_path
,
646 svn_revnum_t
*copyfrom_rev
,
649 /* ### TODO: use a subpool to do this work, clear it at the end? */
650 svn_fs_t
*fs
= svn_repos_fs(b
->repos
);
651 svn_fs_root_t
*closest_copy_root
= NULL
;
652 const char *closest_copy_path
= NULL
;
654 /* Pre-emptively assume no copyfrom args exist. */
655 *copyfrom_path
= NULL
;
656 *copyfrom_rev
= SVN_INVALID_REVNUM
;
658 if (b
->send_copyfrom_args
)
660 /* Find the destination of the nearest 'copy event' which may have
661 caused o_path@t_root to exist. svn_fs_closest_copy only returns paths
662 starting with '/', so make sure o_path always starts with a '/'
665 o_path
= apr_pstrcat(pool
, "/", o_path
, NULL
);
667 SVN_ERR(svn_fs_closest_copy(&closest_copy_root
, &closest_copy_path
,
668 b
->t_root
, o_path
, pool
));
669 if (closest_copy_root
!= NULL
)
671 /* If the destination of the copy event is the same path as
672 o_path, then we've found something interesting that should
673 have 'copyfrom' history. */
674 if (strcmp(closest_copy_path
, o_path
) == 0)
676 SVN_ERR(svn_fs_copied_from(copyfrom_rev
, copyfrom_path
,
677 closest_copy_root
, closest_copy_path
,
679 if (b
->authz_read_func
)
681 svn_boolean_t allowed
;
682 svn_fs_root_t
*copyfrom_root
;
683 SVN_ERR(svn_fs_revision_root(©from_root
, fs
,
684 *copyfrom_rev
, pool
));
685 SVN_ERR(b
->authz_read_func(&allowed
, copyfrom_root
,
686 *copyfrom_path
, b
->authz_read_baton
,
690 *copyfrom_path
= NULL
;
691 *copyfrom_rev
= SVN_INVALID_REVNUM
;
698 SVN_ERR(b
->editor
->add_file(path
, parent_baton
,
699 *copyfrom_path
, *copyfrom_rev
,
700 pool
, new_file_baton
));
705 /* Emit a series of editing operations to transform a source entry to
708 S_REV and S_PATH specify the source entry. S_ENTRY contains the
709 already-looked-up information about the node-revision existing at
710 that location. S_PATH and S_ENTRY may be NULL if the entry does
711 not exist in the source. S_PATH may be non-NULL and S_ENTRY may be
712 NULL if the caller expects INFO to modify the source to an existing
715 B->t_root and T_PATH specify the target entry. T_ENTRY contains
716 the already-looked-up information about the node-revision existing
717 at that location. T_PATH and T_ENTRY may be NULL if the entry does
718 not exist in the target.
720 DIR_BATON and E_PATH contain the parameters which should be passed
721 to the editor calls--DIR_BATON for the parent directory baton and
722 E_PATH for the pathname. (E_PATH is the anchor-relative working
723 copy pathname, which may differ from the source and target
724 pathnames if the report contains a link_path.)
726 INFO contains the report information for this working copy path, or
727 NULL if there is none. This function will internally modify the
728 source and target entries as appropriate based on the report
731 WC_DEPTH and REQUESTED_DEPTH are propagated to delta_dirs() if
732 necessary. Refer to delta_dirs' docstring to find out what
733 should happen for various combinations of WC_DEPTH/REQUESTED_DEPTH. */
735 update_entry(report_baton_t
*b
, svn_revnum_t s_rev
, const char *s_path
,
736 const svn_fs_dirent_t
*s_entry
, const char *t_path
,
737 const svn_fs_dirent_t
*t_entry
, void *dir_baton
,
738 const char *e_path
, path_info_t
*info
, svn_depth_t wc_depth
,
739 svn_depth_t requested_depth
, apr_pool_t
*pool
)
741 svn_fs_root_t
*s_root
;
742 svn_boolean_t allowed
, related
;
744 unsigned char digest
[APR_MD5_DIGESTSIZE
];
745 const char *hex_digest
;
748 /* For non-switch operations, follow link_path in the target. */
749 if (info
&& info
->link_path
&& !b
->is_switch
)
751 t_path
= info
->link_path
;
752 SVN_ERR(fake_dirent(&t_entry
, b
->t_root
, t_path
, pool
));
755 if (info
&& !SVN_IS_VALID_REVNUM(info
->rev
))
757 /* Delete this entry in the source. */
761 else if (info
&& s_path
)
763 /* Follow the rev and possibly path in this entry. */
764 s_path
= (info
->link_path
) ? info
->link_path
: s_path
;
766 SVN_ERR(get_source_root(b
, &s_root
, s_rev
));
767 SVN_ERR(fake_dirent(&s_entry
, s_root
, s_path
, pool
));
770 /* Don't let the report carry us somewhere nonexistent. */
771 if (s_path
&& !s_entry
)
772 return svn_error_createf(SVN_ERR_FS_NOT_FOUND
, NULL
,
773 _("Working copy path '%s' does not exist in "
774 "repository"), e_path
);
776 /* If the source and target both exist and are of the same kind,
777 then find out whether they're related. If they're exactly the
778 same, then we don't have to do anything (unless the report has
779 changes to the source). If we're ignoring ancestry, then any two
780 nodes of the same type are related enough for us. */
782 if (s_entry
&& t_entry
&& s_entry
->kind
== t_entry
->kind
)
784 distance
= svn_fs_compare_ids(s_entry
->id
, t_entry
->id
);
785 if (distance
== 0 && !any_path_info(b
, e_path
)
786 && (!info
|| (!info
->start_empty
&& !info
->lock_token
))
787 && (requested_depth
<= wc_depth
|| t_entry
->kind
== svn_node_file
))
789 else if (distance
!= -1 || b
->ignore_ancestry
)
793 /* If there's a source and it's not related to the target, nuke it. */
794 if (s_entry
&& !related
)
796 svn_revnum_t deleted_rev
;
798 SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b
->t_root
), t_path
,
799 s_rev
, b
->t_rev
, &deleted_rev
,
801 SVN_ERR(b
->editor
->delete_entry(e_path
, deleted_rev
, dir_baton
,
806 /* If there's no target, we have nothing more to do. */
808 return skip_path_info(b
, e_path
);
810 /* Check if the user is authorized to find out about the target. */
811 SVN_ERR(check_auth(b
, &allowed
, t_path
, pool
));
814 if (t_entry
->kind
== svn_node_dir
)
815 SVN_ERR(b
->editor
->absent_directory(e_path
, dir_baton
, pool
));
817 SVN_ERR(b
->editor
->absent_file(e_path
, dir_baton
, pool
));
818 return skip_path_info(b
, e_path
);
821 if (t_entry
->kind
== svn_node_dir
)
824 SVN_ERR(b
->editor
->open_directory(e_path
, dir_baton
, s_rev
, pool
,
827 SVN_ERR(b
->editor
->add_directory(e_path
, dir_baton
, NULL
,
828 SVN_INVALID_REVNUM
, pool
,
831 SVN_ERR(delta_dirs(b
, s_rev
, s_path
, t_path
, new_baton
, e_path
,
832 info
? info
->start_empty
: FALSE
,
833 wc_depth
, requested_depth
, pool
));
834 return b
->editor
->close_directory(new_baton
, pool
);
840 SVN_ERR(b
->editor
->open_file(e_path
, dir_baton
, s_rev
, pool
,
842 SVN_ERR(delta_files(b
, new_baton
, s_rev
, s_path
, t_path
,
843 info
? info
->lock_token
: NULL
, pool
));
847 svn_revnum_t copyfrom_rev
= SVN_INVALID_REVNUM
;
848 const char *copyfrom_path
= NULL
;
849 SVN_ERR(add_file_smartly(b
, e_path
, dir_baton
, t_path
, &new_baton
,
850 ©from_path
, ©from_rev
, pool
));
852 /* Send txdelta between empty file (s_path@s_rev doesn't
853 exist) and added file (t_path@t_root). */
854 SVN_ERR(delta_files(b
, new_baton
, s_rev
, s_path
, t_path
,
855 info
? info
->lock_token
: NULL
, pool
));
857 /* Send txdelta between copied file (copyfrom_path@copyfrom_rev)
858 and added file (tpath@t_root). */
859 SVN_ERR(delta_files(b
, new_baton
, copyfrom_rev
, copyfrom_path
,
860 t_path
, info
? info
->lock_token
: NULL
, pool
));
863 SVN_ERR(svn_fs_file_md5_checksum(digest
, b
->t_root
, t_path
, pool
));
864 hex_digest
= svn_md5_digest_to_cstring(digest
, pool
);
865 return b
->editor
->close_file(new_baton
, hex_digest
, pool
);
869 /* A helper macro for when we have to recurse into subdirectories. */
870 #define DEPTH_BELOW_HERE(depth) ((depth) == svn_depth_immediates) ? \
871 svn_depth_empty : (depth)
873 /* Emit edits within directory DIR_BATON (with corresponding path
874 E_PATH) with the changes from the directory S_REV/S_PATH to the
875 directory B->t_rev/T_PATH. S_PATH may be NULL if the entry does
876 not exist in the source.
878 WC_DEPTH is this path's depth as reported by set_path/link_path.
879 REQUESTED_DEPTH is derived from the depth set by
880 svn_repos_begin_report().
882 When iterating over this directory's entries, the following tables
883 describe what happens for all possible combinations
884 of WC_DEPTH/REQUESTED_DEPTH (rows represent WC_DEPTH, columns
885 represent REQUESTED_DEPTH):
888 X: ignore this entry (it's either below the requested depth, or
889 if the requested depth is svn_depth_unknown, below the working
891 o: handle this entry normally
892 U: handle the entry as if it were a newly added repository path
893 (the client is upgrading to a deeper wc and doesn't currently
894 have this entry, but it should be there after the upgrade, so we
895 need to send the whole thing, not just deltas)
898 ______________________________________________________________
899 | req. depth| unknown | empty | files | immediates | infinity |
900 |wc. depth | | | | | |
901 |___________|_________|_______|_______|____________|__________|
902 |empty | X | X | U | U | U |
903 |___________|_________|_______|_______|____________|__________|
904 |files | o | X | o | o | o |
905 |___________|_________|_______|_______|____________|__________|
906 |immediates | o | X | o | o | o |
907 |___________|_________|_______|_______|____________|__________|
908 |infinity | o | X | o | o | o |
909 |___________|_________|_______|_______|____________|__________|
912 ______________________________________________________________
913 | req. depth| unknown | empty | files | immediates | infinity |
914 |wc. depth | | | | | |
915 |___________|_________|_______|_______|____________|__________|
916 |empty | X | X | X | U | U |
917 |___________|_________|_______|_______|____________|__________|
918 |files | X | X | X | U | U |
919 |___________|_________|_______|_______|____________|__________|
920 |immediates | o | X | X | o | o |
921 |___________|_________|_______|_______|____________|__________|
922 |infinity | o | X | X | o | o |
923 |___________|_________|_______|_______|____________|__________|
925 These rules are enforced by the is_depth_upgrade() function and by
926 various other checks below.
929 delta_dirs(report_baton_t
*b
, svn_revnum_t s_rev
, const char *s_path
,
930 const char *t_path
, void *dir_baton
, const char *e_path
,
931 svn_boolean_t start_empty
, svn_depth_t wc_depth
,
932 svn_depth_t requested_depth
, apr_pool_t
*pool
)
934 svn_fs_root_t
*s_root
;
935 apr_hash_t
*s_entries
= NULL
, *t_entries
;
936 apr_hash_index_t
*hi
;
938 const svn_fs_dirent_t
*s_entry
, *t_entry
;
940 const char *name
, *s_fullpath
, *t_fullpath
, *e_fullpath
;
943 /* Compare the property lists. If we're starting empty, pass a NULL
944 source path so that we add all the properties.
946 When we support directory locks, we must pass the lock token here. */
947 SVN_ERR(delta_proplists(b
, s_rev
, start_empty
? NULL
: s_path
, t_path
,
948 NULL
, change_dir_prop
, dir_baton
, pool
));
950 if (requested_depth
> svn_depth_empty
951 || requested_depth
== svn_depth_unknown
)
953 /* Get the list of entries in each of source and target. */
954 if (s_path
&& !start_empty
)
956 SVN_ERR(get_source_root(b
, &s_root
, s_rev
));
957 SVN_ERR(svn_fs_dir_entries(&s_entries
, s_root
, s_path
, pool
));
959 SVN_ERR(svn_fs_dir_entries(&t_entries
, b
->t_root
, t_path
, pool
));
961 /* Iterate over the report information for this directory. */
962 subpool
= svn_pool_create(pool
);
966 svn_pool_clear(subpool
);
967 SVN_ERR(fetch_path_info(b
, &name
, &info
, e_path
, subpool
));
971 /* Invalid revnum means we should delete, unless this is
972 just an excluded subpath. */
974 && !SVN_IS_VALID_REVNUM(info
->rev
)
975 && info
->depth
!= svn_depth_exclude
)
977 /* We want to perform deletes before non-replacement adds,
978 for graceful handling of case-only renames on
979 case-insensitive client filesystems. So, if the report
980 item is a delete, remove the entry from the source hash,
981 but don't update the entry yet. */
983 apr_hash_set(s_entries
, name
, APR_HASH_KEY_STRING
, NULL
);
987 e_fullpath
= svn_path_join(e_path
, name
, subpool
);
988 t_fullpath
= svn_path_join(t_path
, name
, subpool
);
989 t_entry
= apr_hash_get(t_entries
, name
, APR_HASH_KEY_STRING
);
990 s_fullpath
= s_path
? svn_path_join(s_path
, name
, subpool
) : NULL
;
991 s_entry
= s_entries
?
992 apr_hash_get(s_entries
, name
, APR_HASH_KEY_STRING
) : NULL
;
994 /* The only special cases here are
996 - When requested_depth is files but the reported path is
997 a directory. This is technically a client error, but we
998 handle it anyway, by skipping the entry.
1000 - When the reported depth is svn_depth_exclude.
1002 if ((! info
|| info
->depth
!= svn_depth_exclude
)
1003 && (requested_depth
!= svn_depth_files
1004 || ((! t_entry
|| t_entry
->kind
!= svn_node_dir
)
1005 && (! s_entry
|| s_entry
->kind
!= svn_node_dir
))))
1006 SVN_ERR(update_entry(b
, s_rev
, s_fullpath
, s_entry
, t_fullpath
,
1007 t_entry
, dir_baton
, e_fullpath
, info
,
1009 : DEPTH_BELOW_HERE(wc_depth
),
1010 DEPTH_BELOW_HERE(requested_depth
), subpool
));
1012 /* Don't revisit this name in the target or source entries. */
1013 apr_hash_set(t_entries
, name
, APR_HASH_KEY_STRING
, NULL
);
1015 apr_hash_set(s_entries
, name
, APR_HASH_KEY_STRING
, NULL
);
1017 /* pathinfo entries live in their own subpools due to lookahead,
1018 so we need to clear each one out as we finish with it. */
1020 svn_pool_destroy(info
->pool
);
1023 /* Remove any deleted entries. Do this before processing the
1024 target, for graceful handling of case-only renames. */
1027 for (hi
= apr_hash_first(pool
, s_entries
);
1029 hi
= apr_hash_next(hi
))
1031 svn_pool_clear(subpool
);
1032 apr_hash_this(hi
, NULL
, NULL
, &val
);
1035 if (apr_hash_get(t_entries
, s_entry
->name
,
1036 APR_HASH_KEY_STRING
) == NULL
)
1038 svn_revnum_t deleted_rev
;
1040 if (s_entry
->kind
== svn_node_file
1041 && wc_depth
< svn_depth_files
)
1044 if (s_entry
->kind
== svn_node_dir
1045 && (wc_depth
< svn_depth_immediates
1046 || requested_depth
== svn_depth_files
))
1049 /* There is no corresponding target entry, so delete. */
1050 e_fullpath
= svn_path_join(e_path
, s_entry
->name
, subpool
);
1051 SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b
->t_root
),
1052 svn_path_join(t_path
,
1056 &deleted_rev
, subpool
));
1058 SVN_ERR(b
->editor
->delete_entry(e_fullpath
,
1060 dir_baton
, subpool
));
1065 /* Loop over the dirents in the target. */
1066 for (hi
= apr_hash_first(pool
, t_entries
); hi
; hi
= apr_hash_next(hi
))
1068 svn_pool_clear(subpool
);
1069 apr_hash_this(hi
, NULL
, NULL
, &val
);
1072 if (is_depth_upgrade(wc_depth
, requested_depth
, t_entry
->kind
))
1074 /* We're making the working copy deeper, pretend the source
1081 if (t_entry
->kind
== svn_node_file
1082 && requested_depth
== svn_depth_unknown
1083 && wc_depth
< svn_depth_files
)
1086 if (t_entry
->kind
== svn_node_dir
1087 && (wc_depth
< svn_depth_immediates
1088 || requested_depth
== svn_depth_files
))
1091 /* Look for an entry with the same name
1092 in the source dirents. */
1093 s_entry
= s_entries
?
1094 apr_hash_get(s_entries
, t_entry
->name
, APR_HASH_KEY_STRING
)
1096 s_fullpath
= s_entry
?
1097 svn_path_join(s_path
, t_entry
->name
, subpool
) : NULL
;
1100 /* Compose the report, editor, and target paths for this entry. */
1101 e_fullpath
= svn_path_join(e_path
, t_entry
->name
, subpool
);
1102 t_fullpath
= svn_path_join(t_path
, t_entry
->name
, subpool
);
1104 SVN_ERR(update_entry(b
, s_rev
, s_fullpath
, s_entry
, t_fullpath
,
1105 t_entry
, dir_baton
, e_fullpath
, NULL
,
1106 DEPTH_BELOW_HERE(wc_depth
),
1107 DEPTH_BELOW_HERE(requested_depth
),
1112 /* Destroy iteration subpool. */
1113 svn_pool_destroy(subpool
);
1115 return SVN_NO_ERROR
;
1118 static svn_error_t
*
1119 drive(report_baton_t
*b
, svn_revnum_t s_rev
, path_info_t
*info
,
1122 const char *t_anchor
, *s_fullpath
;
1123 svn_boolean_t allowed
, info_is_set_path
;
1124 svn_fs_root_t
*s_root
;
1125 const svn_fs_dirent_t
*s_entry
, *t_entry
;
1128 /* Compute the target path corresponding to the working copy anchor,
1129 and check its authorization. */
1130 t_anchor
= *b
->s_operand
? svn_path_dirname(b
->t_path
, pool
) : b
->t_path
;
1131 SVN_ERR(check_auth(b
, &allowed
, t_anchor
, pool
));
1133 return svn_error_create
1134 (SVN_ERR_AUTHZ_ROOT_UNREADABLE
, NULL
,
1135 _("Not authorized to open root of edit operation"));
1137 SVN_ERR(b
->editor
->set_target_revision(b
->edit_baton
, b
->t_rev
, pool
));
1139 /* Collect information about the source and target nodes. */
1140 s_fullpath
= svn_path_join(b
->fs_base
, b
->s_operand
, pool
);
1141 SVN_ERR(get_source_root(b
, &s_root
, s_rev
));
1142 SVN_ERR(fake_dirent(&s_entry
, s_root
, s_fullpath
, pool
));
1143 SVN_ERR(fake_dirent(&t_entry
, b
->t_root
, b
->t_path
, pool
));
1145 /* If the operand is a locally added file or directory, it won't
1146 exist in the source, so accept that. */
1147 info_is_set_path
= (SVN_IS_VALID_REVNUM(info
->rev
) && !info
->link_path
);
1148 if (info_is_set_path
&& !s_entry
)
1151 /* Check if the target path exists first. */
1152 if (!*b
->s_operand
&& !(t_entry
))
1153 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX
, NULL
,
1154 _("Target path does not exist"));
1156 /* If the anchor is the operand, the source and target must be dirs.
1157 Check this before opening the root to avoid modifying the wc. */
1158 else if (!*b
->s_operand
&& (!s_entry
|| s_entry
->kind
!= svn_node_dir
1159 || t_entry
->kind
!= svn_node_dir
))
1160 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX
, NULL
,
1161 _("Cannot replace a directory from within"));
1163 SVN_ERR(b
->editor
->open_root(b
->edit_baton
, s_rev
, pool
, &root_baton
));
1165 /* If the anchor is the operand, diff the two directories; otherwise
1166 update the operand within the anchor directory. */
1168 SVN_ERR(delta_dirs(b
, s_rev
, s_fullpath
, b
->t_path
, root_baton
,
1169 "", info
->start_empty
, info
->depth
, b
->requested_depth
,
1172 SVN_ERR(update_entry(b
, s_rev
, s_fullpath
, s_entry
, b
->t_path
,
1173 t_entry
, root_baton
, b
->s_operand
, info
,
1174 info
->depth
, b
->requested_depth
, pool
));
1176 SVN_ERR(b
->editor
->close_directory(root_baton
, pool
));
1177 SVN_ERR(b
->editor
->close_edit(b
->edit_baton
, pool
));
1178 return SVN_NO_ERROR
;
1181 /* Initialize the baton fields for editor-driving, and drive the editor. */
1182 static svn_error_t
*
1183 finish_report(report_baton_t
*b
, apr_pool_t
*pool
)
1187 apr_pool_t
*subpool
;
1191 /* Save our pool to manage the lookahead and fs_root cache with. */
1194 /* Add an end marker and rewind the temporary file. */
1195 SVN_ERR(svn_io_file_write_full(b
->tempfile
, "-", 1, NULL
, pool
));
1197 SVN_ERR(svn_io_file_seek(b
->tempfile
, APR_SET
, &offset
, pool
));
1199 /* Read the first pathinfo from the report and verify that it is a top-level
1201 SVN_ERR(read_path_info(&info
, b
->tempfile
, pool
));
1202 if (!info
|| strcmp(info
->path
, b
->s_operand
) != 0
1203 || info
->link_path
|| !SVN_IS_VALID_REVNUM(info
->rev
))
1204 return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT
, NULL
,
1205 _("Invalid report for top level of working copy"));
1208 /* Initialize the lookahead pathinfo. */
1209 subpool
= svn_pool_create(pool
);
1210 SVN_ERR(read_path_info(&b
->lookahead
, b
->tempfile
, subpool
));
1212 if (b
->lookahead
&& strcmp(b
->lookahead
->path
, b
->s_operand
) == 0)
1214 /* If the operand of the wc operation is switched or deleted,
1215 then info above is just a place-holder, and the only thing we
1216 have to do is pass the revision it contains to open_root.
1217 The next pathinfo actually describes the target. */
1219 return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT
, NULL
,
1220 _("Two top-level reports with no target"));
1221 /* If the client issued a set-path followed by a delete-path, we need
1222 to respect the depth set by the initial set-path. */
1223 if (! SVN_IS_VALID_REVNUM(b
->lookahead
->rev
))
1225 b
->lookahead
->depth
= info
->depth
;
1227 info
= b
->lookahead
;
1228 SVN_ERR(read_path_info(&b
->lookahead
, b
->tempfile
, subpool
));
1231 /* Open the target root and initialize the source root cache. */
1232 SVN_ERR(svn_fs_revision_root(&b
->t_root
, b
->repos
->fs
, b
->t_rev
, pool
));
1233 for (i
= 0; i
< NUM_CACHED_SOURCE_ROOTS
; i
++)
1234 b
->s_roots
[i
] = NULL
;
1236 return drive(b
, s_rev
, info
, pool
);
1239 /* --- COLLECTING THE REPORT INFORMATION --- */
1241 /* Record a report operation into the temporary file. Return an error
1242 if DEPTH is svn_depth_unknown. */
1243 static svn_error_t
*
1244 write_path_info(report_baton_t
*b
, const char *path
, const char *lpath
,
1245 svn_revnum_t rev
, svn_depth_t depth
,
1246 svn_boolean_t start_empty
,
1247 const char *lock_token
, apr_pool_t
*pool
)
1249 const char *lrep
, *rrep
, *drep
, *ltrep
, *rep
;
1251 /* Munge the path to be anchor-relative, so that we can use edit paths
1253 path
= svn_path_join(b
->s_operand
, path
, pool
);
1255 lrep
= lpath
? apr_psprintf(pool
, "+%" APR_SIZE_T_FMT
":%s",
1256 strlen(lpath
), lpath
) : "-";
1257 rrep
= (SVN_IS_VALID_REVNUM(rev
)) ?
1258 apr_psprintf(pool
, "+%ld:", rev
) : "-";
1260 if (depth
== svn_depth_exclude
)
1262 else if (depth
== svn_depth_empty
)
1264 else if (depth
== svn_depth_files
)
1266 else if (depth
== svn_depth_immediates
)
1268 else if (depth
== svn_depth_infinity
)
1271 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
1272 _("Unsupported report depth '%s'"),
1273 svn_depth_to_word(depth
));
1275 ltrep
= lock_token
? apr_psprintf(pool
, "+%" APR_SIZE_T_FMT
":%s",
1276 strlen(lock_token
), lock_token
) : "-";
1277 rep
= apr_psprintf(pool
, "+%" APR_SIZE_T_FMT
":%s%s%s%s%c%s",
1278 strlen(path
), path
, lrep
, rrep
, drep
,
1279 start_empty
? '+' : '-', ltrep
);
1280 return svn_io_file_write_full(b
->tempfile
, rep
, strlen(rep
), NULL
, pool
);
1284 svn_repos_set_path3(void *baton
, const char *path
, svn_revnum_t rev
,
1285 svn_depth_t depth
, svn_boolean_t start_empty
,
1286 const char *lock_token
, apr_pool_t
*pool
)
1288 return write_path_info(baton
, path
, NULL
, rev
, depth
, start_empty
,
1293 svn_repos_set_path2(void *baton
, const char *path
, svn_revnum_t rev
,
1294 svn_boolean_t start_empty
, const char *lock_token
,
1297 return svn_repos_set_path3(baton
, path
, rev
, svn_depth_infinity
,
1298 start_empty
, lock_token
, pool
);
1302 svn_repos_set_path(void *baton
, const char *path
, svn_revnum_t rev
,
1303 svn_boolean_t start_empty
, apr_pool_t
*pool
)
1305 return svn_repos_set_path2(baton
, path
, rev
, start_empty
, NULL
, pool
);
1309 svn_repos_link_path3(void *baton
, const char *path
, const char *link_path
,
1310 svn_revnum_t rev
, svn_depth_t depth
,
1311 svn_boolean_t start_empty
,
1312 const char *lock_token
, apr_pool_t
*pool
)
1314 if (depth
== svn_depth_exclude
)
1315 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
1316 _("Depth 'exclude' not supported for link"));
1318 return write_path_info(baton
, path
, link_path
, rev
, depth
,
1319 start_empty
, lock_token
, pool
);
1323 svn_repos_link_path2(void *baton
, const char *path
, const char *link_path
,
1324 svn_revnum_t rev
, svn_boolean_t start_empty
,
1325 const char *lock_token
, apr_pool_t
*pool
)
1327 return svn_repos_link_path3(baton
, path
, link_path
, rev
, svn_depth_infinity
,
1328 start_empty
, lock_token
, pool
);
1332 svn_repos_link_path(void *baton
, const char *path
, const char *link_path
,
1333 svn_revnum_t rev
, svn_boolean_t start_empty
,
1336 return svn_repos_link_path2(baton
, path
, link_path
, rev
, start_empty
,
1341 svn_repos_delete_path(void *baton
, const char *path
, apr_pool_t
*pool
)
1343 /* We pass svn_depth_infinity because deletion of a path always
1344 deletes everything underneath it. */
1345 return write_path_info(baton
, path
, NULL
, SVN_INVALID_REVNUM
,
1346 svn_depth_infinity
, FALSE
, NULL
, pool
);
1350 svn_repos_finish_report(void *baton
, apr_pool_t
*pool
)
1352 report_baton_t
*b
= baton
;
1353 svn_error_t
*finish_err
, *close_err
;
1355 finish_err
= finish_report(b
, pool
);
1356 close_err
= svn_io_file_close(b
->tempfile
, pool
);
1358 svn_error_clear(close_err
);
1359 return finish_err
? finish_err
: close_err
;
1363 svn_repos_abort_report(void *baton
, apr_pool_t
*pool
)
1365 report_baton_t
*b
= baton
;
1367 return svn_io_file_close(b
->tempfile
, pool
);
1370 /* --- BEGINNING THE REPORT --- */
1374 svn_repos_begin_report2(void **report_baton
,
1375 svn_revnum_t revnum
,
1377 const char *fs_base
,
1378 const char *s_operand
,
1379 const char *switch_path
,
1380 svn_boolean_t text_deltas
,
1382 svn_boolean_t ignore_ancestry
,
1383 svn_boolean_t send_copyfrom_args
,
1384 const svn_delta_editor_t
*editor
,
1386 svn_repos_authz_func_t authz_read_func
,
1387 void *authz_read_baton
,
1391 const char *tempdir
;
1393 if (depth
== svn_depth_exclude
)
1394 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
1395 _("Request depth 'exclude' not supported"));
1397 /* Build a reporter baton. Copy strings in case the caller doesn't
1398 keep track of them. */
1399 b
= apr_palloc(pool
, sizeof(*b
));
1401 b
->fs_base
= apr_pstrdup(pool
, fs_base
);
1402 b
->s_operand
= apr_pstrdup(pool
, s_operand
);
1404 b
->t_path
= switch_path
? switch_path
1405 : svn_path_join(fs_base
, s_operand
, pool
);
1406 b
->text_deltas
= text_deltas
;
1407 b
->requested_depth
= depth
;
1408 b
->ignore_ancestry
= ignore_ancestry
;
1409 b
->send_copyfrom_args
= send_copyfrom_args
;
1410 b
->is_switch
= (switch_path
!= NULL
);
1412 b
->edit_baton
= edit_baton
;
1413 b
->authz_read_func
= authz_read_func
;
1414 b
->authz_read_baton
= authz_read_baton
;
1416 SVN_ERR(svn_io_temp_dir(&tempdir
, pool
));
1417 SVN_ERR(svn_io_open_unique_file2(&b
->tempfile
, NULL
,
1418 apr_psprintf(pool
, "%s/report", tempdir
),
1419 ".tmp", svn_io_file_del_on_close
, pool
));
1421 /* Hand reporter back to client. */
1423 return SVN_NO_ERROR
;
1428 svn_repos_begin_report(void **report_baton
,
1429 svn_revnum_t revnum
,
1430 const char *username
,
1432 const char *fs_base
,
1433 const char *s_operand
,
1434 const char *switch_path
,
1435 svn_boolean_t text_deltas
,
1436 svn_boolean_t recurse
,
1437 svn_boolean_t ignore_ancestry
,
1438 const svn_delta_editor_t
*editor
,
1440 svn_repos_authz_func_t authz_read_func
,
1441 void *authz_read_baton
,
1444 return svn_repos_begin_report2(report_baton
,
1451 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1453 FALSE
, /* don't send copyfrom args */