diff options
Diffstat (limited to 'subversion/libsvn_repos/reporter.c')
-rw-r--r-- | subversion/libsvn_repos/reporter.c | 1610 |
1 files changed, 1610 insertions, 0 deletions
diff --git a/subversion/libsvn_repos/reporter.c b/subversion/libsvn_repos/reporter.c new file mode 100644 index 0000000000000..a9d1eff5fa389 --- /dev/null +++ b/subversion/libsvn_repos/reporter.c @@ -0,0 +1,1610 @@ +/* + * reporter.c : `reporter' vtable routines for updates. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_types.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_fs.h" +#include "svn_repos.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "repos.h" +#include "svn_private_config.h" + +#include "private/svn_dep_compat.h" +#include "private/svn_fspath.h" +#include "private/svn_subr_private.h" +#include "private/svn_string_private.h" + +#define NUM_CACHED_SOURCE_ROOTS 4 + +/* Theory of operation: we write report operations out to a spill-buffer + as we receive them. When the report is finished, we read the + operations back out again, using them to guide the progression of + the delta between the source and target revs. + + Spill-buffer content format: we use a simple ad-hoc format to store the + report operations. Each report operation is the concatention of + the following ("+/-" indicates the single character '+' or '-'; + <length> and <revnum> are written out as decimal strings): + + +/- '-' marks the end of the report + If previous is +: + <length>:<bytes> Length-counted path string + +/- '+' indicates the presence of link_path + If previous is +: + <length>:<bytes> Length-counted link_path string + +/- '+' indicates presence of revnum + If previous is +: + <revnum>: Revnum of set_path or link_path + +/- '+' indicates depth other than svn_depth_infinity + If previous is +: + <depth>: "X","E","F","M" => + svn_depth_{exclude,empty,files,immediates} + +/- '+' indicates start_empty field set + +/- '+' indicates presence of lock_token field. + If previous is +: + <length>:<bytes> Length-counted lock_token string + + Terminology: for brevity, this file frequently uses the prefixes + "s_" for source, "t_" for target, and "e_" for editor. Also, to + avoid overloading the word "target", we talk about the source + "anchor and operand", rather than the usual "anchor and target". */ + +/* Describes the state of a working copy subtree, as given by a + report. Because we keep a lookahead pathinfo, we need to allocate + each one of these things in a subpool of the report baton and free + it when done. */ +typedef struct path_info_t +{ + const char *path; /* path, munged to be anchor-relative */ + const char *link_path; /* NULL for set_path or delete_path */ + svn_revnum_t rev; /* SVN_INVALID_REVNUM for delete_path */ + svn_depth_t depth; /* Depth of this path, meaningless for files */ + svn_boolean_t start_empty; /* Meaningless for delete_path */ + const char *lock_token; /* NULL if no token */ + apr_pool_t *pool; /* Container pool */ +} path_info_t; + +/* Describes the standard revision properties that are relevant for + reports. Since a particular revision will often show up more than + once in the report, we cache these properties for the time of the + report generation. */ +typedef struct revision_info_t +{ + svn_revnum_t rev; /* revision number */ + svn_string_t* date; /* revision timestamp */ + svn_string_t* author; /* name of the revisions' author */ +} revision_info_t; + +/* A structure used by the routines within the `reporter' vtable, + driven by the client as it describes its working copy revisions. */ +typedef struct report_baton_t +{ + /* Parameters remembered from svn_repos_begin_report3 */ + svn_repos_t *repos; + const char *fs_base; /* fspath corresponding to wc anchor */ + const char *s_operand; /* anchor-relative wc target (may be empty) */ + svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */ + const char *t_path; /* FS path the edit will bring the wc to */ + svn_boolean_t text_deltas; /* Whether to report text deltas */ + apr_size_t zero_copy_limit; /* Max item size that will be sent using + the zero-copy code path. */ + + /* If the client requested a specific depth, record it here; if the + client did not, then this is svn_depth_unknown, and the depth of + information transmitted from server to client will be governed + strictly by the path-associated depths recorded in the report. */ + svn_depth_t requested_depth; + + svn_boolean_t ignore_ancestry; + svn_boolean_t send_copyfrom_args; + svn_boolean_t is_switch; + const svn_delta_editor_t *editor; + void *edit_baton; + svn_repos_authz_func_t authz_read_func; + void *authz_read_baton; + + /* The spill-buffer holding the report. */ + svn_spillbuf_reader_t *reader; + + /* For the actual editor drive, we'll need a lookahead path info + entry, a cache of FS roots, and a pool to store them. */ + path_info_t *lookahead; + svn_fs_root_t *t_root; + svn_fs_root_t *s_roots[NUM_CACHED_SOURCE_ROOTS]; + + /* Cache for revision properties. This is used to eliminate redundant + revprop fetching. */ + apr_hash_t *revision_infos; + + /* This will not change. So, fetch it once and reuse it. */ + svn_string_t *repos_uuid; + apr_pool_t *pool; +} report_baton_t; + +/* The type of a function that accepts changes to an object's property + list. OBJECT is the object whose properties are being changed. + NAME is the name of the property to change. VALUE is the new value + for the property, or zero if the property should be deleted. */ +typedef svn_error_t *proplist_change_fn_t(report_baton_t *b, void *object, + const char *name, + const svn_string_t *value, + apr_pool_t *pool); + +static svn_error_t *delta_dirs(report_baton_t *b, svn_revnum_t s_rev, + const char *s_path, const char *t_path, + void *dir_baton, const char *e_path, + svn_boolean_t start_empty, + svn_depth_t wc_depth, + svn_depth_t requested_depth, + apr_pool_t *pool); + +/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */ + +static svn_error_t * +read_number(apr_uint64_t *num, svn_spillbuf_reader_t *reader, apr_pool_t *pool) +{ + char c; + + *num = 0; + while (1) + { + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + if (c == ':') + break; + *num = *num * 10 + (c - '0'); + } + return SVN_NO_ERROR; +} + +static svn_error_t * +read_string(const char **str, svn_spillbuf_reader_t *reader, apr_pool_t *pool) +{ + apr_uint64_t len; + apr_size_t size; + apr_size_t amt; + char *buf; + + SVN_ERR(read_number(&len, reader, pool)); + + /* Len can never be less than zero. But could len be so large that + len + 1 wraps around and we end up passing 0 to apr_palloc(), + thus getting a pointer to no storage? Probably not (16 exabyte + string, anyone?) but let's be future-proof anyway. */ + if (len + 1 < len || len + 1 > APR_SIZE_MAX) + { + /* xgettext doesn't expand preprocessor definitions, so we must + pass translatable string to apr_psprintf() function to create + intermediate string with appropriate format specifier. */ + return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, + apr_psprintf(pool, + _("Invalid length (%%%s) when " + "about to read a string"), + APR_UINT64_T_FMT), + len); + } + + size = (apr_size_t)len; + buf = apr_palloc(pool, size+1); + if (size > 0) + { + SVN_ERR(svn_spillbuf__reader_read(&amt, reader, buf, size, pool)); + SVN_ERR_ASSERT(amt == size); + } + buf[len] = 0; + *str = buf; + return SVN_NO_ERROR; +} + +static svn_error_t * +read_rev(svn_revnum_t *rev, svn_spillbuf_reader_t *reader, apr_pool_t *pool) +{ + char c; + apr_uint64_t num; + + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + if (c == '+') + { + SVN_ERR(read_number(&num, reader, pool)); + *rev = (svn_revnum_t) num; + } + else + *rev = SVN_INVALID_REVNUM; + return SVN_NO_ERROR; +} + +/* Read a single character to set *DEPTH (having already read '+') + from READER. PATH is the path to which the depth applies, and is + used for error reporting only. */ +static svn_error_t * +read_depth(svn_depth_t *depth, svn_spillbuf_reader_t *reader, const char *path, + apr_pool_t *pool) +{ + char c; + + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + switch (c) + { + case 'X': + *depth = svn_depth_exclude; + break; + case 'E': + *depth = svn_depth_empty; + break; + case 'F': + *depth = svn_depth_files; + break; + case 'M': + *depth = svn_depth_immediates; + break; + + /* Note that we do not tolerate explicit representation of + svn_depth_infinity here, because that's not how + write_path_info() writes it. */ + default: + return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, + _("Invalid depth (%c) for path '%s'"), c, path); + } + + return SVN_NO_ERROR; +} + +/* Read a report operation *PI out of READER. Set *PI to NULL if we + have reached the end of the report. */ +static svn_error_t * +read_path_info(path_info_t **pi, + svn_spillbuf_reader_t *reader, + apr_pool_t *pool) +{ + char c; + + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + if (c == '-') + { + *pi = NULL; + return SVN_NO_ERROR; + } + + *pi = apr_palloc(pool, sizeof(**pi)); + SVN_ERR(read_string(&(*pi)->path, reader, pool)); + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + if (c == '+') + SVN_ERR(read_string(&(*pi)->link_path, reader, pool)); + else + (*pi)->link_path = NULL; + SVN_ERR(read_rev(&(*pi)->rev, reader, pool)); + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + if (c == '+') + SVN_ERR(read_depth(&((*pi)->depth), reader, (*pi)->path, pool)); + else + (*pi)->depth = svn_depth_infinity; + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + (*pi)->start_empty = (c == '+'); + SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); + if (c == '+') + SVN_ERR(read_string(&(*pi)->lock_token, reader, pool)); + else + (*pi)->lock_token = NULL; + (*pi)->pool = pool; + return SVN_NO_ERROR; +} + +/* Return true if PI's path is a child of PREFIX (which has length PLEN). */ +static svn_boolean_t +relevant(path_info_t *pi, const char *prefix, apr_size_t plen) +{ + return (pi && strncmp(pi->path, prefix, plen) == 0 && + (!*prefix || pi->path[plen] == '/')); +} + +/* Fetch the next pathinfo from B->reader for a descendant of + PREFIX. If the next pathinfo is for an immediate child of PREFIX, + set *ENTRY to the path component of the report information and + *INFO to the path information for that entry. If the next pathinfo + is for a grandchild or other more remote descendant of PREFIX, set + *ENTRY to the immediate child corresponding to that descendant and + set *INFO to NULL. If the next pathinfo is not for a descendant of + PREFIX, or if we reach the end of the report, set both *ENTRY and + *INFO to NULL. + + At all times, B->lookahead is presumed to be the next pathinfo not + yet returned as an immediate child, or NULL if we have reached the + end of the report. Because we use a lookahead element, we can't + rely on the usual nested pool lifetimes, so allocate each pathinfo + in a subpool of the report baton's pool. The caller should delete + (*INFO)->pool when it is done with the information. */ +static svn_error_t * +fetch_path_info(report_baton_t *b, const char **entry, path_info_t **info, + const char *prefix, apr_pool_t *pool) +{ + apr_size_t plen = strlen(prefix); + const char *relpath, *sep; + apr_pool_t *subpool; + + if (!relevant(b->lookahead, prefix, plen)) + { + /* No more entries relevant to prefix. */ + *entry = NULL; + *info = NULL; + } + else + { + /* Take a look at the prefix-relative part of the path. */ + relpath = b->lookahead->path + (*prefix ? plen + 1 : 0); + sep = strchr(relpath, '/'); + if (sep) + { + /* Return the immediate child part; do not advance. */ + *entry = apr_pstrmemdup(pool, relpath, sep - relpath); + *info = NULL; + } + else + { + /* This is an immediate child; return it and advance. */ + *entry = relpath; + *info = b->lookahead; + subpool = svn_pool_create(b->pool); + SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); + } + } + return SVN_NO_ERROR; +} + +/* Skip all path info entries relevant to *PREFIX. Call this when the + editor drive skips a directory. */ +static svn_error_t * +skip_path_info(report_baton_t *b, const char *prefix) +{ + apr_size_t plen = strlen(prefix); + apr_pool_t *subpool; + + while (relevant(b->lookahead, prefix, plen)) + { + svn_pool_destroy(b->lookahead->pool); + subpool = svn_pool_create(b->pool); + SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); + } + return SVN_NO_ERROR; +} + +/* Return true if there is at least one path info entry relevant to *PREFIX. */ +static svn_boolean_t +any_path_info(report_baton_t *b, const char *prefix) +{ + return relevant(b->lookahead, prefix, strlen(prefix)); +} + +/* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */ + +/* While driving the editor, the target root will remain constant, but + we may have to jump around between source roots depending on the + state of the working copy. If we were to open a root each time we + revisit a rev, we would get no benefit from node-id caching; on the + other hand, if we hold open all the roots we ever visit, we'll use + an unbounded amount of memory. As a compromise, we maintain a + fixed-size LRU cache of source roots. get_source_root retrieves a + root from the cache, using POOL to allocate the new root if + necessary. Be careful not to hold onto the root for too long, + particularly after recursing, since another call to get_source_root + can close it. */ +static svn_error_t * +get_source_root(report_baton_t *b, svn_fs_root_t **s_root, svn_revnum_t rev) +{ + int i; + svn_fs_root_t *root, *prev = NULL; + + /* Look for the desired root in the cache, sliding all the unmatched + entries backwards a slot to make room for the right one. */ + for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++) + { + root = b->s_roots[i]; + b->s_roots[i] = prev; + if (root && svn_fs_revision_root_revision(root) == rev) + break; + prev = root; + } + + /* If we didn't find it, throw out the oldest root and open a new one. */ + if (i == NUM_CACHED_SOURCE_ROOTS) + { + if (prev) + svn_fs_close_root(prev); + SVN_ERR(svn_fs_revision_root(&root, b->repos->fs, rev, b->pool)); + } + + /* Assign the desired root to the first cache slot and hand it back. */ + b->s_roots[0] = root; + *s_root = root; + return SVN_NO_ERROR; +} + +/* Call the directory property-setting function of B->editor to set + the property NAME to VALUE on DIR_BATON. */ +static svn_error_t * +change_dir_prop(report_baton_t *b, void *dir_baton, const char *name, + const svn_string_t *value, apr_pool_t *pool) +{ + return svn_error_trace(b->editor->change_dir_prop(dir_baton, name, value, + pool)); +} + +/* Call the file property-setting function of B->editor to set the + property NAME to VALUE on FILE_BATON. */ +static svn_error_t * +change_file_prop(report_baton_t *b, void *file_baton, const char *name, + const svn_string_t *value, apr_pool_t *pool) +{ + return svn_error_trace(b->editor->change_file_prop(file_baton, name, value, + pool)); +} + +/* For the report B, return the relevant revprop data of revision REV in + REVISION_INFO. The revision info will be allocated in b->pool. + Temporaries get allocated on SCRATCH_POOL. */ +static svn_error_t * +get_revision_info(report_baton_t *b, + svn_revnum_t rev, + revision_info_t** revision_info, + apr_pool_t *scratch_pool) +{ + apr_hash_t *r_props; + svn_string_t *cdate, *author; + revision_info_t* info; + + /* Try to find the info in the report's cache */ + info = apr_hash_get(b->revision_infos, &rev, sizeof(rev)); + if (!info) + { + /* Info is not available, yet. + Get all revprops. */ + SVN_ERR(svn_fs_revision_proplist(&r_props, + b->repos->fs, + rev, + scratch_pool)); + + /* Extract the committed-date. */ + cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE); + + /* Extract the last-author. */ + author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR); + + /* Create a result object */ + info = apr_palloc(b->pool, sizeof(*info)); + info->rev = rev; + info->date = cdate ? svn_string_dup(cdate, b->pool) : NULL; + info->author = author ? svn_string_dup(author, b->pool) : NULL; + + /* Cache it */ + apr_hash_set(b->revision_infos, &info->rev, sizeof(rev), info); + } + + *revision_info = info; + return SVN_NO_ERROR; +} + + +/* Generate the appropriate property editing calls to turn the + properties of S_REV/S_PATH into those of B->t_root/T_PATH. If + S_PATH is NULL, this is an add, so assume the target starts with no + properties. Pass OBJECT on to the editor function wrapper + CHANGE_FN. */ +static svn_error_t * +delta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path, + const char *t_path, const char *lock_token, + proplist_change_fn_t *change_fn, + void *object, apr_pool_t *pool) +{ + svn_fs_root_t *s_root; + apr_hash_t *s_props = NULL, *t_props; + apr_array_header_t *prop_diffs; + int i; + svn_revnum_t crev; + revision_info_t *revision_info; + svn_boolean_t changed; + const svn_prop_t *pc; + svn_lock_t *lock; + apr_hash_index_t *hi; + + /* Fetch the created-rev and send entry props. */ + SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool)); + if (SVN_IS_VALID_REVNUM(crev)) + { + /* convert committed-rev to string */ + char buf[SVN_INT64_BUFFER_SIZE]; + svn_string_t cr_str; + cr_str.data = buf; + cr_str.len = svn__i64toa(buf, crev); + + /* Transmit the committed-rev. */ + SVN_ERR(change_fn(b, object, + SVN_PROP_ENTRY_COMMITTED_REV, &cr_str, pool)); + + SVN_ERR(get_revision_info(b, crev, &revision_info, pool)); + + /* Transmit the committed-date. */ + if (revision_info->date || s_path) + SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_COMMITTED_DATE, + revision_info->date, pool)); + + /* Transmit the last-author. */ + if (revision_info->author || s_path) + SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LAST_AUTHOR, + revision_info->author, pool)); + + /* Transmit the UUID. */ + SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_UUID, + b->repos_uuid, pool)); + } + + /* Update lock properties. */ + if (lock_token) + { + SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool)); + + /* Delete a defunct lock. */ + if (! lock || strcmp(lock_token, lock->token) != 0) + SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LOCK_TOKEN, + NULL, pool)); + } + + if (s_path) + { + SVN_ERR(get_source_root(b, &s_root, s_rev)); + + /* Is this deltification worth our time? */ + SVN_ERR(svn_fs_props_changed(&changed, b->t_root, t_path, s_root, + s_path, pool)); + if (! changed) + return SVN_NO_ERROR; + + /* If so, go ahead and get the source path's properties. */ + SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool)); + } + + /* Get the target path's properties */ + SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool)); + + if (s_props && apr_hash_count(s_props)) + { + /* Now transmit the differences. */ + SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool)); + for (i = 0; i < prop_diffs->nelts; i++) + { + pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); + SVN_ERR(change_fn(b, object, pc->name, pc->value, pool)); + } + } + else if (apr_hash_count(t_props)) + { + /* So source, i.e. all new. Transmit all target props. */ + for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + + apr_hash_this(hi, &key, NULL, &val); + SVN_ERR(change_fn(b, object, key, val, pool)); + } + } + + return SVN_NO_ERROR; +} + +/* Baton type to be passed into send_zero_copy_delta. + */ +typedef struct zero_copy_baton_t +{ + /* don't process data larger than this limit */ + apr_size_t zero_copy_limit; + + /* window handler and baton to send the data to */ + svn_txdelta_window_handler_t dhandler; + void *dbaton; + + /* return value: will be set to TRUE, if the data was processed. */ + svn_boolean_t zero_copy_succeeded; +} zero_copy_baton_t; + +/* Implement svn_fs_process_contents_func_t. If LEN is smaller than the + * limit given in *BATON, send the CONTENTS as an delta windows to the + * handler given in BATON and set the ZERO_COPY_SUCCEEDED flag in that + * BATON. Otherwise, reset it to FALSE. + * Use POOL for temporary allocations. + */ +static svn_error_t * +send_zero_copy_delta(const unsigned char *contents, + apr_size_t len, + void *baton, + apr_pool_t *pool) +{ + zero_copy_baton_t *zero_copy_baton = baton; + + /* if the item is too large, the caller must revert to traditional + streaming code. */ + if (len > zero_copy_baton->zero_copy_limit) + { + zero_copy_baton->zero_copy_succeeded = FALSE; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_txdelta_send_contents(contents, len, + zero_copy_baton->dhandler, + zero_copy_baton->dbaton, pool)); + + /* all fine now */ + zero_copy_baton->zero_copy_succeeded = TRUE; + return SVN_NO_ERROR; +} + + +/* Make the appropriate edits on FILE_BATON to change its contents and + properties from those in S_REV/S_PATH to those in B->t_root/T_PATH, + possibly using LOCK_TOKEN to determine if the client's lock on the file + is defunct. */ +static svn_error_t * +delta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev, + const char *s_path, const char *t_path, const char *lock_token, + apr_pool_t *pool) +{ + svn_boolean_t changed; + svn_fs_root_t *s_root = NULL; + svn_txdelta_stream_t *dstream = NULL; + svn_checksum_t *s_checksum; + const char *s_hex_digest = NULL; + svn_txdelta_window_handler_t dhandler; + void *dbaton; + + /* Compare the files' property lists. */ + SVN_ERR(delta_proplists(b, s_rev, s_path, t_path, lock_token, + change_file_prop, file_baton, pool)); + + if (s_path) + { + SVN_ERR(get_source_root(b, &s_root, s_rev)); + + /* We're not interested in the theoretical difference between "has + contents which have not changed with respect to" and "has the same + actual contents as" when sending text-deltas. If we know the + delta is an empty one, we avoiding sending it in either case. */ + SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path, + s_root, s_path, pool)); + + if (!changed) + return SVN_NO_ERROR; + + SVN_ERR(svn_fs_file_checksum(&s_checksum, svn_checksum_md5, s_root, + s_path, TRUE, pool)); + s_hex_digest = svn_checksum_to_cstring(s_checksum, pool); + } + + /* Send the delta stream if desired, or just a NULL window if not. */ + SVN_ERR(b->editor->apply_textdelta(file_baton, s_hex_digest, pool, + &dhandler, &dbaton)); + + if (dhandler != svn_delta_noop_window_handler) + { + if (b->text_deltas) + { + /* if we send deltas against empty streams, we may use our + zero-copy code. */ + if (b->zero_copy_limit > 0 && s_path == NULL) + { + zero_copy_baton_t baton; + svn_boolean_t called = FALSE; + + baton.zero_copy_limit = b->zero_copy_limit; + baton.dhandler = dhandler; + baton.dbaton = dbaton; + baton.zero_copy_succeeded = FALSE; + SVN_ERR(svn_fs_try_process_file_contents(&called, + b->t_root, t_path, + send_zero_copy_delta, + &baton, pool)); + + /* data has been available and small enough, + i.e. been processed? */ + if (called && baton.zero_copy_succeeded) + return SVN_NO_ERROR; + } + + SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path, + b->t_root, t_path, pool)); + SVN_ERR(svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool)); + } + else + SVN_ERR(dhandler(NULL, dbaton)); + } + + return SVN_NO_ERROR; +} + +/* Determine if the user is authorized to view B->t_root/PATH. */ +static svn_error_t * +check_auth(report_baton_t *b, svn_boolean_t *allowed, const char *path, + apr_pool_t *pool) +{ + if (b->authz_read_func) + return svn_error_trace(b->authz_read_func(allowed, b->t_root, path, + b->authz_read_baton, pool)); + *allowed = TRUE; + return SVN_NO_ERROR; +} + +/* Create a dirent in *ENTRY for the given ROOT and PATH. We use this to + replace the source or target dirent when a report pathinfo tells us to + change paths or revisions. */ +static svn_error_t * +fake_dirent(const svn_fs_dirent_t **entry, svn_fs_root_t *root, + const char *path, apr_pool_t *pool) +{ + svn_node_kind_t kind; + svn_fs_dirent_t *ent; + + SVN_ERR(svn_fs_check_path(&kind, root, path, pool)); + if (kind == svn_node_none) + *entry = NULL; + else + { + ent = apr_palloc(pool, sizeof(**entry)); + /* ### All callers should be updated to pass just one of these + formats */ + ent->name = (*path == '/') ? svn_fspath__basename(path, pool) + : svn_relpath_basename(path, pool); + SVN_ERR(svn_fs_node_id(&ent->id, root, path, pool)); + ent->kind = kind; + *entry = ent; + } + return SVN_NO_ERROR; +} + + +/* Given REQUESTED_DEPTH, WC_DEPTH and the current entry's KIND, + determine whether we need to send the whole entry, not just deltas. + Please refer to delta_dirs' docstring for an explanation of the + conditionals below. */ +static svn_boolean_t +is_depth_upgrade(svn_depth_t wc_depth, + svn_depth_t requested_depth, + svn_node_kind_t kind) +{ + if (requested_depth == svn_depth_unknown + || requested_depth <= wc_depth + || wc_depth == svn_depth_immediates) + return FALSE; + + if (kind == svn_node_file + && wc_depth == svn_depth_files) + return FALSE; + + if (kind == svn_node_dir + && wc_depth == svn_depth_empty + && requested_depth == svn_depth_files) + return FALSE; + + return TRUE; +} + + +/* Call the B->editor's add_file() function to create PATH as a child + of PARENT_BATON, returning a new baton in *NEW_FILE_BATON. + However, make an attempt to send 'copyfrom' arguments if they're + available, by examining the closest copy of the original file + O_PATH within B->t_root. If any copyfrom args are discovered, + return those in *COPYFROM_PATH and *COPYFROM_REV; otherwise leave + those return args untouched. */ +static svn_error_t * +add_file_smartly(report_baton_t *b, + const char *path, + void *parent_baton, + const char *o_path, + void **new_file_baton, + const char **copyfrom_path, + svn_revnum_t *copyfrom_rev, + apr_pool_t *pool) +{ + /* ### TODO: use a subpool to do this work, clear it at the end? */ + svn_fs_t *fs = svn_repos_fs(b->repos); + svn_fs_root_t *closest_copy_root = NULL; + const char *closest_copy_path = NULL; + + /* Pre-emptively assume no copyfrom args exist. */ + *copyfrom_path = NULL; + *copyfrom_rev = SVN_INVALID_REVNUM; + + if (b->send_copyfrom_args) + { + /* Find the destination of the nearest 'copy event' which may have + caused o_path@t_root to exist. svn_fs_closest_copy only returns paths + starting with '/', so make sure o_path always starts with a '/' + too. */ + if (*o_path != '/') + o_path = apr_pstrcat(pool, "/", o_path, (char *)NULL); + + SVN_ERR(svn_fs_closest_copy(&closest_copy_root, &closest_copy_path, + b->t_root, o_path, pool)); + if (closest_copy_root != NULL) + { + /* If the destination of the copy event is the same path as + o_path, then we've found something interesting that should + have 'copyfrom' history. */ + if (strcmp(closest_copy_path, o_path) == 0) + { + SVN_ERR(svn_fs_copied_from(copyfrom_rev, copyfrom_path, + closest_copy_root, closest_copy_path, + pool)); + if (b->authz_read_func) + { + svn_boolean_t allowed; + svn_fs_root_t *copyfrom_root; + SVN_ERR(svn_fs_revision_root(©from_root, fs, + *copyfrom_rev, pool)); + SVN_ERR(b->authz_read_func(&allowed, copyfrom_root, + *copyfrom_path, b->authz_read_baton, + pool)); + if (! allowed) + { + *copyfrom_path = NULL; + *copyfrom_rev = SVN_INVALID_REVNUM; + } + } + } + } + } + + return svn_error_trace(b->editor->add_file(path, parent_baton, + *copyfrom_path, *copyfrom_rev, + pool, new_file_baton)); +} + + +/* Emit a series of editing operations to transform a source entry to + a target entry. + + S_REV and S_PATH specify the source entry. S_ENTRY contains the + already-looked-up information about the node-revision existing at + that location. S_PATH and S_ENTRY may be NULL if the entry does + not exist in the source. S_PATH may be non-NULL and S_ENTRY may be + NULL if the caller expects INFO to modify the source to an existing + location. + + B->t_root and T_PATH specify the target entry. T_ENTRY contains + the already-looked-up information about the node-revision existing + at that location. T_PATH and T_ENTRY may be NULL if the entry does + not exist in the target. + + DIR_BATON and E_PATH contain the parameters which should be passed + to the editor calls--DIR_BATON for the parent directory baton and + E_PATH for the pathname. (E_PATH is the anchor-relative working + copy pathname, which may differ from the source and target + pathnames if the report contains a link_path.) + + INFO contains the report information for this working copy path, or + NULL if there is none. This function will internally modify the + source and target entries as appropriate based on the report + information. + + WC_DEPTH and REQUESTED_DEPTH are propagated to delta_dirs() if + necessary. Refer to delta_dirs' docstring to find out what + should happen for various combinations of WC_DEPTH/REQUESTED_DEPTH. */ +static svn_error_t * +update_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path, + const svn_fs_dirent_t *s_entry, const char *t_path, + const svn_fs_dirent_t *t_entry, void *dir_baton, + const char *e_path, path_info_t *info, svn_depth_t wc_depth, + svn_depth_t requested_depth, apr_pool_t *pool) +{ + svn_fs_root_t *s_root; + svn_boolean_t allowed, related; + void *new_baton; + svn_checksum_t *checksum; + const char *hex_digest; + + /* For non-switch operations, follow link_path in the target. */ + if (info && info->link_path && !b->is_switch) + { + t_path = info->link_path; + SVN_ERR(fake_dirent(&t_entry, b->t_root, t_path, pool)); + } + + if (info && !SVN_IS_VALID_REVNUM(info->rev)) + { + /* Delete this entry in the source. */ + s_path = NULL; + s_entry = NULL; + } + else if (info && s_path) + { + /* Follow the rev and possibly path in this entry. */ + s_path = (info->link_path) ? info->link_path : s_path; + s_rev = info->rev; + SVN_ERR(get_source_root(b, &s_root, s_rev)); + SVN_ERR(fake_dirent(&s_entry, s_root, s_path, pool)); + } + + /* Don't let the report carry us somewhere nonexistent. */ + if (s_path && !s_entry) + return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, + _("Working copy path '%s' does not exist in " + "repository"), e_path); + + /* If the source and target both exist and are of the same kind, + then find out whether they're related. If they're exactly the + same, then we don't have to do anything (unless the report has + changes to the source). If we're ignoring ancestry, then any two + nodes of the same type are related enough for us. */ + related = FALSE; + if (s_entry && t_entry && s_entry->kind == t_entry->kind) + { + int distance = svn_fs_compare_ids(s_entry->id, t_entry->id); + if (distance == 0 && !any_path_info(b, e_path) + && (requested_depth <= wc_depth || t_entry->kind == svn_node_file)) + { + if (!info) + return SVN_NO_ERROR; + + if (!info->start_empty) + { + svn_lock_t *lock; + + if (!info->lock_token) + return SVN_NO_ERROR; + + SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool)); + if (lock && (strcmp(lock->token, info->lock_token) == 0)) + return SVN_NO_ERROR; + } + } + + related = (distance != -1 || b->ignore_ancestry); + } + + /* If there's a source and it's not related to the target, nuke it. */ + if (s_entry && !related) + { + svn_revnum_t deleted_rev; + + SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path, + s_rev, b->t_rev, &deleted_rev, + pool)); + + if (!SVN_IS_VALID_REVNUM(deleted_rev)) + { + /* Two possibilities: either the thing doesn't exist in S_REV; or + it wasn't deleted between S_REV and B->T_REV. In the first case, + I think we should leave DELETED_REV as SVN_INVALID_REVNUM, but + in the second, it should be set to B->T_REV-1 for the call to + delete_entry() below. */ + svn_node_kind_t kind; + + SVN_ERR(svn_fs_check_path(&kind, b->t_root, t_path, pool)); + if (kind != svn_node_none) + deleted_rev = b->t_rev - 1; + } + + SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton, + pool)); + s_path = NULL; + } + + /* If there's no target, we have nothing more to do. */ + if (!t_entry) + return svn_error_trace(skip_path_info(b, e_path)); + + /* Check if the user is authorized to find out about the target. */ + SVN_ERR(check_auth(b, &allowed, t_path, pool)); + if (!allowed) + { + if (t_entry->kind == svn_node_dir) + SVN_ERR(b->editor->absent_directory(e_path, dir_baton, pool)); + else + SVN_ERR(b->editor->absent_file(e_path, dir_baton, pool)); + return svn_error_trace(skip_path_info(b, e_path)); + } + + if (t_entry->kind == svn_node_dir) + { + if (related) + SVN_ERR(b->editor->open_directory(e_path, dir_baton, s_rev, pool, + &new_baton)); + else + SVN_ERR(b->editor->add_directory(e_path, dir_baton, NULL, + SVN_INVALID_REVNUM, pool, + &new_baton)); + + SVN_ERR(delta_dirs(b, s_rev, s_path, t_path, new_baton, e_path, + info ? info->start_empty : FALSE, + wc_depth, requested_depth, pool)); + return svn_error_trace(b->editor->close_directory(new_baton, pool)); + } + else + { + if (related) + { + SVN_ERR(b->editor->open_file(e_path, dir_baton, s_rev, pool, + &new_baton)); + SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path, + info ? info->lock_token : NULL, pool)); + } + else + { + svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; + const char *copyfrom_path = NULL; + SVN_ERR(add_file_smartly(b, e_path, dir_baton, t_path, &new_baton, + ©from_path, ©from_rev, pool)); + if (! copyfrom_path) + /* Send txdelta between empty file (s_path@s_rev doesn't + exist) and added file (t_path@t_root). */ + SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path, + info ? info->lock_token : NULL, pool)); + else + /* Send txdelta between copied file (copyfrom_path@copyfrom_rev) + and added file (tpath@t_root). */ + SVN_ERR(delta_files(b, new_baton, copyfrom_rev, copyfrom_path, + t_path, info ? info->lock_token : NULL, pool)); + } + + SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, b->t_root, + t_path, TRUE, pool)); + hex_digest = svn_checksum_to_cstring(checksum, pool); + return svn_error_trace(b->editor->close_file(new_baton, hex_digest, + pool)); + } +} + +/* A helper macro for when we have to recurse into subdirectories. */ +#define DEPTH_BELOW_HERE(depth) ((depth) == svn_depth_immediates) ? \ + svn_depth_empty : (depth) + +/* Emit edits within directory DIR_BATON (with corresponding path + E_PATH) with the changes from the directory S_REV/S_PATH to the + directory B->t_rev/T_PATH. S_PATH may be NULL if the entry does + not exist in the source. + + WC_DEPTH is this path's depth as reported by set_path/link_path. + REQUESTED_DEPTH is derived from the depth set by + svn_repos_begin_report(). + + When iterating over this directory's entries, the following tables + describe what happens for all possible combinations + of WC_DEPTH/REQUESTED_DEPTH (rows represent WC_DEPTH, columns + represent REQUESTED_DEPTH): + + Legend: + X: ignore this entry (it's either below the requested depth, or + if the requested depth is svn_depth_unknown, below the working + copy depth) + o: handle this entry normally + U: handle the entry as if it were a newly added repository path + (the client is upgrading to a deeper wc and doesn't currently + have this entry, but it should be there after the upgrade, so we + need to send the whole thing, not just deltas) + + For files: + ______________________________________________________________ + | req. depth| unknown | empty | files | immediates | infinity | + |wc. depth | | | | | | + |___________|_________|_______|_______|____________|__________| + |empty | X | X | U | U | U | + |___________|_________|_______|_______|____________|__________| + |files | o | X | o | o | o | + |___________|_________|_______|_______|____________|__________| + |immediates | o | X | o | o | o | + |___________|_________|_______|_______|____________|__________| + |infinity | o | X | o | o | o | + |___________|_________|_______|_______|____________|__________| + + For directories: + ______________________________________________________________ + | req. depth| unknown | empty | files | immediates | infinity | + |wc. depth | | | | | | + |___________|_________|_______|_______|____________|__________| + |empty | X | X | X | U | U | + |___________|_________|_______|_______|____________|__________| + |files | X | X | X | U | U | + |___________|_________|_______|_______|____________|__________| + |immediates | o | X | X | o | o | + |___________|_________|_______|_______|____________|__________| + |infinity | o | X | X | o | o | + |___________|_________|_______|_______|____________|__________| + + These rules are enforced by the is_depth_upgrade() function and by + various other checks below. +*/ +static svn_error_t * +delta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path, + const char *t_path, void *dir_baton, const char *e_path, + svn_boolean_t start_empty, svn_depth_t wc_depth, + svn_depth_t requested_depth, apr_pool_t *pool) +{ + svn_fs_root_t *s_root; + apr_hash_t *s_entries = NULL, *t_entries; + apr_hash_index_t *hi; + apr_pool_t *subpool; + const char *name, *s_fullpath, *t_fullpath, *e_fullpath; + path_info_t *info; + + /* Compare the property lists. If we're starting empty, pass a NULL + source path so that we add all the properties. + + When we support directory locks, we must pass the lock token here. */ + SVN_ERR(delta_proplists(b, s_rev, start_empty ? NULL : s_path, t_path, + NULL, change_dir_prop, dir_baton, pool)); + + if (requested_depth > svn_depth_empty + || requested_depth == svn_depth_unknown) + { + /* Get the list of entries in each of source and target. */ + if (s_path && !start_empty) + { + SVN_ERR(get_source_root(b, &s_root, s_rev)); + SVN_ERR(svn_fs_dir_entries(&s_entries, s_root, s_path, pool)); + } + SVN_ERR(svn_fs_dir_entries(&t_entries, b->t_root, t_path, pool)); + + /* Iterate over the report information for this directory. */ + subpool = svn_pool_create(pool); + + while (1) + { + const svn_fs_dirent_t *s_entry, *t_entry; + + svn_pool_clear(subpool); + SVN_ERR(fetch_path_info(b, &name, &info, e_path, subpool)); + if (!name) + break; + + /* Invalid revnum means we should delete, unless this is + just an excluded subpath. */ + if (info + && !SVN_IS_VALID_REVNUM(info->rev) + && info->depth != svn_depth_exclude) + { + /* We want to perform deletes before non-replacement adds, + for graceful handling of case-only renames on + case-insensitive client filesystems. So, if the report + item is a delete, remove the entry from the source hash, + but don't update the entry yet. */ + if (s_entries) + svn_hash_sets(s_entries, name, NULL); + continue; + } + + e_fullpath = svn_relpath_join(e_path, name, subpool); + t_fullpath = svn_fspath__join(t_path, name, subpool); + t_entry = svn_hash_gets(t_entries, name); + s_fullpath = s_path ? svn_fspath__join(s_path, name, subpool) : NULL; + s_entry = s_entries ? + svn_hash_gets(s_entries, name) : NULL; + + /* The only special cases here are + + - When requested_depth is files but the reported path is + a directory. This is technically a client error, but we + handle it anyway, by skipping the entry. + + - When the reported depth is svn_depth_exclude. + */ + if ((! info || info->depth != svn_depth_exclude) + && (requested_depth != svn_depth_files + || ((! t_entry || t_entry->kind != svn_node_dir) + && (! s_entry || s_entry->kind != svn_node_dir)))) + SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath, + t_entry, dir_baton, e_fullpath, info, + info ? info->depth + : DEPTH_BELOW_HERE(wc_depth), + DEPTH_BELOW_HERE(requested_depth), subpool)); + + /* Don't revisit this name in the target or source entries. */ + svn_hash_sets(t_entries, name, NULL); + if (s_entries + /* Keep the entry for later process if it is reported as + excluded and got deleted in repos. */ + && (! info || info->depth != svn_depth_exclude || t_entry)) + svn_hash_sets(s_entries, name, NULL); + + /* pathinfo entries live in their own subpools due to lookahead, + so we need to clear each one out as we finish with it. */ + if (info) + svn_pool_destroy(info->pool); + } + + /* Remove any deleted entries. Do this before processing the + target, for graceful handling of case-only renames. */ + if (s_entries) + { + for (hi = apr_hash_first(pool, s_entries); + hi; + hi = apr_hash_next(hi)) + { + const svn_fs_dirent_t *s_entry; + + svn_pool_clear(subpool); + s_entry = svn__apr_hash_index_val(hi); + + if (svn_hash_gets(t_entries, s_entry->name) == NULL) + { + svn_revnum_t deleted_rev; + + if (s_entry->kind == svn_node_file + && wc_depth < svn_depth_files) + continue; + + if (s_entry->kind == svn_node_dir + && (wc_depth < svn_depth_immediates + || requested_depth == svn_depth_files)) + continue; + + /* There is no corresponding target entry, so delete. */ + e_fullpath = svn_relpath_join(e_path, s_entry->name, subpool); + SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), + svn_fspath__join(t_path, + s_entry->name, + subpool), + s_rev, b->t_rev, + &deleted_rev, subpool)); + + SVN_ERR(b->editor->delete_entry(e_fullpath, + deleted_rev, + dir_baton, subpool)); + } + } + } + + /* Loop over the dirents in the target. */ + for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi)) + { + const svn_fs_dirent_t *s_entry, *t_entry; + + svn_pool_clear(subpool); + t_entry = svn__apr_hash_index_val(hi); + + if (is_depth_upgrade(wc_depth, requested_depth, t_entry->kind)) + { + /* We're making the working copy deeper, pretend the source + doesn't exist. */ + s_entry = NULL; + s_fullpath = NULL; + } + else + { + if (t_entry->kind == svn_node_file + && requested_depth == svn_depth_unknown + && wc_depth < svn_depth_files) + continue; + + if (t_entry->kind == svn_node_dir + && (wc_depth < svn_depth_immediates + || requested_depth == svn_depth_files)) + continue; + + /* Look for an entry with the same name + in the source dirents. */ + s_entry = s_entries ? + svn_hash_gets(s_entries, t_entry->name) + : NULL; + s_fullpath = s_entry ? + svn_fspath__join(s_path, t_entry->name, subpool) : NULL; + } + + /* Compose the report, editor, and target paths for this entry. */ + e_fullpath = svn_relpath_join(e_path, t_entry->name, subpool); + t_fullpath = svn_fspath__join(t_path, t_entry->name, subpool); + + SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath, + t_entry, dir_baton, e_fullpath, NULL, + DEPTH_BELOW_HERE(wc_depth), + DEPTH_BELOW_HERE(requested_depth), + subpool)); + } + + + /* Destroy iteration subpool. */ + svn_pool_destroy(subpool); + } + return SVN_NO_ERROR; +} + +static svn_error_t * +drive(report_baton_t *b, svn_revnum_t s_rev, path_info_t *info, + apr_pool_t *pool) +{ + const char *t_anchor, *s_fullpath; + svn_boolean_t allowed, info_is_set_path; + svn_fs_root_t *s_root; + const svn_fs_dirent_t *s_entry, *t_entry; + void *root_baton; + + /* Compute the target path corresponding to the working copy anchor, + and check its authorization. */ + t_anchor = *b->s_operand ? svn_fspath__dirname(b->t_path, pool) : b->t_path; + SVN_ERR(check_auth(b, &allowed, t_anchor, pool)); + if (!allowed) + return svn_error_create + (SVN_ERR_AUTHZ_ROOT_UNREADABLE, NULL, + _("Not authorized to open root of edit operation")); + + /* Collect information about the source and target nodes. */ + s_fullpath = svn_fspath__join(b->fs_base, b->s_operand, pool); + SVN_ERR(get_source_root(b, &s_root, s_rev)); + SVN_ERR(fake_dirent(&s_entry, s_root, s_fullpath, pool)); + SVN_ERR(fake_dirent(&t_entry, b->t_root, b->t_path, pool)); + + /* If the operand is a locally added file or directory, it won't + exist in the source, so accept that. */ + info_is_set_path = (SVN_IS_VALID_REVNUM(info->rev) && !info->link_path); + if (info_is_set_path && !s_entry) + s_fullpath = NULL; + + /* Check if the target path exists first. */ + if (!*b->s_operand && !(t_entry)) + return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Target path '%s' does not exist"), + b->t_path); + + /* If the anchor is the operand, the source and target must be dirs. + Check this before opening the root to avoid modifying the wc. */ + else if (!*b->s_operand && (!s_entry || s_entry->kind != svn_node_dir + || t_entry->kind != svn_node_dir)) + return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Cannot replace a directory from within")); + + SVN_ERR(b->editor->set_target_revision(b->edit_baton, b->t_rev, pool)); + SVN_ERR(b->editor->open_root(b->edit_baton, s_rev, pool, &root_baton)); + + /* If the anchor is the operand, diff the two directories; otherwise + update the operand within the anchor directory. */ + if (!*b->s_operand) + SVN_ERR(delta_dirs(b, s_rev, s_fullpath, b->t_path, root_baton, + "", info->start_empty, info->depth, b->requested_depth, + pool)); + else + SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, b->t_path, + t_entry, root_baton, b->s_operand, info, + info->depth, b->requested_depth, pool)); + + return svn_error_trace(b->editor->close_directory(root_baton, pool)); +} + +/* Initialize the baton fields for editor-driving, and drive the editor. */ +static svn_error_t * +finish_report(report_baton_t *b, apr_pool_t *pool) +{ + path_info_t *info; + apr_pool_t *subpool; + svn_revnum_t s_rev; + int i; + + /* Save our pool to manage the lookahead and fs_root cache with. */ + b->pool = pool; + + /* Add the end marker. */ + SVN_ERR(svn_spillbuf__reader_write(b->reader, "-", 1, pool)); + + /* Read the first pathinfo from the report and verify that it is a top-level + set_path entry. */ + SVN_ERR(read_path_info(&info, b->reader, pool)); + if (!info || strcmp(info->path, b->s_operand) != 0 + || info->link_path || !SVN_IS_VALID_REVNUM(info->rev)) + return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, + _("Invalid report for top level of working copy")); + s_rev = info->rev; + + /* Initialize the lookahead pathinfo. */ + subpool = svn_pool_create(pool); + SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); + + if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0) + { + /* If the operand of the wc operation is switched or deleted, + then info above is just a place-holder, and the only thing we + have to do is pass the revision it contains to open_root. + The next pathinfo actually describes the target. */ + if (!*b->s_operand) + return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, + _("Two top-level reports with no target")); + /* If the client issued a set-path followed by a delete-path, we need + to respect the depth set by the initial set-path. */ + if (! SVN_IS_VALID_REVNUM(b->lookahead->rev)) + { + b->lookahead->depth = info->depth; + } + info = b->lookahead; + SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); + } + + /* Open the target root and initialize the source root cache. */ + SVN_ERR(svn_fs_revision_root(&b->t_root, b->repos->fs, b->t_rev, pool)); + for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++) + b->s_roots[i] = NULL; + + { + svn_error_t *err = svn_error_trace(drive(b, s_rev, info, pool)); + + if (err == SVN_NO_ERROR) + return svn_error_trace(b->editor->close_edit(b->edit_baton, pool)); + + return svn_error_trace( + svn_error_compose_create(err, + b->editor->abort_edit(b->edit_baton, + pool))); + } +} + +/* --- COLLECTING THE REPORT INFORMATION --- */ + +/* Record a report operation into the spill buffer. Return an error + if DEPTH is svn_depth_unknown. */ +static svn_error_t * +write_path_info(report_baton_t *b, const char *path, const char *lpath, + svn_revnum_t rev, svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, apr_pool_t *pool) +{ + const char *lrep, *rrep, *drep, *ltrep, *rep; + + /* Munge the path to be anchor-relative, so that we can use edit paths + as report paths. */ + path = svn_relpath_join(b->s_operand, path, pool); + + lrep = lpath ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s", + strlen(lpath), lpath) : "-"; + rrep = (SVN_IS_VALID_REVNUM(rev)) ? + apr_psprintf(pool, "+%ld:", rev) : "-"; + + if (depth == svn_depth_exclude) + drep = "+X"; + else if (depth == svn_depth_empty) + drep = "+E"; + else if (depth == svn_depth_files) + drep = "+F"; + else if (depth == svn_depth_immediates) + drep = "+M"; + else if (depth == svn_depth_infinity) + drep = "-"; + else + return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, + _("Unsupported report depth '%s'"), + svn_depth_to_word(depth)); + + ltrep = lock_token ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s", + strlen(lock_token), lock_token) : "-"; + rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s", + strlen(path), path, lrep, rrep, drep, + start_empty ? '+' : '-', ltrep); + return svn_error_trace( + svn_spillbuf__reader_write(b->reader, rep, strlen(rep), pool)); +} + +svn_error_t * +svn_repos_set_path3(void *baton, const char *path, svn_revnum_t rev, + svn_depth_t depth, svn_boolean_t start_empty, + const char *lock_token, apr_pool_t *pool) +{ + return svn_error_trace( + write_path_info(baton, path, NULL, rev, depth, start_empty, + lock_token, pool)); +} + +svn_error_t * +svn_repos_link_path3(void *baton, const char *path, const char *link_path, + svn_revnum_t rev, svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, apr_pool_t *pool) +{ + if (depth == svn_depth_exclude) + return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, + _("Depth 'exclude' not supported for link")); + + return svn_error_trace( + write_path_info(baton, path, link_path, rev, depth, + start_empty, lock_token, pool)); +} + +svn_error_t * +svn_repos_delete_path(void *baton, const char *path, apr_pool_t *pool) +{ + /* We pass svn_depth_infinity because deletion of a path always + deletes everything underneath it. */ + return svn_error_trace( + write_path_info(baton, path, NULL, SVN_INVALID_REVNUM, + svn_depth_infinity, FALSE, NULL, pool)); +} + +svn_error_t * +svn_repos_finish_report(void *baton, apr_pool_t *pool) +{ + report_baton_t *b = baton; + + return svn_error_trace(finish_report(b, pool)); +} + +svn_error_t * +svn_repos_abort_report(void *baton, apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + +/* --- BEGINNING THE REPORT --- */ + + +svn_error_t * +svn_repos_begin_report3(void **report_baton, + svn_revnum_t revnum, + svn_repos_t *repos, + const char *fs_base, + const char *s_operand, + const char *switch_path, + svn_boolean_t text_deltas, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t send_copyfrom_args, + const svn_delta_editor_t *editor, + void *edit_baton, + svn_repos_authz_func_t authz_read_func, + void *authz_read_baton, + apr_size_t zero_copy_limit, + apr_pool_t *pool) +{ + report_baton_t *b; + const char *uuid; + + if (depth == svn_depth_exclude) + return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, + _("Request depth 'exclude' not supported")); + + SVN_ERR(svn_fs_get_uuid(repos->fs, &uuid, pool)); + + /* Build a reporter baton. Copy strings in case the caller doesn't + keep track of them. */ + b = apr_palloc(pool, sizeof(*b)); + b->repos = repos; + b->fs_base = svn_fspath__canonicalize(fs_base, pool); + b->s_operand = apr_pstrdup(pool, s_operand); + b->t_rev = revnum; + b->t_path = switch_path ? svn_fspath__canonicalize(switch_path, pool) + : svn_fspath__join(b->fs_base, s_operand, pool); + b->text_deltas = text_deltas; + b->zero_copy_limit = zero_copy_limit; + b->requested_depth = depth; + b->ignore_ancestry = ignore_ancestry; + b->send_copyfrom_args = send_copyfrom_args; + b->is_switch = (switch_path != NULL); + b->editor = editor; + b->edit_baton = edit_baton; + b->authz_read_func = authz_read_func; + b->authz_read_baton = authz_read_baton; + b->revision_infos = apr_hash_make(pool); + b->pool = pool; + b->reader = svn_spillbuf__reader_create(1000 /* blocksize */, + 1000000 /* maxsize */, + pool); + b->repos_uuid = svn_string_create(uuid, pool); + + /* Hand reporter back to client. */ + *report_baton = b; + return SVN_NO_ERROR; +} |