summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/adm_crawler.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/adm_crawler.c')
-rw-r--r--subversion/libsvn_wc/adm_crawler.c1239
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);
+}
OpenPOWER on IntegriCloud