diff options
Diffstat (limited to 'subversion/libsvn_wc/adm_crawler.c')
-rw-r--r-- | subversion/libsvn_wc/adm_crawler.c | 1239 |
1 files changed, 1239 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/adm_crawler.c b/subversion/libsvn_wc/adm_crawler.c new file mode 100644 index 0000000..e5935a2 --- /dev/null +++ b/subversion/libsvn_wc/adm_crawler.c @@ -0,0 +1,1239 @@ +/* + * adm_crawler.c: report local WC mods to an Editor. + * + * ==================================================================== + * 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 <string.h> + +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_hash.h> + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_wc.h" +#include "svn_io.h" +#include "svn_delta.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + +#include "private/svn_wc_private.h" + +#include "wc.h" +#include "adm_files.h" +#include "translate.h" +#include "workqueue.h" +#include "conflicts.h" + +#include "svn_private_config.h" + + +/* Helper for report_revisions_and_depths(). + + Perform an atomic restoration of the file LOCAL_ABSPATH; that is, copy + the file's text-base to the administrative tmp area, and then move + that file to LOCAL_ABSPATH with possible translations/expansions. If + USE_COMMIT_TIMES is set, then set working file's timestamp to + last-commit-time. Either way, set entry-timestamp to match that of + the working file when all is finished. + + If MARK_RESOLVED_TEXT_CONFLICT is TRUE, mark as resolved any existing + text conflict on LOCAL_ABSPATH. + + Not that a valid access baton with a write lock to the directory of + LOCAL_ABSPATH must be available in DB.*/ +static svn_error_t * +restore_file(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t use_commit_times, + svn_boolean_t mark_resolved_text_conflict, + apr_pool_t *scratch_pool) +{ + svn_skel_t *work_item; + + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + db, local_abspath, + NULL /* source_abspath */, + use_commit_times, + TRUE /* record_fileinfo */, + scratch_pool, scratch_pool)); + /* ### we need an existing path for wq_add. not entirely WRI_ABSPATH yet */ + SVN_ERR(svn_wc__db_wq_add(db, + svn_dirent_dirname(local_abspath, scratch_pool), + work_item, scratch_pool)); + + /* Run the work item immediately. */ + SVN_ERR(svn_wc__wq_run(db, local_abspath, + NULL, NULL, /* ### nice to have cancel_func/baton */ + scratch_pool)); + + /* Remove any text conflict */ + if (mark_resolved_text_conflict) + SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_restore(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t use_commit_times, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_node_kind_t disk_kind; + const svn_checksum_t *checksum; + + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); + + if (disk_kind != svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, + _("The existing node '%s' can not be restored."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_normal + && !((status == svn_wc__db_status_added + || status == svn_wc__db_status_incomplete) + && (kind == svn_node_dir + || (kind == svn_node_file && checksum != NULL) + /* || (kind == svn_node_symlink && target)*/))) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("The node '%s' can not be restored."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + if (kind == svn_node_file || kind == svn_node_symlink) + SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times, + FALSE /*mark_resolved_text_conflict*/, + scratch_pool)); + else + SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Try to restore LOCAL_ABSPATH of node type KIND and if successfull, + notify that the node is restored. Use DB for accessing the working copy. + If USE_COMMIT_TIMES is set, then set working file's timestamp to + last-commit-time. + + This function does all temporary allocations in SCRATCH_POOL + */ +static svn_error_t * +restore_node(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + svn_boolean_t use_commit_times, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + if (kind == svn_node_file || kind == svn_node_symlink) + { + /* Recreate file from text-base; mark any text conflict as resolved */ + SVN_ERR(restore_file(db, local_abspath, use_commit_times, + TRUE /*mark_resolved_text_conflict*/, + scratch_pool)); + } + else if (kind == svn_node_dir) + { + /* Recreating a directory is just a mkdir */ + SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); + } + + /* ... report the restoration to the caller. */ + if (notify_func != NULL) + { + svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_restore, + scratch_pool); + notify->kind = svn_node_file; + (*notify_func)(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* The recursive crawler that describes a mixed-revision working + copy to an RA layer. Used to initiate updates. + + This is a depth-first recursive walk of the children of DIR_ABSPATH + (not including DIR_ABSPATH itself) using DB. Look at each node and + check if its revision is different than DIR_REV. If so, report this + fact to REPORTER. If a node has a different URL than expected, or + a different depth than its parent, report that to REPORTER. + + Report DIR_ABSPATH to the reporter as REPORT_RELPATH. + + Alternatively, if REPORT_EVERYTHING is set, then report all + children unconditionally. + + DEPTH is actually the *requested* depth for the update-like + operation for which we are reporting working copy state. However, + certain requested depths affect the depth of the report crawl. For + example, if the requested depth is svn_depth_empty, there's no + point descending into subdirs, no matter what their depths. So: + + If DEPTH is svn_depth_empty, don't report any files and don't + descend into any subdirs. If svn_depth_files, report files but + still don't descend into subdirs. If svn_depth_immediates, report + files, and report subdirs themselves but not their entries. If + svn_depth_infinity or svn_depth_unknown, report everything all the + way down. (That last sentence might sound counterintuitive, but + since you can't go deeper than the local ambient depth anyway, + requesting svn_depth_infinity really means "as deep as the various + parts of this working copy go". Of course, the information that + comes back from the server will be different for svn_depth_unknown + than for svn_depth_infinity.) + + DIR_REPOS_RELPATH, DIR_REPOS_ROOT and DIR_DEPTH are the repository + relative path, the repository root and depth stored on the directory, + passed here to avoid another database query. + + DEPTH_COMPATIBILITY_TRICK means the same thing here as it does + in svn_wc_crawl_revisions5(). + + If RESTORE_FILES is set, then unexpectedly missing working files + will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON + will be called to report the restoration. USE_COMMIT_TIMES is + passed to restore_file() helper. */ +static svn_error_t * +report_revisions_and_depths(svn_wc__db_t *db, + const char *dir_abspath, + const char *report_relpath, + svn_revnum_t dir_rev, + const char *dir_repos_relpath, + const char *dir_repos_root, + svn_depth_t dir_depth, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_depth_t depth, + svn_boolean_t honor_depth_exclude, + svn_boolean_t depth_compatibility_trick, + svn_boolean_t report_everything, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_hash_t *base_children; + apr_hash_t *dirents; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + svn_error_t *err; + + + /* Get both the SVN Entries and the actual on-disk entries. Also + notice that we're picking up hidden entries too (read_children never + hides children). */ + SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath, + scratch_pool, iterpool)); + + if (restore_files) + { + err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE, + scratch_pool, scratch_pool); + + if (err && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + /* There is no directory, and if we could create the directory + we would have already created it when walking the parent + directory */ + restore_files = FALSE; + dirents = NULL; + } + else + SVN_ERR(err); + } + else + dirents = NULL; + + /*** Do the real reporting and recursing. ***/ + + /* Looping over current directory's BASE children: */ + for (hi = apr_hash_first(scratch_pool, base_children); + hi != NULL; + hi = apr_hash_next(hi)) + { + const char *child = svn__apr_hash_index_key(hi); + const char *this_report_relpath; + const char *this_abspath; + svn_boolean_t this_switched = FALSE; + struct svn_wc__db_base_info_t *ths = svn__apr_hash_index_val(hi); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Clear the iteration subpool here because the loop has a bunch + of 'continue' jump statements. */ + svn_pool_clear(iterpool); + + /* Compute the paths and URLs we need. */ + this_report_relpath = svn_relpath_join(report_relpath, child, iterpool); + this_abspath = svn_dirent_join(dir_abspath, child, iterpool); + + /*** File Externals **/ + if (ths->update_root) + { + /* File externals are ... special. We ignore them. */; + continue; + } + + /* First check for exclusion */ + if (ths->status == svn_wc__db_status_excluded) + { + if (honor_depth_exclude) + { + /* Report the excluded path, no matter whether report_everything + flag is set. Because the report_everything flag indicates + that the server will treat the wc as empty and thus push + full content of the files/subdirs. But we want to prevent the + server from pushing the full content of this_path at us. */ + + /* The server does not support link_path report on excluded + path. We explicitly prohibit this situation in + svn_wc_crop_tree(). */ + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + dir_rev, + svn_depth_exclude, + FALSE, + NULL, + iterpool)); + } + else + { + /* We want to pull in the excluded target. So, report it as + deleted, and server will respond properly. */ + if (! report_everything) + SVN_ERR(reporter->delete_path(report_baton, + this_report_relpath, iterpool)); + } + continue; + } + + /*** The Big Tests: ***/ + if (ths->status == svn_wc__db_status_server_excluded + || ths->status == svn_wc__db_status_not_present) + { + /* If the entry is 'absent' or 'not-present', make sure the server + knows it's gone... + ...unless we're reporting everything, in which case we're + going to report it missing later anyway. + + This instructs the server to send it back to us, if it is + now available (an addition after a not-present state), or if + it is now authorized (change in authz for the absent item). */ + if (! report_everything) + SVN_ERR(reporter->delete_path(report_baton, this_report_relpath, + iterpool)); + continue; + } + + /* Is the entry NOT on the disk? We may be able to restore it. */ + if (restore_files + && svn_hash_gets(dirents, child) == NULL) + { + svn_wc__db_status_t wrk_status; + svn_node_kind_t wrk_kind; + const svn_checksum_t *checksum; + + SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + db, this_abspath, iterpool, iterpool)); + + if ((wrk_status == svn_wc__db_status_normal + || wrk_status == svn_wc__db_status_added + || wrk_status == svn_wc__db_status_incomplete) + && (wrk_kind == svn_node_dir || checksum)) + { + svn_node_kind_t dirent_kind; + + /* It is possible on a case insensitive system that the + entry is not really missing, but just cased incorrectly. + In this case we can't overwrite it with the pristine + version */ + SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool)); + + if (dirent_kind == svn_node_none) + { + SVN_ERR(restore_node(db, this_abspath, wrk_kind, + use_commit_times, notify_func, + notify_baton, iterpool)); + } + } + } + + /* And finally prepare for reporting */ + if (!ths->repos_relpath) + { + ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child, + iterpool); + } + else + { + const char *childname + = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath); + + if (childname == NULL || strcmp(childname, child) != 0) + { + this_switched = TRUE; + } + } + + /* Tweak THIS_DEPTH to a useful value. */ + if (ths->depth == svn_depth_unknown) + ths->depth = svn_depth_infinity; + + /*** Files ***/ + if (ths->kind == svn_node_file + || ths->kind == svn_node_symlink) + { + if (report_everything) + { + /* Report the file unconditionally, one way or another. */ + if (this_switched) + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + else + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + + /* Possibly report a disjoint URL ... */ + else if (this_switched) + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + /* ... or perhaps just a differing revision or lock token, + or the mere presence of the file in a depth-empty dir. */ + else if (ths->revnum != dir_rev + || ths->lock + || dir_depth == svn_depth_empty) + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } /* end file case */ + + /*** Directories (in recursive mode) ***/ + else if (ths->kind == svn_node_dir + && (depth > svn_depth_files + || depth == svn_depth_unknown)) + { + svn_boolean_t is_incomplete; + svn_boolean_t start_empty; + svn_depth_t report_depth = ths->depth; + + is_incomplete = (ths->status == svn_wc__db_status_incomplete); + start_empty = is_incomplete; + + if (!SVN_DEPTH_IS_RECURSIVE(depth)) + report_depth = svn_depth_empty; + + /* When a <= 1.6 working copy is upgraded without some of its + subdirectories we miss some information in the database. If we + report the revision as -1, the update editor will receive an + add_directory() while it still knows the directory. + + This would raise strange tree conflicts and probably assertions + as it would a BASE vs BASE conflict */ + if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum)) + ths->revnum = dir_rev; + + if (depth_compatibility_trick + && ths->depth <= svn_depth_files + && depth > ths->depth) + { + start_empty = TRUE; + } + + if (report_everything) + { + /* Report the dir unconditionally, one way or another... */ + if (this_switched) + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token + : NULL, + iterpool)); + else + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + else if (this_switched) + { + /* ...or possibly report a disjoint URL ... */ + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + else if (ths->revnum != dir_rev + || ths->lock + || is_incomplete + || dir_depth == svn_depth_empty + || dir_depth == svn_depth_files + || (dir_depth == svn_depth_immediates + && ths->depth != svn_depth_empty) + || (ths->depth < svn_depth_infinity + && SVN_DEPTH_IS_RECURSIVE(depth))) + { + /* ... or perhaps just a differing revision, lock token, + incomplete subdir, the mere presence of the directory + in a depth-empty or depth-files dir, or if the parent + dir is at depth-immediates but the child is not at + depth-empty. Also describe shallow subdirs if we are + trying to set depth to infinity. */ + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + + /* Finally, recurse if necessary and appropriate. */ + if (SVN_DEPTH_IS_RECURSIVE(depth)) + { + const char *repos_relpath = ths->repos_relpath; + + if (repos_relpath == NULL) + { + repos_relpath = svn_relpath_join(dir_repos_relpath, child, + iterpool); + } + + SVN_ERR(report_revisions_and_depths(db, + this_abspath, + this_report_relpath, + ths->revnum, + repos_relpath, + dir_repos_root, + ths->depth, + reporter, report_baton, + restore_files, depth, + honor_depth_exclude, + depth_compatibility_trick, + start_empty, + use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + } /* end directory case */ + } /* end main entries loop */ + + /* We're done examining this dir's entries, so free everything. */ + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/*------------------------------------------------------------------*/ +/*** Public Interfaces ***/ + + +svn_error_t * +svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_depth_t depth, + svn_boolean_t honor_depth_exclude, + svn_boolean_t depth_compatibility_trick, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *fserr, *err; + svn_revnum_t target_rev = SVN_INVALID_REVNUM; + svn_boolean_t start_empty; + svn_wc__db_status_t status; + svn_node_kind_t target_kind; + const char *repos_relpath, *repos_root_url; + svn_depth_t target_depth; + svn_wc__db_lock_t *target_lock; + svn_node_kind_t disk_kind; + svn_depth_t report_depth; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Get the base rev, which is the first revnum that entries will be + compared to, and some other WC info about the target. */ + err = svn_wc__db_base_get_info(&status, &target_kind, &target_rev, + &repos_relpath, &repos_root_url, + NULL, NULL, NULL, NULL, &target_depth, + NULL, NULL, &target_lock, + NULL, NULL, NULL, + db, local_abspath, scratch_pool, + scratch_pool); + + if (err + || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_incomplete)) + { + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + /* We don't know about this node, so all we have to do is tell + the reporter that we don't know this node. + + But first we have to start the report by sending some basic + information for the root. */ + + if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + SVN_ERR(reporter->set_path(report_baton, "", 0, depth, FALSE, + NULL, scratch_pool)); + SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool)); + + /* Finish the report, which causes the update editor to be + driven. */ + SVN_ERR(reporter->finish_report(report_baton, scratch_pool)); + + return SVN_NO_ERROR; + } + + if (target_depth == svn_depth_unknown) + target_depth = svn_depth_infinity; + + start_empty = (status == svn_wc__db_status_incomplete); + if (depth_compatibility_trick + && target_depth <= svn_depth_immediates + && depth > target_depth) + { + start_empty = TRUE; + } + + if (restore_files) + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); + else + disk_kind = svn_node_unknown; + + /* Determine if there is a missing node that should be restored */ + if (restore_files + && disk_kind == svn_node_none) + { + svn_wc__db_status_t wrk_status; + svn_node_kind_t wrk_kind; + const svn_checksum_t *checksum; + + err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &checksum, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + wrk_status = svn_wc__db_status_not_present; + wrk_kind = svn_node_file; + } + else + SVN_ERR(err); + + if ((wrk_status == svn_wc__db_status_normal + || wrk_status == svn_wc__db_status_added + || wrk_status == svn_wc__db_status_incomplete) + && (wrk_kind == svn_node_dir || checksum)) + { + SVN_ERR(restore_node(wc_ctx->db, local_abspath, + wrk_kind, use_commit_times, + notify_func, notify_baton, + scratch_pool)); + } + } + + { + report_depth = target_depth; + + if (honor_depth_exclude + && depth != svn_depth_unknown + && depth < target_depth) + report_depth = depth; + + /* The first call to the reporter merely informs it that the + top-level directory being updated is at BASE_REV. Its PATH + argument is ignored. */ + SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth, + start_empty, NULL, scratch_pool)); + } + if (target_kind == svn_node_dir) + { + if (depth != svn_depth_empty) + { + /* Recursively crawl ROOT_DIRECTORY and report differing + revisions. */ + err = report_revisions_and_depths(wc_ctx->db, + local_abspath, + "", + target_rev, + repos_relpath, + repos_root_url, + report_depth, + reporter, report_baton, + restore_files, depth, + honor_depth_exclude, + depth_compatibility_trick, + start_empty, + use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + if (err) + goto abort_report; + } + } + + else if (target_kind == svn_node_file || target_kind == svn_node_symlink) + { + const char *parent_abspath, *base; + svn_wc__db_status_t parent_status; + const char *parent_repos_relpath; + + svn_dirent_split(&parent_abspath, &base, local_abspath, + scratch_pool); + + /* We can assume a file is in the same repository as its parent + directory, so we only look at the relpath. */ + err = svn_wc__db_base_get_info(&parent_status, NULL, NULL, + &parent_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, parent_abspath, + scratch_pool, scratch_pool); + + if (err) + goto abort_report; + + if (strcmp(repos_relpath, + svn_relpath_join(parent_repos_relpath, base, + scratch_pool)) != 0) + { + /* This file is disjoint with respect to its parent + directory. Since we are looking at the actual target of + the report (not some file in a subdirectory of a target + directory), and that target is a file, we need to pass an + empty string to link_path. */ + err = reporter->link_path(report_baton, + "", + svn_path_url_add_component2( + repos_root_url, + repos_relpath, + scratch_pool), + target_rev, + svn_depth_infinity, + FALSE, + target_lock ? target_lock->token : NULL, + scratch_pool); + if (err) + goto abort_report; + } + else if (target_lock) + { + /* If this entry is a file node, we just want to report that + node's revision. Since we are looking at the actual target + of the report (not some file in a subdirectory of a target + directory), and that target is a file, we need to pass an + empty string to set_path. */ + err = reporter->set_path(report_baton, "", target_rev, + svn_depth_infinity, + FALSE, + target_lock ? target_lock->token : NULL, + scratch_pool); + if (err) + goto abort_report; + } + } + + /* Finish the report, which causes the update editor to be driven. */ + return svn_error_trace(reporter->finish_report(report_baton, scratch_pool)); + + abort_report: + /* Clean up the fs transaction. */ + if ((fserr = reporter->abort_report(report_baton, scratch_pool))) + { + fserr = svn_error_quick_wrap(fserr, _("Error aborting report")); + svn_error_compose(err, fserr); + } + return svn_error_trace(err); +} + +/*** Copying stream ***/ + +/* A copying stream is a bit like the unix tee utility: + * + * It reads the SOURCE when asked for data and while returning it, + * also writes the same data to TARGET. + */ +struct copying_stream_baton +{ + /* Stream to read input from. */ + svn_stream_t *source; + + /* Stream to write all data read to. */ + svn_stream_t *target; +}; + + +/* */ +static svn_error_t * +read_handler_copy(void *baton, char *buffer, apr_size_t *len) +{ + struct copying_stream_baton *btn = baton; + + SVN_ERR(svn_stream_read(btn->source, buffer, len)); + + return svn_stream_write(btn->target, buffer, len); +} + +/* */ +static svn_error_t * +close_handler_copy(void *baton) +{ + struct copying_stream_baton *btn = baton; + + SVN_ERR(svn_stream_close(btn->target)); + return svn_stream_close(btn->source); +} + + +/* Return a stream - allocated in POOL - which reads its input + * from SOURCE and, while returning that to the caller, at the + * same time writes that to TARGET. + */ +static svn_stream_t * +copying_stream(svn_stream_t *source, + svn_stream_t *target, + apr_pool_t *pool) +{ + struct copying_stream_baton *baton; + svn_stream_t *stream; + + baton = apr_palloc(pool, sizeof (*baton)); + baton->source = source; + baton->target = target; + + stream = svn_stream_create(baton, pool); + svn_stream_set_read(stream, read_handler_copy); + svn_stream_set_close(stream, close_handler_copy); + + return stream; +} + + +/* Set *STREAM to a stream from which the caller can read the pristine text + * of the working version of the file at LOCAL_ABSPATH. If the working + * version of LOCAL_ABSPATH has no pristine text because it is locally + * added, set *STREAM to an empty stream. If the working version of + * LOCAL_ABSPATH is not a file, return an error. + * + * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum. + * + * Arrange for the actual checksum of the text to be calculated and written + * into *ACTUAL_MD5_CHECKSUM when the stream is read. + */ +static svn_error_t * +read_and_checksum_pristine_text(svn_stream_t **stream, + const svn_checksum_t **expected_md5_checksum, + svn_checksum_t **actual_md5_checksum, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stream_t *base_stream; + + SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath, + result_pool, scratch_pool)); + if (base_stream == NULL) + { + base_stream = svn_stream_empty(result_pool); + *expected_md5_checksum = NULL; + *actual_md5_checksum = NULL; + } + else + { + const svn_checksum_t *expected_md5; + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &expected_md5, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + if (expected_md5 == NULL) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Pristine checksum for file '%s' is missing"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (expected_md5->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath, + expected_md5, + result_pool, scratch_pool)); + *expected_md5_checksum = expected_md5; + + /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually* + found when the base stream is read. */ + base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum, + NULL, svn_checksum_md5, TRUE, + result_pool); + } + + *stream = base_stream; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__internal_transmit_text_deltas(const char **tempfile, + const svn_checksum_t **new_text_base_md5_checksum, + const svn_checksum_t **new_text_base_sha1_checksum, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t fulltext, + const svn_delta_editor_t *editor, + void *file_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_txdelta_window_handler_t handler; + void *wh_baton; + const svn_checksum_t *expected_md5_checksum; /* recorded MD5 of BASE_S. */ + svn_checksum_t *verify_checksum; /* calc'd MD5 of BASE_STREAM */ + svn_checksum_t *local_md5_checksum; /* calc'd MD5 of LOCAL_STREAM */ + svn_checksum_t *local_sha1_checksum; /* calc'd SHA1 of LOCAL_STREAM */ + const char *new_pristine_tmp_abspath; + svn_error_t *err; + svn_stream_t *base_stream; /* delta source */ + svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */ + + /* Translated input */ + SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db, + local_abspath, local_abspath, + SVN_WC_TRANSLATE_TO_NF, + scratch_pool, scratch_pool)); + + /* If the caller wants a copy of the working file translated to + * repository-normal form, make the copy by tee-ing the stream and set + * *TEMPFILE to the path to it. This is only needed for the 1.6 API, + * 1.7 doesn't set TEMPFILE. Even when using the 1.6 API this file + * is not used by the functions that would have used it when using + * the 1.6 code. It's possible that 3rd party users (if there are any) + * might expect this file to be a text-base. */ + if (tempfile) + { + svn_stream_t *tempstream; + + /* It can't be the same location as in 1.6 because the admin directory + no longer exists. */ + SVN_ERR(svn_stream_open_unique(&tempstream, tempfile, + NULL, svn_io_file_del_none, + result_pool, scratch_pool)); + + /* Wrap the translated stream with a new stream that writes the + translated contents into the new text base file as we read from it. + Note that the new text base file will be closed when the new stream + is closed. */ + local_stream = copying_stream(local_stream, tempstream, scratch_pool); + } + if (new_text_base_sha1_checksum) + { + svn_stream_t *new_pristine_stream; + + SVN_ERR(svn_wc__open_writable_base(&new_pristine_stream, + &new_pristine_tmp_abspath, + NULL, &local_sha1_checksum, + db, local_abspath, + scratch_pool, scratch_pool)); + local_stream = copying_stream(local_stream, new_pristine_stream, + scratch_pool); + } + + /* If sending a full text is requested, or if there is no pristine text + * (e.g. the node is locally added), then set BASE_STREAM to an empty + * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL. + * + * Otherwise, set BASE_STREAM to a stream providing the base (source) text + * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum, + * and arrange for its VERIFY_CHECKSUM to be calculated later. */ + if (! fulltext) + { + /* We will be computing a delta against the pristine contents */ + /* We need the expected checksum to be an MD-5 checksum rather than a + * SHA-1 because we want to pass it to apply_textdelta(). */ + SVN_ERR(read_and_checksum_pristine_text(&base_stream, + &expected_md5_checksum, + &verify_checksum, + db, local_abspath, + scratch_pool, scratch_pool)); + } + else + { + /* Send a fulltext. */ + base_stream = svn_stream_empty(scratch_pool); + expected_md5_checksum = NULL; + verify_checksum = NULL; + } + + /* Tell the editor that we're about to apply a textdelta to the + file baton; the editor returns to us a window consumer and baton. */ + { + /* apply_textdelta() is working against a base with this checksum */ + const char *base_digest_hex = NULL; + + if (expected_md5_checksum) + /* ### Why '..._display()'? expected_md5_checksum should never be all- + * zero, but if it is, we would want to pass NULL not an all-zero + * digest to apply_textdelta(), wouldn't we? */ + base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum, + scratch_pool); + + SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool, + &handler, &wh_baton)); + } + + /* Run diff processing, throwing windows at the handler. */ + err = svn_txdelta_run(base_stream, local_stream, + handler, wh_baton, + svn_checksum_md5, &local_md5_checksum, + NULL, NULL, + scratch_pool, scratch_pool); + + /* Close the two streams to force writing the digest */ + err = svn_error_compose_create(err, svn_stream_close(base_stream)); + err = svn_error_compose_create(err, svn_stream_close(local_stream)); + + /* If we have an error, it may be caused by a corrupt text base, + so check the checksum. */ + if (expected_md5_checksum && verify_checksum + && !svn_checksum_match(expected_md5_checksum, verify_checksum)) + { + /* The entry checksum does not match the actual text + base checksum. Extreme badness. Of course, + theoretically we could just switch to + fulltext transmission here, and everything would + work fine; after all, we're going to replace the + text base with a new one in a moment anyway, and + we'd fix the checksum then. But it's better to + error out. People should know that their text + bases are getting corrupted, so they can + investigate. Other commands could be affected, + too, such as `svn diff'. */ + + if (tempfile) + err = svn_error_compose_create( + err, + svn_io_remove_file2(*tempfile, TRUE, scratch_pool)); + + err = svn_error_compose_create( + svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum, + scratch_pool, + _("Checksum mismatch for text base of '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)), + err); + + return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL); + } + + /* Now, handle that delta transmission error if any, so we can stop + thinking about it after this point. */ + SVN_ERR_W(err, apr_psprintf(scratch_pool, + _("While preparing '%s' for commit"), + svn_dirent_local_style(local_abspath, + scratch_pool))); + + if (new_text_base_md5_checksum) + *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum, + result_pool); + if (new_text_base_sha1_checksum) + { + SVN_ERR(svn_wc__db_pristine_install(db, new_pristine_tmp_abspath, + local_sha1_checksum, + local_md5_checksum, + scratch_pool)); + *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum, + result_pool); + } + + /* Close the file baton, and get outta here. */ + return svn_error_trace( + editor->close_file(file_baton, + svn_checksum_to_cstring(local_md5_checksum, + scratch_pool), + scratch_pool)); +} + +svn_error_t * +svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum, + const svn_checksum_t **new_text_base_sha1_checksum, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t fulltext, + const svn_delta_editor_t *editor, + void *file_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_wc__internal_transmit_text_deltas(NULL, + new_text_base_md5_checksum, + new_text_base_sha1_checksum, + wc_ctx->db, local_abspath, + fulltext, editor, + file_baton, result_pool, + scratch_pool); +} + +svn_error_t * +svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db, + const char *local_abspath, + const svn_delta_editor_t *editor, + void *baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + apr_array_header_t *propmods; + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, + FALSE /* allow_missing */, + FALSE /* show_deleted */, + FALSE /* show_hidden */, + iterpool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, iterpool)); + + /* Get an array of local changes by comparing the hashes. */ + SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath, + scratch_pool, iterpool)); + + /* Apply each local change to the baton */ + for (i = 0; i < propmods->nelts; i++) + { + const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t); + + svn_pool_clear(iterpool); + + if (kind == svn_node_file) + SVN_ERR(editor->change_file_prop(baton, p->name, p->value, + iterpool)); + else + SVN_ERR(editor->change_dir_prop(baton, p->name, p->value, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_delta_editor_t *editor, + void *baton, + apr_pool_t *scratch_pool) +{ + return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath, + editor, baton, scratch_pool); +} |