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