diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc/diff_editor.c | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_wc/diff_editor.c')
-rw-r--r-- | subversion/libsvn_wc/diff_editor.c | 2747 |
1 files changed, 2747 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/diff_editor.c b/subversion/libsvn_wc/diff_editor.c new file mode 100644 index 0000000..839241f --- /dev/null +++ b/subversion/libsvn_wc/diff_editor.c @@ -0,0 +1,2747 @@ +/* + * diff_editor.c -- The diff editor for comparing the working copy against the + * repository. + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +/* + * This code uses an svn_delta_editor_t editor driven by + * svn_wc_crawl_revisions (like the update command) to retrieve the + * differences between the working copy and the requested repository + * version. Rather than updating the working copy, this new editor creates + * temporary files that contain the pristine repository versions. When the + * crawler closes the files the editor calls back to a client layer + * function to compare the working copy and the temporary file. There is + * only ever one temporary file in existence at any time. + * + * When the crawler closes a directory, the editor then calls back to the + * client layer to compare any remaining files that may have been modified + * locally. Added directories do not have corresponding temporary + * directories created, as they are not needed. + * + * The diff result from this editor is a combination of the restructuring + * operations from the repository with the local restructurings since checking + * out. + * + * ### TODO: Make sure that we properly support and report multi layered + * operations instead of only simple file replacements. + * + * ### TODO: Replacements where the node kind changes needs support. It + * mostly works when the change is in the repository, but not when it is + * in the working copy. + * + * ### TODO: Do we need to support copyfrom? + * + */ + +#include <apr_hash.h> +#include <apr_md5.h> + +#include <assert.h> + +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_sorts.h" + +#include "private/svn_subr_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_diff_tree.h" +#include "private/svn_editor.h" + +#include "wc.h" +#include "props.h" +#include "adm_files.h" +#include "translate.h" +#include "diff.h" + +#include "svn_private_config.h" + +/*-------------------------------------------------------------------------*/ + + +/* Overall crawler editor baton. + */ +struct edit_baton_t +{ + /* A wc db. */ + svn_wc__db_t *db; + + /* A diff tree processor, receiving the result of the diff. */ + const svn_diff_tree_processor_t *processor; + + /* A boolean indicating whether local additions should be reported before + remote deletes. The processor can transform adds in deletes and deletes + in adds, but it can't reorder the output. */ + svn_boolean_t local_before_remote; + + /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */ + const char *target; + const char *anchor_abspath; + + /* Target revision */ + svn_revnum_t revnum; + + /* Was the root opened? */ + svn_boolean_t root_opened; + + /* How does this diff descend as seen from target? */ + svn_depth_t depth; + + /* Should this diff ignore node ancestry? */ + svn_boolean_t ignore_ancestry; + + /* Possibly diff repos against text-bases instead of working files. */ + svn_boolean_t diff_pristine; + + /* Hash whose keys are const char * changelist names. */ + apr_hash_t *changelist_hash; + + /* Cancel function/baton */ + svn_cancel_func_t cancel_func; + void *cancel_baton; + + apr_pool_t *pool; +}; + +/* Directory level baton. + */ +struct dir_baton_t +{ + /* Reference to parent directory baton (or NULL for the root) */ + struct dir_baton_t *parent_baton; + + /* The depth at which this directory should be diffed. */ + svn_depth_t depth; + + /* The name and path of this directory as if they would be/are in the + local working copy. */ + const char *name; + const char *relpath; + const char *local_abspath; + + /* TRUE if the file is added by the editor drive. */ + svn_boolean_t added; + /* TRUE if the node exists only on the repository side (op_depth 0 or added) */ + svn_boolean_t repos_only; + /* TRUE if the node is to be compared with an unrelated node*/ + svn_boolean_t ignoring_ancestry; + + /* Processor state */ + void *pdb; + svn_boolean_t skip; + svn_boolean_t skip_children; + + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + + apr_hash_t *local_info; + + /* A hash containing the basenames of the nodes reported deleted by the + repository (or NULL for no values). */ + apr_hash_t *deletes; + + /* Identifies those directory elements that get compared while running + the crawler. These elements should not be compared again when + recursively looking for local modifications. + + This hash maps the basename of the node to an unimportant value. + + If the directory's properties have been compared, an item with hash + key of "" will be present in the hash. */ + apr_hash_t *compared; + + /* The list of incoming BASE->repos propchanges. */ + apr_array_header_t *propchanges; + + /* Has a change on regular properties */ + svn_boolean_t has_propchange; + + /* The overall crawler editor baton. */ + struct edit_baton_t *eb; + + apr_pool_t *pool; + int users; +}; + +/* File level baton. + */ +struct file_baton_t +{ + struct dir_baton_t *parent_baton; + + /* The name and path of this file as if they would be/are in the + parent directory, diff session and local working copy. */ + const char *name; + const char *relpath; + const char *local_abspath; + + /* Processor state */ + void *pfb; + svn_boolean_t skip; + + /* TRUE if the file is added by the editor drive. */ + svn_boolean_t added; + /* TRUE if the node exists only on the repository side (op_depth 0 or added) */ + svn_boolean_t repos_only; + /* TRUE if the node is to be compared with an unrelated node*/ + svn_boolean_t ignoring_ancestry; + + const svn_diff_source_t *left_src; + const svn_diff_source_t *right_src; + + /* The list of incoming BASE->repos propchanges. */ + apr_array_header_t *propchanges; + + /* Has a change on regular properties */ + svn_boolean_t has_propchange; + + /* The current BASE checksum and props */ + const svn_checksum_t *base_checksum; + apr_hash_t *base_props; + + /* The resulting from apply_textdelta */ + const char *temp_file_path; + unsigned char result_digest[APR_MD5_DIGESTSIZE]; + + /* The overall crawler editor baton. */ + struct edit_baton_t *eb; + + apr_pool_t *pool; +}; + +/* Create a new edit baton. TARGET_PATH/ANCHOR are working copy paths + * that describe the root of the comparison. CALLBACKS/CALLBACK_BATON + * define the callbacks to compare files. DEPTH defines if and how to + * descend into subdirectories; see public doc string for exactly how. + * IGNORE_ANCESTRY defines whether to utilize node ancestry when + * calculating diffs. USE_TEXT_BASE defines whether to compare + * against working files or text-bases. REVERSE_ORDER defines which + * direction to perform the diff. + * + * CHANGELIST_FILTER is a list of const char * changelist names, used to + * filter diff output responses to only those items in one of the + * specified changelists, empty (or NULL altogether) if no changelist + * filtering is requested. + */ +static svn_error_t * +make_edit_baton(struct edit_baton_t **edit_baton, + svn_wc__db_t *db, + const char *anchor_abspath, + const char *target, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t show_copies_as_adds, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + apr_hash_t *changelist_hash = NULL; + struct edit_baton_t *eb; + const svn_diff_tree_processor_t *processor; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); + + if (changelist_filter && changelist_filter->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + pool)); + + SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, + callbacks, callback_baton, TRUE, + pool, pool)); + + if (reverse_order) + processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); + + /* --show-copies-as-adds implies --notice-ancestry */ + if (show_copies_as_adds) + ignore_ancestry = FALSE; + + if (! show_copies_as_adds) + processor = svn_diff__tree_processor_copy_as_changed_create(processor, + pool); + + eb = apr_pcalloc(pool, sizeof(*eb)); + eb->db = db; + eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath); + eb->target = apr_pstrdup(pool, target); + eb->processor = processor; + eb->depth = depth; + eb->ignore_ancestry = ignore_ancestry; + eb->local_before_remote = reverse_order; + eb->diff_pristine = use_text_base; + eb->changelist_hash = changelist_hash; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + eb->pool = pool; + + *edit_baton = eb; + return SVN_NO_ERROR; +} + +/* Create a new directory baton. PATH is the directory path, + * including anchor_path. ADDED is set if this directory is being + * added rather than replaced. PARENT_BATON is the baton of the + * parent directory, it will be null if this is the root of the + * comparison hierarchy. The directory and its parent may or may not + * exist in the working copy. EDIT_BATON is the overall crawler + * editor baton. + */ +static struct dir_baton_t * +make_dir_baton(const char *path, + struct dir_baton_t *parent_baton, + struct edit_baton_t *eb, + svn_boolean_t added, + svn_depth_t depth, + apr_pool_t *result_pool) +{ + apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool + : eb->pool); + struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); + + db->parent_baton = parent_baton; + + /* Allocate 1 string for using as 3 strings */ + db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool); + db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath); + db->name = svn_dirent_basename(db->relpath, NULL); + + db->eb = eb; + db->added = added; + db->depth = depth; + db->pool = dir_pool; + db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t)); + db->compared = apr_hash_make(dir_pool); + + if (parent_baton != NULL) + { + parent_baton->users++; + } + + db->users = 1; + + return db; +} + +/* Create a new file baton. PATH is the file path, including + * anchor_path. ADDED is set if this file is being added rather than + * replaced. PARENT_BATON is the baton of the parent directory. + * The directory and its parent may or may not exist in the working copy. + */ +static struct file_baton_t * +make_file_baton(const char *path, + svn_boolean_t added, + struct dir_baton_t *parent_baton, + apr_pool_t *result_pool) +{ + apr_pool_t *file_pool = svn_pool_create(result_pool); + struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); + struct edit_baton_t *eb = parent_baton->eb; + + fb->eb = eb; + fb->parent_baton = parent_baton; + fb->parent_baton->users++; + + /* Allocate 1 string for using as 3 strings */ + fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool); + fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath); + fb->name = svn_dirent_basename(fb->relpath, NULL); + + fb->added = added; + fb->pool = file_pool; + fb->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t)); + + return fb; +} + +/* Destroy DB when there are no more registered users */ +static svn_error_t * +maybe_done(struct dir_baton_t *db) +{ + db->users--; + + if (!db->users) + { + struct dir_baton_t *pb = db->parent_baton; + + svn_pool_clear(db->pool); + + if (pb != NULL) + SVN_ERR(maybe_done(pb)); + } + + return SVN_NO_ERROR; +} + +/* Standard check to see if a node is represented in the local working copy */ +#define NOT_PRESENT(status) \ + ((status) == svn_wc__db_status_not_present \ + || (status) == svn_wc__db_status_excluded \ + || (status) == svn_wc__db_status_server_excluded) + +svn_error_t * +svn_wc__diff_base_working_diff(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *changelist_hash, + const svn_diff_tree_processor_t *processor, + void *processor_dir_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_wc__db_status_t status; + svn_revnum_t db_revision; + svn_boolean_t had_props; + svn_boolean_t props_mod; + svn_boolean_t files_same = FALSE; + svn_wc__db_status_t base_status; + const svn_checksum_t *working_checksum; + const svn_checksum_t *checksum; + svn_filesize_t recorded_size; + apr_time_t recorded_time; + const char *pristine_file; + const char *local_file; + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + apr_hash_t *base_props; + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + const char *changelist; + + SVN_ERR(svn_wc__db_read_info(&status, NULL, &db_revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &working_checksum, NULL, + NULL, NULL, NULL, NULL, NULL, &recorded_size, + &recorded_time, &changelist, NULL, NULL, + &had_props, &props_mod, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + checksum = working_checksum; + + assert(status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || (status == svn_wc__db_status_deleted && diff_pristine)); + + /* If the item is not a member of a specified changelist (and there are + some specified changelists), skip it. */ + if (changelist_hash && !svn_hash_gets(changelist_hash, changelist)) + return SVN_NO_ERROR; + + + if (status != svn_wc__db_status_normal) + { + SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db_revision, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &checksum, NULL, NULL, &had_props, + NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + recorded_size = SVN_INVALID_FILESIZE; + recorded_time = 0; + props_mod = TRUE; /* Requires compare */ + } + else if (diff_pristine) + files_same = TRUE; + else + { + const svn_io_dirent2_t *dirent; + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, + FALSE /* verify truename */, + TRUE /* ingore_enoent */, + scratch_pool, scratch_pool)); + + if (dirent->kind == svn_node_file + && dirent->filesize == recorded_size + && dirent->mtime == recorded_time) + { + files_same = TRUE; + } + } + + if (files_same && !props_mod) + return SVN_NO_ERROR; /* Cheap exit */ + + assert(checksum); + + if (!SVN_IS_VALID_REVNUM(revision)) + revision = db_revision; + + left_src = svn_diff__source_create(revision, scratch_pool); + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + + SVN_ERR(processor->file_opened(&file_baton, &skip, relpath, + left_src, + right_src, + NULL /* copyfrom_src */, + processor_dir_baton, + processor, + scratch_pool, scratch_pool)); + + if (skip) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, + db, local_abspath, checksum, + scratch_pool, scratch_pool)); + + if (diff_pristine) + SVN_ERR(svn_wc__db_pristine_get_path(&local_file, + db, local_abspath, + working_checksum, + scratch_pool, scratch_pool)); + else if (! (had_props || props_mod)) + local_file = local_abspath; + else if (files_same) + local_file = pristine_file; + else + SVN_ERR(svn_wc__internal_translated_file( + &local_file, local_abspath, + db, local_abspath, + SVN_WC_TRANSLATE_TO_NF + | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + if (! files_same) + SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file, + pristine_file, scratch_pool)); + + if (had_props) + SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + base_props = apr_hash_make(scratch_pool); + + if (status == svn_wc__db_status_normal && (diff_pristine || !props_mod)) + local_props = base_props; + else if (diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool)); + + if (prop_changes->nelts || !files_same) + { + SVN_ERR(processor->file_changed(relpath, + left_src, + right_src, + pristine_file, + local_file, + base_props, + local_props, + ! files_same, + prop_changes, + file_baton, + processor, + scratch_pool)); + } + else + { + SVN_ERR(processor->file_closed(relpath, + left_src, + right_src, + file_baton, + processor, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +ensure_local_info(struct dir_baton_t *db, + apr_pool_t *scratch_pool) +{ + apr_hash_t *conflicts; + + if (db->local_info) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts, + db->eb->db, db->local_abspath, + db->pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Called when the directory is closed to compare any elements that have + * not yet been compared. This identifies local, working copy only + * changes. At this stage we are dealing with files/directories that do + * exist in the working copy. + * + * DIR_BATON is the baton for the directory. + */ +static svn_error_t * +walk_local_nodes_diff(struct edit_baton_t *eb, + const char *local_abspath, + const char *path, + svn_depth_t depth, + apr_hash_t *compared, + void *parent_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = eb->db; + svn_boolean_t in_anchor_not_target; + apr_pool_t *iterpool; + void *dir_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_revnum_t revision; + svn_boolean_t props_mod; + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + + /* Everything we do below is useless if we are comparing to BASE. */ + if (eb->diff_pristine) + return SVN_NO_ERROR; + + /* Determine if this is the anchor directory if the anchor is different + to the target. When the target is a file, the anchor is the parent + directory and if this is that directory the non-target entries must be + skipped. */ + in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0')); + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &props_mod, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + + left_src = svn_diff__source_create(revision, scratch_pool); + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + + if (compared) + { + dir_baton = parent_baton; + skip = TRUE; + } + else if (!in_anchor_not_target) + SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children, + path, + left_src, + right_src, + NULL /* copyfrom_src */, + parent_baton, + eb->processor, + scratch_pool, scratch_pool)); + + + if (!skip_children && depth != svn_depth_empty) + { + apr_hash_t *nodes; + apr_hash_t *conflicts; + apr_array_header_t *children; + svn_depth_t depth_below_here = depth; + svn_boolean_t diff_files; + svn_boolean_t diff_dirs; + int i; + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + diff_files = (depth == svn_depth_unknown + || depth >= svn_depth_files); + diff_dirs = (depth == svn_depth_unknown + || depth >= svn_depth_immediates); + + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, + db, local_abspath, + scratch_pool, iterpool)); + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_info_t *info = item->value; + + const char *child_abspath; + const char *child_relpath; + svn_boolean_t repos_only; + svn_boolean_t local_only; + svn_node_kind_t base_kind; + + if (eb->cancel_func) + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + /* In the anchor directory, if the anchor is not the target then all + entries other than the target should not be diff'd. Running diff + on one file in a directory should not diff other files in that + directory. */ + if (in_anchor_not_target && strcmp(eb->target, name)) + continue; + + if (compared && svn_hash_gets(compared, name)) + continue; + + if (NOT_PRESENT(info->status)) + continue; + + assert(info->status == svn_wc__db_status_normal + || info->status == svn_wc__db_status_added + || info->status == svn_wc__db_status_deleted); + + svn_pool_clear(iterpool); + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + child_relpath = svn_relpath_join(path, name, iterpool); + + repos_only = FALSE; + local_only = FALSE; + + if (!info->have_base) + { + local_only = TRUE; /* Only report additions */ + } + else if (info->status == svn_wc__db_status_normal) + { + /* Simple diff */ + base_kind = info->kind; + } + else if (info->status == svn_wc__db_status_deleted + && (!eb->diff_pristine || !info->have_more_work)) + { + svn_wc__db_status_t base_status; + repos_only = TRUE; + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, child_abspath, + iterpool, iterpool)); + + if (NOT_PRESENT(base_status)) + continue; + } + else + { + /* working status is either added or deleted */ + svn_wc__db_status_t base_status; + + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, child_abspath, + iterpool, iterpool)); + + if (NOT_PRESENT(base_status)) + local_only = TRUE; + else if (base_kind != info->kind || !eb->ignore_ancestry) + { + repos_only = TRUE; + local_only = TRUE; + } + } + + if (eb->local_before_remote && local_only) + { + if (info->kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, + depth_below_here, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } + + if (repos_only) + { + /* Report repository form deleted */ + if (base_kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, eb->revnum, + eb->processor, dir_baton, + iterpool)); + else if (base_kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, eb->revnum, + depth_below_here, + eb->processor, dir_baton, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } + else if (!local_only) /* Not local only nor remote only */ + { + /* Diff base against actual */ + if (info->kind == svn_node_file && diff_files) + { + if (info->status != svn_wc__db_status_normal + || !eb->diff_pristine) + { + SVN_ERR(svn_wc__diff_base_working_diff( + db, child_abspath, + child_relpath, + eb->revnum, + eb->changelist_hash, + eb->processor, dir_baton, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + } + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(walk_local_nodes_diff(eb, child_abspath, + child_relpath, + depth_below_here, + NULL /* compared */, + dir_baton, + scratch_pool)); + } + + if (!eb->local_before_remote && local_only) + { + if (info->kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } + } + } + + if (compared) + return SVN_NO_ERROR; + + /* Check for local property mods on this directory, if we haven't + already reported them and we aren't changelist-filted. + ### it should be noted that we do not currently allow directories + ### to be part of changelists, so if a changelist is provided, the + ### changelist check will always fail. */ + if (! skip + && ! eb->changelist_hash + && ! in_anchor_not_target + && props_mod) + { + apr_array_header_t *propchanges; + apr_hash_t *left_props; + apr_hash_t *right_props; + + SVN_ERR(svn_wc__internal_propdiff(&propchanges, &left_props, + db, local_abspath, + scratch_pool, scratch_pool)); + + right_props = svn_prop__patch(left_props, propchanges, scratch_pool); + + SVN_ERR(eb->processor->dir_changed(path, + left_src, + right_src, + left_props, + right_props, + propchanges, + dir_baton, + eb->processor, + scratch_pool)); + } + else if (! skip) + SVN_ERR(eb->processor->dir_closed(path, + left_src, + right_src, + dir_baton, + eb->processor, + scratch_pool)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__diff_local_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_hash_t *changelist_hash, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_diff_source_t *right_src; + svn_diff_source_t *copyfrom_src = NULL; + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + const char *original_repos_relpath; + svn_revnum_t original_revision; + const char *changelist; + svn_boolean_t had_props; + svn_boolean_t props_mod; + apr_hash_t *pristine_props; + apr_hash_t *right_props = NULL; + const char *pristine_file; + const char *translated_file; + svn_revnum_t revision; + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t file_mod = TRUE; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &checksum, NULL, + &original_repos_relpath, NULL, NULL, + &original_revision, NULL, NULL, NULL, + &changelist, NULL, NULL, &had_props, + &props_mod, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + assert(kind == svn_node_file + && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || (status == svn_wc__db_status_deleted && diff_pristine))); + + + if (changelist && changelist_hash + && !svn_hash_gets(changelist_hash, changelist)) + return SVN_NO_ERROR; + + if (status == svn_wc__db_status_deleted) + { + assert(diff_pristine); + + SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, + NULL, &checksum, NULL, &had_props, + &pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); + props_mod = FALSE; + } + else if (!had_props) + pristine_props = apr_hash_make(scratch_pool); + else + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (original_repos_relpath) + { + copyfrom_src = svn_diff__source_create(original_revision, scratch_pool); + copyfrom_src->repos_relpath = original_repos_relpath; + } + + if (props_mod || !SVN_IS_VALID_REVNUM(revision)) + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + else + { + if (diff_pristine) + file_mod = FALSE; + else + SVN_ERR(svn_wc__internal_file_modified_p(&file_mod, db, local_abspath, + FALSE, scratch_pool)); + + if (!file_mod) + right_src = svn_diff__source_create(revision, scratch_pool); + else + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + } + + SVN_ERR(processor->file_opened(&file_baton, &skip, + relpath, + NULL /* left_source */, + right_src, + copyfrom_src, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (skip) + return SVN_NO_ERROR; + + if (props_mod && !diff_pristine) + SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + right_props = svn_prop_hash_dup(pristine_props, scratch_pool); + + if (checksum) + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db, local_abspath, + checksum, scratch_pool, scratch_pool)); + else + pristine_file = NULL; + + if (diff_pristine) + { + translated_file = pristine_file; /* No translation needed */ + } + else + { + SVN_ERR(svn_wc__internal_translated_file( + &translated_file, local_abspath, db, local_abspath, + SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + } + + SVN_ERR(processor->file_added(relpath, + copyfrom_src, + right_src, + copyfrom_src + ? pristine_file + : NULL, + translated_file, + copyfrom_src + ? pristine_props + : NULL, + right_props, + file_baton, + processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__diff_local_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_hash_t *changelist_hash, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *children; + int i; + apr_pool_t *iterpool; + void *pdb = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_diff_source_t *right_src = svn_diff__source_create(SVN_INVALID_REVNUM, + scratch_pool); + svn_depth_t depth_below_here = depth; + apr_hash_t *nodes; + apr_hash_t *conflicts; + + /* Report the addition of the directory's contents. */ + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(processor->dir_opened(&pdb, &skip, &skip_children, + relpath, + NULL, + right_src, + NULL /* copyfrom_src */, + processor_parent_baton, + processor, + scratch_pool, iterpool)); + + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db, local_abspath, + scratch_pool, iterpool)); + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_info_t *info = item->value; + const char *child_abspath; + const char *child_relpath; + + svn_pool_clear(iterpool); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + + if (NOT_PRESENT(info->status)) + { + continue; + } + + /* If comparing against WORKING, skip entries that are + schedule-deleted - they don't really exist. */ + if (!diff_pristine && info->status == svn_wc__db_status_deleted) + continue; + + child_relpath = svn_relpath_join(relpath, name, iterpool); + + switch (info->kind) + { + case svn_node_file: + case svn_node_symlink: + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + processor, pdb, + changelist_hash, + diff_pristine, + cancel_func, cancel_baton, + scratch_pool)); + break; + + case svn_node_dir: + if (depth > svn_depth_files || depth == svn_depth_unknown) + { + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + processor, pdb, + changelist_hash, + diff_pristine, + cancel_func, cancel_baton, + iterpool)); + } + break; + + default: + break; + } + } + + if (!skip) + { + apr_hash_t *right_props; + if (diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__get_actual_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->dir_added(relpath, + NULL /* copyfrom_src */, + right_src, + NULL, + right_props, + pdb, + processor, + iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Reports local changes. */ +static svn_error_t * +handle_local_only(struct dir_baton_t *pb, + const char *name, + apr_pool_t *scratch_pool) +{ + struct edit_baton_t *eb = pb->eb; + const struct svn_wc__db_info_t *info; + svn_boolean_t repos_delete = (pb->deletes + && svn_hash_gets(pb->deletes, name)); + + assert(!strchr(name, '/')); + assert(!pb->added || eb->ignore_ancestry); + + if (pb->skip_children) + return SVN_NO_ERROR; + + SVN_ERR(ensure_local_info(pb, scratch_pool)); + + info = svn_hash_gets(pb->local_info, name); + + if (info == NULL || NOT_PRESENT(info->status)) + return SVN_NO_ERROR; + + switch (info->status) + { + case svn_wc__db_status_incomplete: + return SVN_NO_ERROR; /* Not local only */ + + case svn_wc__db_status_normal: + if (!repos_delete) + return SVN_NO_ERROR; /* Local and remote */ + svn_hash_sets(pb->deletes, name, NULL); + break; + + case svn_wc__db_status_deleted: + if (!(eb->diff_pristine && repos_delete)) + return SVN_NO_ERROR; + break; + + case svn_wc__db_status_added: + default: + break; + } + + if (info->kind == svn_node_dir) + { + svn_depth_t depth ; + + if (pb->depth == svn_depth_infinity || pb->depth == svn_depth_unknown) + depth = pb->depth; + else + depth = svn_depth_empty; + + SVN_ERR(svn_wc__diff_local_only_dir( + eb->db, + svn_dirent_join(pb->local_abspath, name, scratch_pool), + svn_relpath_join(pb->relpath, name, scratch_pool), + repos_delete ? svn_depth_infinity : depth, + eb->processor, pb->pdb, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + } + else + SVN_ERR(svn_wc__diff_local_only_file( + eb->db, + svn_dirent_join(pb->local_abspath, name, scratch_pool), + svn_relpath_join(pb->relpath, name, scratch_pool), + eb->processor, pb->pdb, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Reports a file LOCAL_ABSPATH in BASE as deleted */ +svn_error_t * +svn_wc__diff_base_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + apr_hash_t *props; + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_diff_source_t *left_src; + const char *pristine_file; + + SVN_ERR(svn_wc__db_base_get_info(&status, &kind, + SVN_IS_VALID_REVNUM(revision) + ? NULL : &revision, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, &props, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(status == svn_wc__db_status_normal + && kind == svn_node_file + && checksum); + + left_src = svn_diff__source_create(revision, scratch_pool); + + SVN_ERR(processor->file_opened(&file_baton, &skip, + relpath, + left_src, + NULL /* right_src */, + NULL /* copyfrom_source */, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (skip) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, + db, local_abspath, checksum, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->file_deleted(relpath, + left_src, + pristine_file, + props, + file_baton, + processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__diff_base_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + void *dir_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_diff_source_t *left_src; + svn_revnum_t report_rev = revision; + + if (!SVN_IS_VALID_REVNUM(report_rev)) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &report_rev, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + left_src = svn_diff__source_create(report_rev, scratch_pool); + + SVN_ERR(processor->dir_opened(&dir_baton, &skip, &skip_children, + relpath, + left_src, + NULL /* right_src */, + NULL /* copyfrom_src */, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (!skip_children && (depth == svn_depth_unknown || depth > svn_depth_empty)) + { + apr_hash_t *nodes; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *children; + int i; + + SVN_ERR(svn_wc__db_base_get_children_info(&nodes, db, local_abspath, + scratch_pool, iterpool)); + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_base_info_t *info = item->value; + const char *child_abspath; + const char *child_relpath; + + if (info->status != svn_wc__db_status_normal) + continue; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + child_relpath = svn_relpath_join(relpath, name, iterpool); + + switch (info->kind) + { + case svn_node_file: + case svn_node_symlink: + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, + revision, + processor, dir_baton, + iterpool)); + break; + case svn_node_dir: + if (depth > svn_depth_files || depth == svn_depth_unknown) + { + svn_depth_t depth_below_here = depth; + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, + revision, + depth_below_here, + processor, dir_baton, + cancel_func, + cancel_baton, + iterpool)); + } + break; + + default: + break; + } + } + } + + if (!skip) + { + apr_hash_t *props; + SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->dir_deleted(relpath, + left_src, + props, + dir_baton, + processor, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton_t *eb = edit_baton; + eb->revnum = target_revision; + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. The root of the comparison hierarchy */ +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *dir_pool, + void **root_baton) +{ + struct edit_baton_t *eb = edit_baton; + struct dir_baton_t *db; + + eb->root_opened = TRUE; + db = make_dir_baton("", NULL, eb, FALSE, eb->depth, dir_pool); + *root_baton = db; + + if (eb->target[0] == '\0') + { + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, + &db->skip_children, + "", + db->left_src, + db->right_src, + NULL /* copyfrom_source */, + NULL /* parent_baton */, + eb->processor, + db->pool, db->pool)); + } + else + db->skip = TRUE; /* Skip this, but not the children */ + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t base_revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton_t *pb = parent_baton; + const char *name = svn_dirent_basename(path, pb->pool); + + if (!pb->deletes) + pb->deletes = apr_hash_make(pb->pool); + + svn_hash_sets(pb->deletes, name, ""); + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *dir_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; + svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates) + ? svn_depth_empty : pb->depth; + + db = make_dir_baton(path, pb, pb->eb, TRUE, subdir_depth, + dir_pool); + *child_baton = db; + + if (pb->repos_only || !eb->ignore_ancestry) + db->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, dir_pool)); + + info = svn_hash_gets(pb->local_info, db->name); + + if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status)) + db->repos_only = TRUE; + + if (!db->repos_only && info->status != svn_wc__db_status_added) + db->repos_only = TRUE; + + if (!db->repos_only) + { + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + db->ignoring_ancestry = TRUE; + + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), ""); + } + } + + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + + if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, dir_pool)); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children, + db->relpath, + db->left_src, + db->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + db->pool, db->pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *dir_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; + svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates) + ? svn_depth_empty : pb->depth; + + /* Allocate path from the parent pool since the memory is used in the + parent's compared hash */ + db = make_dir_baton(path, pb, pb->eb, FALSE, subdir_depth, dir_pool); + *child_baton = db; + + if (pb->repos_only) + db->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, dir_pool)); + + info = svn_hash_gets(pb->local_info, db->name); + + if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status)) + db->repos_only = TRUE; + + if (!db->repos_only) + switch (info->status) + { + case svn_wc__db_status_normal: + break; + case svn_wc__db_status_deleted: + db->repos_only = TRUE; + + if (!info->have_more_work) + svn_hash_sets(pb->compared, + apr_pstrdup(pb->pool, db->name), ""); + break; + case svn_wc__db_status_added: + if (eb->ignore_ancestry) + db->ignoring_ancestry = TRUE; + else + db->repos_only = TRUE; + break; + default: + SVN_ERR_MALFUNCTION(); + } + + if (!db->repos_only) + { + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), ""); + } + } + + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + + if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, dir_pool)); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children, + db->relpath, + db->left_src, + db->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + db->pool, db->pool)); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. When a directory is closed, all the + * directory elements that have been added or replaced will already have been + * diff'd. However there may be other elements in the working copy + * that have not yet been considered. */ +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton_t *db = dir_baton; + struct dir_baton_t *pb = db->parent_baton; + struct edit_baton_t *eb = db->eb; + apr_pool_t *scratch_pool = db->pool; + svn_boolean_t reported_closed = FALSE; + + if (!db->skip_children && db->deletes && apr_hash_count(db->deletes)) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *children; + int i; + children = svn_sort__hash(db->deletes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + + svn_pool_clear(iterpool); + SVN_ERR(handle_local_only(db, name, iterpool)); + + svn_hash_sets(db->compared, name, ""); + } + + svn_pool_destroy(iterpool); + } + + /* Report local modifications for this directory. Skip added + directories since they can only contain added elements, all of + which have already been diff'd. */ + if (!db->repos_only && !db->skip_children) + { + SVN_ERR(walk_local_nodes_diff(eb, + db->local_abspath, + db->relpath, + db->depth, + db->compared, + db->pdb, + scratch_pool)); + } + + /* Report the property changes on the directory itself, if necessary. */ + if (db->skip) + { + /* Diff processor requested no directory details */ + } + else if (db->propchanges->nelts > 0 || db->repos_only) + { + apr_hash_t *repos_props; + + if (db->added) + { + repos_props = apr_hash_make(scratch_pool); + } + else + { + SVN_ERR(svn_wc__db_base_get_props(&repos_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + + /* Add received property changes and entry props */ + if (db->propchanges->nelts) + repos_props = svn_prop__patch(repos_props, db->propchanges, + scratch_pool); + + if (db->repos_only) + { + SVN_ERR(eb->processor->dir_deleted(db->relpath, + db->left_src, + repos_props, + db->pdb, + eb->processor, + scratch_pool)); + reported_closed = TRUE; + } + else + { + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + + if (eb->diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + &local_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_props(&local_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props, + scratch_pool)); + + /* ### as a good diff processor we should now only report changes + if there are non-entry changes, but for now we stick to + compatibility */ + + if (prop_changes->nelts) + { + SVN_ERR(eb->processor->dir_changed(db->relpath, + db->left_src, + db->right_src, + repos_props, + local_props, + prop_changes, + db->pdb, + eb->processor, + scratch_pool)); + reported_closed = TRUE; + } + } + } + + /* Mark this directory as compared in the parent directory's baton, + unless this is the root of the comparison. */ + if (!reported_closed && !db->skip) + SVN_ERR(eb->processor->dir_closed(db->relpath, + db->left_src, + db->right_src, + db->pdb, + eb->processor, + scratch_pool)); + + if (pb && !eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, scratch_pool)); + + SVN_ERR(maybe_done(db)); /* destroys scratch_pool */ + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb; + + fb = make_file_baton(path, TRUE, pb, file_pool); + *file_baton = fb; + + if (pb->skip_children) + { + fb->skip = TRUE; + return SVN_NO_ERROR; + } + else if (pb->repos_only || !eb->ignore_ancestry) + fb->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, file_pool)); + + info = svn_hash_gets(pb->local_info, fb->name); + + if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status)) + fb->repos_only = TRUE; + + if (!fb->repos_only && info->status != svn_wc__db_status_added) + fb->repos_only = TRUE; + + if (!fb->repos_only) + { + /* Add this path to the parent directory's list of elements that + have been compared. */ + fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool); + fb->ignoring_ancestry = TRUE; + + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), ""); + } + } + + fb->left_src = svn_diff__source_create(eb->revnum, fb->pool); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip, + fb->relpath, + fb->left_src, + fb->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + fb->pool, fb->pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb; + + fb = make_file_baton(path, FALSE, pb, file_pool); + *file_baton = fb; + + if (pb->skip_children) + fb->skip = TRUE; + else if (pb->repos_only) + fb->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, file_pool)); + + info = svn_hash_gets(pb->local_info, fb->name); + + if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status)) + fb->repos_only = TRUE; + + if (!fb->repos_only) + switch (info->status) + { + case svn_wc__db_status_normal: + break; + case svn_wc__db_status_deleted: + fb->repos_only = TRUE; + if (!info->have_more_work) + svn_hash_sets(pb->compared, + apr_pstrdup(pb->pool, fb->name), ""); + break; + case svn_wc__db_status_added: + if (eb->ignore_ancestry) + fb->ignoring_ancestry = TRUE; + else + fb->repos_only = TRUE; + break; + default: + SVN_ERR_MALFUNCTION(); + } + + if (!fb->repos_only) + { + /* Add this path to the parent directory's list of elements that + have been compared. */ + fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool); + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), ""); + } + } + + fb->left_src = svn_diff__source_create(eb->revnum, fb->pool); + + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &fb->base_checksum, NULL, + NULL, NULL, &fb->base_props, NULL, + eb->db, fb->local_abspath, + fb->pool, fb->pool)); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip, + fb->relpath, + fb->left_src, + fb->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + fb->pool, fb->pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum_hex, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton_t *fb = file_baton; + struct edit_baton_t *eb = fb->eb; + svn_stream_t *source; + svn_stream_t *temp_stream; + svn_checksum_t *repos_checksum = NULL; + + if (fb->skip) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; + } + + if (base_checksum_hex && fb->base_checksum) + { + const svn_checksum_t *base_md5; + SVN_ERR(svn_checksum_parse_hex(&repos_checksum, svn_checksum_md5, + base_checksum_hex, pool)); + + SVN_ERR(svn_wc__db_pristine_get_md5(&base_md5, + eb->db, eb->anchor_abspath, + fb->base_checksum, + pool, pool)); + + if (! svn_checksum_match(repos_checksum, base_md5)) + { + /* ### I expect that there are some bad drivers out there + ### that used to give bad results. We could look in + ### working to see if the expected checksum matches and + ### then return the pristine of that... But that only moves + ### the problem */ + + /* If needed: compare checksum obtained via md5 of working. + And if they match set fb->base_checksum and fb->base_props */ + + return svn_checksum_mismatch_err( + base_md5, + repos_checksum, + pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style(fb->local_abspath, + pool)); + } + + SVN_ERR(svn_wc__db_pristine_read(&source, NULL, + eb->db, fb->local_abspath, + fb->base_checksum, + pool, pool)); + } + else if (fb->base_checksum) + { + SVN_ERR(svn_wc__db_pristine_read(&source, NULL, + eb->db, fb->local_abspath, + fb->base_checksum, + pool, pool)); + } + else + source = svn_stream_empty(pool); + + /* This is the file that will contain the pristine repository version. */ + SVN_ERR(svn_stream_open_unique(&temp_stream, &fb->temp_file_path, NULL, + svn_io_file_del_on_pool_cleanup, + fb->pool, fb->pool)); + + svn_txdelta_apply(source, temp_stream, + fb->result_digest, + fb->local_abspath /* error_info */, + fb->pool, + handler, handler_baton); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. When the file is closed we have a temporary + * file containing a pristine version of the repository file. This can + * be compared against the working copy. + * + * Ignore TEXT_CHECKSUM. + */ +static svn_error_t * +close_file(void *file_baton, + const char *expected_md5_digest, + apr_pool_t *pool) +{ + struct file_baton_t *fb = file_baton; + struct dir_baton_t *pb = fb->parent_baton; + struct edit_baton_t *eb = fb->eb; + apr_pool_t *scratch_pool = fb->pool; + + /* The repository information; constructed from BASE + Changes */ + const char *repos_file; + apr_hash_t *repos_props; + + if (!fb->skip && expected_md5_digest != NULL) + { + svn_checksum_t *expected_checksum; + const svn_checksum_t *result_checksum; + + if (fb->temp_file_path) + result_checksum = svn_checksum__from_digest_md5(fb->result_digest, + scratch_pool); + else + result_checksum = fb->base_checksum; + + SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, + expected_md5_digest, scratch_pool)); + + if (result_checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&result_checksum, + eb->db, fb->local_abspath, + result_checksum, + scratch_pool, scratch_pool)); + + if (!svn_checksum_match(expected_checksum, result_checksum)) + return svn_checksum_mismatch_err( + expected_checksum, + result_checksum, + pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style(fb->local_abspath, + scratch_pool)); + } + + if (eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, fb->name, scratch_pool)); + + { + apr_hash_t *prop_base; + + if (fb->added) + prop_base = apr_hash_make(scratch_pool); + else + prop_base = fb->base_props; + + /* includes entry props */ + repos_props = svn_prop__patch(prop_base, fb->propchanges, scratch_pool); + + repos_file = fb->temp_file_path; + if (! repos_file) + { + assert(fb->base_checksum); + SVN_ERR(svn_wc__db_pristine_get_path(&repos_file, + eb->db, eb->anchor_abspath, + fb->base_checksum, + scratch_pool, scratch_pool)); + } + } + + if (fb->skip) + { + /* Diff processor requested skipping information */ + } + else if (fb->repos_only) + { + SVN_ERR(eb->processor->file_deleted(fb->relpath, + fb->left_src, + fb->temp_file_path, + repos_props, + fb->pfb, + eb->processor, + scratch_pool)); + } + else + { + /* Produce a diff of actual or pristine against repos */ + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + const char *localfile; + + /* pb->local_info contains some information that might allow optimizing + this a bit */ + + if (eb->diff_pristine) + { + const svn_checksum_t *checksum; + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, + NULL, &checksum, NULL, NULL, + &local_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + assert(checksum); + SVN_ERR(svn_wc__db_pristine_get_path(&localfile, + eb->db, eb->anchor_abspath, + checksum, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_read_props(&local_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + + /* a detranslated version of the working file */ + SVN_ERR(svn_wc__internal_translated_file( + &localfile, fb->local_abspath, eb->db, fb->local_abspath, + SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + eb->cancel_func, eb->cancel_baton, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props, + scratch_pool)); + + + /* ### as a good diff processor we should now only report changes, and + report file_closed() in other cases */ + SVN_ERR(eb->processor->file_changed(fb->relpath, + fb->left_src, + fb->right_src, + repos_file /* left file */, + localfile /* right file */, + repos_props /* left_props */, + local_props /* right props */, + TRUE /* ### file_modified */, + prop_changes, + fb->pfb, + eb->processor, + scratch_pool)); + } + + if (!eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, fb->name, scratch_pool)); + + svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */ + SVN_ERR(maybe_done(pb)); + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct file_baton_t *fb = file_baton; + svn_prop_t *propchange; + svn_prop_kind_t propkind; + + propkind = svn_property_kind2(name); + if (propkind == svn_prop_wc_kind) + return SVN_NO_ERROR; + else if (propkind == svn_prop_regular_kind) + fb->has_propchange = TRUE; + + propchange = apr_array_push(fb->propchanges); + propchange->name = apr_pstrdup(fb->pool, name); + propchange->value = value ? svn_string_dup(value, fb->pool) : NULL; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton_t *db = dir_baton; + svn_prop_t *propchange; + svn_prop_kind_t propkind; + + propkind = svn_property_kind2(name); + if (propkind == svn_prop_wc_kind) + return SVN_NO_ERROR; + else if (propkind == svn_prop_regular_kind) + db->has_propchange = TRUE; + + propchange = apr_array_push(db->propchanges); + propchange->name = apr_pstrdup(db->pool, name); + propchange->value = value ? svn_string_dup(value, db->pool) : NULL; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton_t *eb = edit_baton; + + if (!eb->root_opened) + { + SVN_ERR(walk_local_nodes_diff(eb, + eb->anchor_abspath, + "", + eb->depth, + NULL /* compared */, + NULL /* No parent_baton */, + eb->pool)); + } + + return SVN_NO_ERROR; +} + +/* Public Interface */ + + +/* Create a diff editor and baton. */ +svn_error_t * +svn_wc__get_diff_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t show_copies_as_adds, + svn_boolean_t use_git_diff_format, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_boolean_t server_performs_filtering, + const apr_array_header_t *changelist_filter, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton_t *eb; + void *inner_baton; + svn_delta_editor_t *tree_editor; + const svn_delta_editor_t *inner_editor; + struct svn_wc__shim_fetch_baton_t *sfb; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(result_pool); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); + + /* --git implies --show-copies-as-adds */ + if (use_git_diff_format) + show_copies_as_adds = TRUE; + + SVN_ERR(make_edit_baton(&eb, + wc_ctx->db, + anchor_abspath, target, + callbacks, callback_baton, + depth, ignore_ancestry, show_copies_as_adds, + use_text_base, reverse_order, changelist_filter, + cancel_func, cancel_baton, + result_pool)); + + tree_editor = svn_delta_default_editor(eb->pool); + + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->close_directory = close_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_file = close_file; + tree_editor->close_edit = close_edit; + + inner_editor = tree_editor; + inner_baton = eb; + + if (!server_performs_filtering + && depth == svn_depth_unknown) + SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor, + &inner_baton, + wc_ctx->db, + anchor_abspath, + target, + inner_editor, + inner_baton, + result_pool)); + + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, + cancel_baton, + inner_editor, + inner_baton, + editor, + edit_baton, + result_pool)); + + sfb = apr_palloc(result_pool, sizeof(*sfb)); + sfb->db = wc_ctx->db; + sfb->base_abspath = eb->anchor_abspath; + sfb->fetch_base = TRUE; + + shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func; + shim_callbacks->fetch_props_func = svn_wc__fetch_props_func; + shim_callbacks->fetch_base_func = svn_wc__fetch_base_func; + shim_callbacks->fetch_baton = sfb; + + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Wrapping svn_wc_diff_callbacks4_t as svn_diff_tree_processor_t */ + +/* baton for the svn_diff_tree_processor_t wrapper */ +typedef struct wc_diff_wrap_baton_t +{ + const svn_wc_diff_callbacks4_t *callbacks; + void *callback_baton; + + svn_boolean_t walk_deleted_dirs; + + apr_pool_t *result_pool; + const char *empty_file; + +} wc_diff_wrap_baton_t; + +static svn_error_t * +wrap_ensure_empty_file(wc_diff_wrap_baton_t *wb, + apr_pool_t *scratch_pool) +{ + if (wb->empty_file) + return SVN_NO_ERROR; + + /* Create a unique file in the tempdir */ + SVN_ERR(svn_io_open_unique_file3(NULL, &wb->empty_file, NULL, + svn_io_file_del_on_pool_cleanup, + wb->result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_opened(void **new_dir_baton, + svn_boolean_t *skip, + svn_boolean_t *skip_children, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *parent_dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + + assert(left_source || right_source); + assert(!copyfrom_source || !right_source); + + /* Maybe store state and tree_conflicted in baton? */ + if (left_source != NULL) + { + /* Open for change or delete */ + SVN_ERR(wb->callbacks->dir_opened(&tree_conflicted, skip, skip_children, + relpath, + right_source + ? right_source->revision + : (left_source + ? left_source->revision + : SVN_INVALID_REVNUM), + wb->callback_baton, + scratch_pool)); + + if (! right_source && !wb->walk_deleted_dirs) + *skip_children = TRUE; + } + else /* left_source == NULL -> Add */ + { + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + SVN_ERR(wb->callbacks->dir_added(&state, &tree_conflicted, + skip, skip_children, + relpath, + right_source->revision, + copyfrom_source + ? copyfrom_source->repos_relpath + : NULL, + copyfrom_source + ? copyfrom_source->revision + : SVN_INVALID_REVNUM, + wb->callback_baton, + scratch_pool)); + } + + *new_dir_baton = NULL; + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_added(const char *relpath, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_unknown; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; + apr_hash_t *pristine_props = copyfrom_props; + apr_array_header_t *prop_changes = NULL; + + if (right_props && apr_hash_count(right_props)) + { + if (!pristine_props) + pristine_props = apr_hash_make(scratch_pool); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, pristine_props, + scratch_pool)); + + SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, + &tree_conflicted, + relpath, + TRUE /* dir_was_added */, + prop_changes, pristine_props, + wb->callback_baton, + scratch_pool)); + } + + SVN_ERR(wb->callbacks->dir_closed(&state, &prop_state, + &tree_conflicted, + relpath, + TRUE /* dir_was_added */, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_deleted(const char *relpath, + const svn_diff_source_t *left_source, + /*const*/ apr_hash_t *left_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wb->callbacks->dir_deleted(&state, &tree_conflicted, + relpath, + wb->callback_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_closed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + + /* No previous implementations provided these arguments, so we + are not providing them either */ + SVN_ERR(wb->callbacks->dir_closed(NULL, NULL, NULL, + relpath, + FALSE /* added */, + wb->callback_baton, + scratch_pool)); + +return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + const apr_array_header_t *prop_changes, + void *dir_baton, + const struct svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + + assert(left_source && right_source); + + SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, &tree_conflicted, + relpath, + FALSE /* dir_was_added */, + prop_changes, + left_props, + wb->callback_baton, + scratch_pool)); + + /* And call dir_closed, etc */ + SVN_ERR(wrap_dir_closed(relpath, left_source, right_source, + dir_baton, processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_opened(void **new_file_baton, + svn_boolean_t *skip, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + + if (left_source) /* If ! added */ + SVN_ERR(wb->callbacks->file_opened(&tree_conflicted, skip, relpath, + right_source + ? right_source->revision + : (left_source + ? left_source->revision + : SVN_INVALID_REVNUM), + wb->callback_baton, scratch_pool)); + + /* No old implementation used the output arguments for notify */ + + *new_file_baton = NULL; + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_added(const char *relpath, + const svn_diff_source_t *copyfrom_source, + const svn_diff_source_t *right_source, + const char *copyfrom_file, + const char *right_file, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + apr_array_header_t *prop_changes; + + if (! copyfrom_props) + copyfrom_props = apr_hash_make(scratch_pool); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, copyfrom_props, + scratch_pool)); + + if (! copyfrom_source) + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + SVN_ERR(wb->callbacks->file_added(&state, &prop_state, &tree_conflicted, + relpath, + copyfrom_source + ? copyfrom_file + : wb->empty_file, + right_file, + 0, + right_source->revision, + copyfrom_props + ? svn_prop_get_value(copyfrom_props, + SVN_PROP_MIME_TYPE) + : NULL, + right_props + ? svn_prop_get_value(right_props, + SVN_PROP_MIME_TYPE) + : NULL, + copyfrom_source + ? copyfrom_source->repos_relpath + : NULL, + copyfrom_source + ? copyfrom_source->revision + : SVN_INVALID_REVNUM, + prop_changes, copyfrom_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +wrap_file_deleted(const char *relpath, + const svn_diff_source_t *left_source, + const char *left_file, + apr_hash_t *left_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + SVN_ERR(wb->callbacks->file_deleted(&state, &tree_conflicted, + relpath, + left_file, wb->empty_file, + left_props + ? svn_prop_get_value(left_props, + SVN_PROP_MIME_TYPE) + : NULL, + NULL, + left_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const char *left_file, + const char *right_file, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_boolean_t file_modified, + const apr_array_header_t *prop_changes, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + assert(left_source && right_source); + + SVN_ERR(wb->callbacks->file_changed(&state, &prop_state, &tree_conflicted, + relpath, + file_modified ? left_file : NULL, + file_modified ? right_file : NULL, + left_source->revision, + right_source->revision, + left_props + ? svn_prop_get_value(left_props, + SVN_PROP_MIME_TYPE) + : NULL, + right_props + ? svn_prop_get_value(right_props, + SVN_PROP_MIME_TYPE) + : NULL, + prop_changes, + left_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__wrap_diff_callbacks(const svn_diff_tree_processor_t **diff_processor, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_boolean_t walk_deleted_dirs, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wrap_baton; + svn_diff_tree_processor_t *processor; + + wrap_baton = apr_pcalloc(result_pool, sizeof(*wrap_baton)); + + wrap_baton->result_pool = result_pool; + wrap_baton->callbacks = callbacks; + wrap_baton->callback_baton = callback_baton; + wrap_baton->empty_file = NULL; + wrap_baton->walk_deleted_dirs = walk_deleted_dirs; + + processor = svn_diff__tree_processor_create(wrap_baton, result_pool); + + processor->dir_opened = wrap_dir_opened; + processor->dir_added = wrap_dir_added; + processor->dir_deleted = wrap_dir_deleted; + processor->dir_changed = wrap_dir_changed; + processor->dir_closed = wrap_dir_closed; + + processor->file_opened = wrap_file_opened; + processor->file_added = wrap_file_added; + processor->file_deleted = wrap_file_deleted; + processor->file_changed = wrap_file_changed; + /*processor->file_closed = wrap_file_closed*/; /* Not needed */ + + *diff_processor = processor; + return SVN_NO_ERROR; +} |