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_client/repos_diff.c | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_client/repos_diff.c')
-rw-r--r-- | subversion/libsvn_client/repos_diff.c | 1405 |
1 files changed, 1405 insertions, 0 deletions
diff --git a/subversion/libsvn_client/repos_diff.c b/subversion/libsvn_client/repos_diff.c new file mode 100644 index 0000000..6a7725f --- /dev/null +++ b/subversion/libsvn_client/repos_diff.c @@ -0,0 +1,1405 @@ +/* + * repos_diff.c -- The diff editor for comparing two repository versions + * + * ==================================================================== + * 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 editor driven by a tree delta between two + * repository revisions (REV1 and REV2). For each file encountered in + * the delta the editor constructs two temporary files, one for each + * revision. This necessitates a separate request for the REV1 version + * of the file when the delta shows the file being modified or + * deleted. Files that are added by the delta do not require a + * separate request, the REV1 version is empty and the delta is + * sufficient to construct the REV2 version. When both versions of + * each file have been created the diff callback is invoked to display + * the difference between the two files. */ + +#include <apr_uri.h> +#include <apr_md5.h> +#include <assert.h> + +#include "svn_checksum.h" +#include "svn_hash.h" +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_io.h" +#include "svn_props.h" +#include "svn_private_config.h" + +#include "client.h" + +#include "private/svn_subr_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_editor.h" + +/* Overall crawler editor baton. */ +struct edit_baton { + /* The passed depth */ + svn_depth_t depth; + + /* The result processor */ + const svn_diff_tree_processor_t *processor; + + /* RA_SESSION is the open session for making requests to the RA layer */ + svn_ra_session_t *ra_session; + + /* The rev1 from the '-r Rev1:Rev2' command line option */ + svn_revnum_t revision; + + /* The rev2 from the '-r Rev1:Rev2' option, specifically set by + set_target_revision(). */ + svn_revnum_t target_revision; + + /* The path to a temporary empty file used for add/delete + differences. The path is cached here so that it can be reused, + since all empty files are the same. */ + const char *empty_file; + + /* Empty hash used for adds. */ + apr_hash_t *empty_hash; + + /* Whether to report text deltas */ + svn_boolean_t text_deltas; + + /* A callback used to see if the client wishes to cancel the running + operation. */ + svn_cancel_func_t cancel_func; + + /* A baton to pass to the cancellation callback. */ + void *cancel_baton; + + apr_pool_t *pool; +}; + +typedef struct deleted_path_notify_t +{ + svn_node_kind_t kind; + svn_wc_notify_action_t action; + svn_wc_notify_state_t state; + svn_boolean_t tree_conflicted; +} deleted_path_notify_t; + +/* Directory level baton. + */ +struct dir_baton { + /* Gets set if the directory is added rather than replaced/unchanged. */ + svn_boolean_t added; + + /* Gets set if this operation caused a tree-conflict on this directory + * (does not show tree-conflicts persisting from before this operation). */ + svn_boolean_t tree_conflicted; + + /* If TRUE, this node is skipped entirely. + * This is used to skip all children of a tree-conflicted + * directory without setting TREE_CONFLICTED to TRUE everywhere. */ + svn_boolean_t skip; + + /* If TRUE, all children of this directory are skipped. */ + svn_boolean_t skip_children; + + /* The path of the directory within the repository */ + const char *path; + + /* The baton for the parent directory, or null if this is the root of the + hierarchy to be compared. */ + struct dir_baton *parent_baton; + + /* The overall crawler editor baton. */ + struct edit_baton *edit_baton; + + /* A cache of any property changes (svn_prop_t) received for this dir. */ + apr_array_header_t *propchanges; + + /* Boolean indicating whether a node property was changed */ + svn_boolean_t has_propchange; + + /* Baton for svn_diff_tree_processor_t */ + void *pdb; + svn_diff_source_t *left_source; + svn_diff_source_t *right_source; + + /* The pool passed in by add_dir, open_dir, or open_root. + Also, the pool this dir baton is allocated in. */ + apr_pool_t *pool; + + /* Base revision of directory. */ + svn_revnum_t base_revision; + + /* Number of users of baton. Its pool will be destroyed 0 */ + int users; +}; + +/* File level baton. + */ +struct file_baton { + /* Reference to parent baton */ + struct dir_baton *parent_baton; + + /* Gets set if the file is added rather than replaced. */ + svn_boolean_t added; + + /* Gets set if this operation caused a tree-conflict on this file + * (does not show tree-conflicts persisting from before this operation). */ + svn_boolean_t tree_conflicted; + + /* If TRUE, this node is skipped entirely. + * This is currently used to skip all children of a tree-conflicted + * directory. */ + svn_boolean_t skip; + + /* The path of the file within the repository */ + const char *path; + + /* The path and APR file handle to the temporary file that contains the + first repository version. Also, the pristine-property list of + this file. */ + const char *path_start_revision; + apr_hash_t *pristine_props; + svn_revnum_t base_revision; + + /* The path and APR file handle to the temporary file that contains the + second repository version. These fields are set when processing + textdelta and file deletion, and will be NULL if there's no + textual difference between the two revisions. */ + const char *path_end_revision; + + /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */ + svn_txdelta_window_handler_t apply_handler; + void *apply_baton; + + /* The overall crawler editor baton. */ + struct edit_baton *edit_baton; + + /* Holds the checksum of the start revision file */ + svn_checksum_t *start_md5_checksum; + + /* Holds the resulting md5 digest of a textdelta transform */ + unsigned char result_digest[APR_MD5_DIGESTSIZE]; + svn_checksum_t *result_md5_checksum; + + /* A cache of any property changes (svn_prop_t) received for this file. */ + apr_array_header_t *propchanges; + + /* Boolean indicating whether a node property was changed */ + svn_boolean_t has_propchange; + + /* Baton for svn_diff_tree_processor_t */ + void *pfb; + svn_diff_source_t *left_source; + svn_diff_source_t *right_source; + + /* The pool passed in by add_file or open_file. + Also, the pool this file_baton is allocated in. */ + apr_pool_t *pool; +}; + +/* Create a new directory baton for PATH in POOL. ADDED is set if + * this directory is being added rather than replaced. PARENT_BATON is + * the baton of the parent directory (or 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 * +make_dir_baton(const char *path, + struct dir_baton *parent_baton, + struct edit_baton *edit_baton, + svn_boolean_t added, + svn_revnum_t base_revision, + apr_pool_t *result_pool) +{ + apr_pool_t *dir_pool = svn_pool_create(result_pool); + struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton)); + + dir_baton->parent_baton = parent_baton; + dir_baton->edit_baton = edit_baton; + dir_baton->added = added; + dir_baton->tree_conflicted = FALSE; + dir_baton->skip = FALSE; + dir_baton->skip_children = FALSE; + dir_baton->pool = dir_pool; + dir_baton->path = apr_pstrdup(dir_pool, path); + dir_baton->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t)); + dir_baton->base_revision = base_revision; + dir_baton->users++; + + if (parent_baton) + parent_baton->users++; + + return dir_baton; +} + +/* New function. Called by everyone who has a reference when done */ +static svn_error_t * +release_dir(struct dir_baton *db) +{ + assert(db->users > 0); + + db->users--; + if (db->users) + return SVN_NO_ERROR; + + { + struct dir_baton *pb = db->parent_baton; + + svn_pool_destroy(db->pool); + + if (pb != NULL) + SVN_ERR(release_dir(pb)); + } + + return SVN_NO_ERROR; +} + +/* Create a new file baton for PATH in POOL, which is a child of + * directory PARENT_PATH. ADDED is set if this file is being added + * rather than replaced. EDIT_BATON is a pointer to the global edit + * baton. + */ +static struct file_baton * +make_file_baton(const char *path, + struct dir_baton *parent_baton, + svn_boolean_t added, + apr_pool_t *result_pool) +{ + apr_pool_t *file_pool = svn_pool_create(result_pool); + struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton)); + + file_baton->parent_baton = parent_baton; + file_baton->edit_baton = parent_baton->edit_baton; + file_baton->added = added; + file_baton->tree_conflicted = FALSE; + file_baton->skip = FALSE; + file_baton->pool = file_pool; + file_baton->path = apr_pstrdup(file_pool, path); + file_baton->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t)); + file_baton->base_revision = parent_baton->edit_baton->revision; + + parent_baton->users++; + + return file_baton; +} + +/* Get revision FB->base_revision of the file described by FB from the + * repository, through FB->edit_baton->ra_session. + * + * Unless PROPS_ONLY is true: + * Set FB->path_start_revision to the path of a new temporary file containing + * the file's text. + * Set FB->start_md5_checksum to that file's MD-5 checksum. + * Install a pool cleanup handler on FB->pool to delete the file. + * + * Always: + * Set FB->pristine_props to a new hash containing the file's properties. + * + * Allocate all results in FB->pool. + */ +static svn_error_t * +get_file_from_ra(struct file_baton *fb, + svn_boolean_t props_only, + apr_pool_t *scratch_pool) +{ + if (! props_only) + { + svn_stream_t *fstream; + + SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision), + NULL, svn_io_file_del_on_pool_cleanup, + fb->pool, scratch_pool)); + + fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum, + svn_checksum_md5, TRUE, scratch_pool); + + /* Retrieve the file and its properties */ + SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session, + fb->path, + fb->base_revision, + fstream, NULL, + &(fb->pristine_props), + fb->pool)); + SVN_ERR(svn_stream_close(fstream)); + } + else + { + SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session, + fb->path, + fb->base_revision, + NULL, NULL, + &(fb->pristine_props), + fb->pool)); + } + + return SVN_NO_ERROR; +} + +/* Remove every no-op property change from CHANGES: that is, remove every + entry in which the target value is the same as the value of the + corresponding property in PRISTINE_PROPS. + + Issue #3657 'dav update report handler in skelta mode can cause + spurious conflicts'. When communicating with the repository via ra_serf, + the change_dir_prop and change_file_prop svn_delta_editor_t + callbacks are called (obviously) when a directory or file property has + changed between the start and end of the edit. Less obvious however, + is that these callbacks may be made describing *all* of the properties + on FILE_BATON->PATH when using the DAV providers, not just the change(s). + (Specifically ra_serf does it for diff/merge/update/switch). + + This means that the change_[file|dir]_prop svn_delta_editor_t callbacks + may be made where there are no property changes (i.e. a noop change of + NAME from VALUE to VALUE). Normally this is harmless, but during a + merge it can result in spurious conflicts if the WC's pristine property + NAME has a value other than VALUE. In an ideal world the mod_dav_svn + update report handler, when in 'skelta' mode and describing changes to + a path on which a property has changed, wouldn't ask the client to later + fetch all properties and figure out what has changed itself. The server + already knows which properties have changed! + + Regardless, such a change is not yet implemented, and even when it is, + the client should DTRT with regard to older servers which behave this + way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only + with *actual* property changes. + + See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and + http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details. + */ +static void +remove_non_prop_changes(apr_hash_t *pristine_props, + apr_array_header_t *changes) +{ + int i; + + for (i = 0; i < changes->nelts; i++) + { + svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t); + + if (change->value) + { + const svn_string_t *old_val = svn_hash_gets(pristine_props, + change->name); + + if (old_val && svn_string_compare(old_val, change->value)) + { + int j; + + /* Remove the matching change by shifting the rest */ + for (j = i; j < changes->nelts - 1; j++) + { + APR_ARRAY_IDX(changes, j, svn_prop_t) + = APR_ARRAY_IDX(changes, j+1, svn_prop_t); + } + changes->nelts--; + } + } + } +} + +/* Get the empty file associated with the edit baton. This is cached so + * that it can be reused, all empty files are the same. + */ +static svn_error_t * +get_empty_file(struct edit_baton *eb, + const char **empty_file_path) +{ + /* Create the file if it does not exist */ + /* Note that we tried to use /dev/null in r857294, but + that won't work on Windows: it's impossible to stat NUL */ + if (!eb->empty_file) + SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL, + svn_io_file_del_on_pool_cleanup, + eb->pool, eb->pool)); + + *empty_file_path = eb->empty_file; + + 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 *eb = edit_baton; + + eb->target_revision = 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 *pool, + void **root_baton) +{ + struct edit_baton *eb = edit_baton; + struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision, + eb->pool); + + db->left_source = svn_diff__source_create(eb->revision, db->pool); + db->right_source = svn_diff__source_create(eb->target_revision, db->pool); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, + &db->skip, + &db->skip_children, + "", + db->left_source, + db->right_source, + NULL, + NULL, + eb->processor, + db->pool, + db->pool /* scratch_pool */)); + + *root_baton = db; + return SVN_NO_ERROR; +} + +/* Compare a file being deleted against an empty file. + */ +static svn_error_t * +diff_deleted_file(const char *path, + struct dir_baton *db, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = db->edit_baton; + struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool); + svn_boolean_t skip = FALSE; + svn_diff_source_t *left_source = svn_diff__source_create(eb->revision, + scratch_pool); + + if (eb->cancel_func) + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path, + left_source, + NULL /* right_source */, + NULL /* copyfrom_source */, + db->pdb, + eb->processor, + scratch_pool, scratch_pool)); + + if (eb->cancel_func) + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + if (skip) + return SVN_NO_ERROR; + + SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool)); + + SVN_ERR(eb->processor->file_deleted(fb->path, + left_source, + fb->path_start_revision, + fb->pristine_props, + fb->pfb, + eb->processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Recursively walk tree rooted at DIR (at EB->revision) in the repository, + * reporting all children as deleted. Part of a workaround for issue 2333. + * + * DIR is a repository path relative to the URL in EB->ra_session. EB is + * the overall crawler editor baton. EB->revision must be a valid revision + * number, not SVN_INVALID_REVNUM. Use EB->cancel_func (if not null) with + * EB->cancel_baton for cancellation. + */ +/* ### TODO: Handle depth. */ +static svn_error_t * +diff_deleted_dir(const char *path, + struct dir_baton *pb, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *db; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + apr_hash_t *dirents = NULL; + apr_hash_t *left_props = NULL; + svn_diff_source_t *left_source = svn_diff__source_create(eb->revision, + scratch_pool); + db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM, + scratch_pool); + + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision)); + + if (eb->cancel_func) + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children, + path, + left_source, + NULL /* right_source */, + NULL /* copyfrom_source */, + pb->pdb, + eb->processor, + scratch_pool, iterpool)); + + if (!skip || !skip_children) + SVN_ERR(svn_ra_get_dir2(eb->ra_session, + skip_children ? NULL : &dirents, + NULL, + skip ? NULL : &left_props, + path, + eb->revision, + SVN_DIRENT_KIND, + scratch_pool)); + + /* The "old" dir will be skipped by the repository report. If required, + * crawl it recursively, diffing each file against the empty file. This + * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of + * 'svn diff URL2 URL1'". */ + if (! skip_children) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, dirents); hi; + hi = apr_hash_next(hi)) + { + const char *child_path; + const char *name = svn__apr_hash_index_key(hi); + svn_dirent_t *dirent = svn__apr_hash_index_val(hi); + + svn_pool_clear(iterpool); + + child_path = svn_relpath_join(path, name, iterpool); + + if (dirent->kind == svn_node_file) + { + SVN_ERR(diff_deleted_file(child_path, db, iterpool)); + } + else if (dirent->kind == svn_node_dir) + { + SVN_ERR(diff_deleted_dir(child_path, db, iterpool)); + } + } + } + + if (! skip) + { + SVN_ERR(eb->processor->dir_deleted(path, + left_source, + left_props, + db->pdb, + eb->processor, + scratch_pool)); + } + + SVN_ERR(release_dir(db)); + + svn_pool_destroy(iterpool); + 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 *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + svn_node_kind_t kind; + apr_pool_t *scratch_pool; + + /* Process skips. */ + if (pb->skip_children) + return SVN_NO_ERROR; + + scratch_pool = svn_pool_create(eb->pool); + + /* We need to know if this is a directory or a file */ + SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind, + scratch_pool)); + + switch (kind) + { + case svn_node_file: + { + SVN_ERR(diff_deleted_file(path, pb, scratch_pool)); + break; + } + case svn_node_dir: + { + SVN_ERR(diff_deleted_dir(path, pb, scratch_pool)); + break; + } + default: + break; + } + + svn_pool_destroy(scratch_pool); + + 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 *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *db; + + /* ### TODO: support copyfrom? */ + + db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool); + *child_baton = db; + + /* Skip *everything* within a newly tree-conflicted directory, + * and directories the children of which should be skipped. */ + if (pb->skip_children) + { + db->skip = TRUE; + db->skip_children = TRUE; + return SVN_NO_ERROR; + } + + db->right_source = svn_diff__source_create(eb->target_revision, + db->pool); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, + &db->skip, + &db->skip_children, + db->path, + NULL, + db->right_source, + NULL /* copyfrom_source */, + 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 *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *db; + + db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool); + + *child_baton = db; + + /* Process Skips. */ + if (pb->skip_children) + { + db->skip = TRUE; + db->skip_children = TRUE; + return SVN_NO_ERROR; + } + + db->left_source = svn_diff__source_create(eb->revision, db->pool); + db->right_source = svn_diff__source_create(eb->target_revision, db->pool); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, + &db->skip, &db->skip_children, + path, + db->left_source, + db->right_source, + NULL /* copyfrom */, + pb ? pb->pdb : NULL, + eb->processor, + db->pool, db->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 *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb; + + /* ### TODO: support copyfrom? */ + + fb = make_file_baton(path, pb, TRUE, pb->pool); + *file_baton = fb; + + /* Process Skips. */ + if (pb->skip_children) + { + fb->skip = TRUE; + return SVN_NO_ERROR; + } + + fb->pristine_props = pb->edit_baton->empty_hash; + + fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, + &fb->skip, + path, + NULL, + fb->right_source, + NULL /* copy source */, + 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 *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct file_baton *fb; + struct edit_baton *eb = pb->edit_baton; + fb = make_file_baton(path, pb, FALSE, pb->pool); + *file_baton = fb; + + /* Process Skips. */ + if (pb->skip_children) + { + fb->skip = TRUE; + return SVN_NO_ERROR; + } + + fb->base_revision = base_revision; + + fb->left_source = svn_diff__source_create(eb->revision, fb->pool); + fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, + &fb->skip, + path, + fb->left_source, + fb->right_source, + NULL /* copy source */, + pb->pdb, + eb->processor, + fb->pool, fb->pool)); + + return SVN_NO_ERROR; +} + +/* Do the work of applying the text delta. */ +static svn_error_t * +window_handler(svn_txdelta_window_t *window, + void *window_baton) +{ + struct file_baton *fb = window_baton; + + SVN_ERR(fb->apply_handler(window, fb->apply_baton)); + + if (!window) + { + fb->result_md5_checksum = svn_checksum__from_digest_md5( + fb->result_digest, + fb->pool); + } + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_lazyopen_func_t. */ +static svn_error_t * +lazy_open_source(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct file_baton *fb = baton; + + SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_lazyopen_func_t. */ +static svn_error_t * +lazy_open_result(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct file_baton *fb = baton; + + SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_md5_digest, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + svn_stream_t *src_stream; + svn_stream_t *result_stream; + apr_pool_t *scratch_pool = fb->pool; + + /* Skip *everything* within a newly tree-conflicted directory. */ + if (fb->skip) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; + } + + /* If we're not sending file text, then ignore any that we receive. */ + if (! fb->edit_baton->text_deltas) + { + /* Supply valid paths to indicate there is a text change. */ + SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision)); + SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision)); + + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + + return SVN_NO_ERROR; + } + + /* We need the expected pristine file, so go get it */ + if (!fb->added) + SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool)); + else + SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision))); + + SVN_ERR_ASSERT(fb->path_start_revision != NULL); + + if (base_md5_digest != NULL) + { + svn_checksum_t *base_md5_checksum; + + SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5, + base_md5_digest, scratch_pool)); + + if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum)) + return svn_error_trace(svn_checksum_mismatch_err( + base_md5_checksum, + fb->start_md5_checksum, + scratch_pool, + _("Base checksum mismatch for '%s'"), + fb->path)); + } + + /* Open the file to be used as the base for second revision */ + src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE, + scratch_pool); + + /* Open the file that will become the second revision after applying the + text delta, it starts empty */ + result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE, + scratch_pool); + + svn_txdelta_apply(src_stream, + result_stream, + fb->result_digest, + fb->path, fb->pool, + &(fb->apply_handler), &(fb->apply_baton)); + + *handler = window_handler; + *handler_baton = file_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 for now. Someday we can use it to verify + * ### the integrity of the file being diffed. Done efficiently, this + * ### would probably involve calculating the checksum as the data is + * ### received, storing the final checksum in the file_baton, and + * ### comparing against it here. + */ +static svn_error_t * +close_file(void *file_baton, + const char *expected_md5_digest, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct dir_baton *pb = fb->parent_baton; + struct edit_baton *eb = fb->edit_baton; + apr_pool_t *scratch_pool; + + /* Skip *everything* within a newly tree-conflicted directory. */ + if (fb->skip) + { + svn_pool_destroy(fb->pool); + SVN_ERR(release_dir(pb)); + return SVN_NO_ERROR; + } + + scratch_pool = fb->pool; + + if (expected_md5_digest && eb->text_deltas) + { + svn_checksum_t *expected_md5_checksum; + + SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, + expected_md5_digest, scratch_pool)); + + if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum)) + return svn_error_trace(svn_checksum_mismatch_err( + expected_md5_checksum, + fb->result_md5_checksum, + pool, + _("Checksum mismatch for '%s'"), + fb->path)); + } + + if (fb->added || fb->path_end_revision || fb->has_propchange) + { + apr_hash_t *right_props; + + if (!fb->added && !fb->pristine_props) + { + /* We didn't receive a text change, so we have no pristine props. + Retrieve just the props now. */ + SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool)); + } + + if (fb->pristine_props) + remove_non_prop_changes(fb->pristine_props, fb->propchanges); + + right_props = svn_prop__patch(fb->pristine_props, fb->propchanges, + fb->pool); + + if (fb->added) + SVN_ERR(eb->processor->file_added(fb->path, + NULL /* copyfrom_src */, + fb->right_source, + NULL /* copyfrom_file */, + fb->path_end_revision, + NULL /* copyfrom_props */, + right_props, + fb->pfb, + eb->processor, + fb->pool)); + else + SVN_ERR(eb->processor->file_changed(fb->path, + fb->left_source, + fb->right_source, + fb->path_end_revision + ? fb->path_start_revision + : NULL, + fb->path_end_revision, + fb->pristine_props, + right_props, + (fb->path_end_revision != NULL), + fb->propchanges, + fb->pfb, + eb->processor, + fb->pool)); + } + + svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */ + + SVN_ERR(release_dir(pb)); + + return SVN_NO_ERROR; +} + +/* Report any accumulated prop changes via the 'dir_props_changed' callback, + * and then call the 'dir_closed' callback. Notify about any deleted paths + * within this directory that have not already been notified, and then about + * this directory itself (unless it was added, in which case the notification + * was done at that time). + * + * An svn_delta_editor_t function. */ +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + apr_pool_t *scratch_pool; + apr_hash_t *pristine_props; + svn_boolean_t send_changed = FALSE; + + scratch_pool = db->pool; + + if ((db->has_propchange || db->added) && !db->skip) + { + if (db->added) + { + pristine_props = eb->empty_hash; + } + else + { + SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props, + db->path, db->base_revision, 0, scratch_pool)); + } + + if (db->propchanges->nelts > 0) + { + remove_non_prop_changes(pristine_props, db->propchanges); + } + + if (db->propchanges->nelts > 0 || db->added) + { + apr_hash_t *right_props; + + right_props = svn_prop__patch(pristine_props, db->propchanges, + scratch_pool); + + if (db->added) + { + SVN_ERR(eb->processor->dir_added(db->path, + NULL /* copyfrom */, + db->right_source, + NULL /* copyfrom props */, + right_props, + db->pdb, + eb->processor, + db->pool)); + } + else + { + SVN_ERR(eb->processor->dir_changed(db->path, + db->left_source, + db->right_source, + pristine_props, + right_props, + db->propchanges, + db->pdb, + eb->processor, + db->pool)); + } + + send_changed = TRUE; /* Skip dir_closed */ + } + } + + if (! db->skip && !send_changed) + { + SVN_ERR(eb->processor->dir_closed(db->path, + db->left_source, + db->right_source, + db->pdb, + eb->processor, + db->pool)); + } + SVN_ERR(release_dir(db)); + + return SVN_NO_ERROR; +} + + +/* Record a prop change, which we will report later in close_file(). + * + * 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 *fb = file_baton; + svn_prop_t *propchange; + svn_prop_kind_t propkind; + + /* Skip *everything* within a newly tree-conflicted directory. */ + if (fb->skip) + return SVN_NO_ERROR; + + 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; +} + +/* Make a note of this prop change, to be reported when the dir is closed. + * + * 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 *db = dir_baton; + svn_prop_t *propchange; + svn_prop_kind_t propkind; + + /* Skip *everything* within a newly tree-conflicted directory. */ + if (db->skip) + return SVN_NO_ERROR; + + 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 *eb = edit_baton; + + svn_pool_destroy(eb->pool); + + return SVN_NO_ERROR; +} + +/* Notify that the node at PATH is 'missing'. + * An svn_delta_editor_t function. */ +static svn_error_t * +absent_directory(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool)); + + return SVN_NO_ERROR; +} + + +/* Notify that the node at PATH is 'missing'. + * An svn_delta_editor_t function. */ +static svn_error_t * +absent_file(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = baton; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->revision; + + SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_props_func(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = baton; + svn_node_kind_t node_kind; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->revision; + + SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind, + scratch_pool)); + + if (node_kind == svn_node_file) + { + SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision, + NULL, NULL, props, result_pool)); + } + else if (node_kind == svn_node_dir) + { + apr_array_header_t *tmp_props; + + SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path, + base_revision, 0 /* Dirent fields */, + result_pool)); + tmp_props = svn_prop_hash_to_array(*props, result_pool); + SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, + result_pool)); + *props = svn_prop_array_to_hash(tmp_props, result_pool); + } + else + { + *props = apr_hash_make(result_pool); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = baton; + svn_stream_t *fstream; + svn_error_t *err; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = eb->revision; + + SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + + err = svn_ra_get_file(eb->ra_session, path, base_revision, + fstream, NULL, NULL, scratch_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + SVN_ERR(svn_stream_close(fstream)); + + *filename = NULL; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + SVN_ERR(svn_stream_close(fstream)); + + return SVN_NO_ERROR; +} + +/* Create a repository diff editor and baton. */ +svn_error_t * +svn_client__get_diff_editor2(const svn_delta_editor_t **editor, + void **edit_baton, + svn_ra_session_t *ra_session, + svn_depth_t depth, + svn_revnum_t revision, + svn_boolean_t text_deltas, + const svn_diff_tree_processor_t *processor, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool) +{ + apr_pool_t *editor_pool = svn_pool_create(result_pool); + svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool); + struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb)); + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(editor_pool); + + eb->pool = editor_pool; + eb->depth = depth; + + eb->processor = processor; + + eb->ra_session = ra_session; + + eb->revision = revision; + eb->empty_file = NULL; + eb->empty_hash = apr_hash_make(eb->pool); + eb->text_deltas = text_deltas; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + + 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->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->close_file = close_file; + tree_editor->close_directory = close_directory; + tree_editor->change_file_prop = change_file_prop; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_edit = close_edit; + tree_editor->absent_directory = absent_directory; + tree_editor->absent_file = absent_file; + + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + tree_editor, eb, + editor, edit_baton, + eb->pool)); + + shim_callbacks->fetch_kind_func = fetch_kind_func; + shim_callbacks->fetch_props_func = fetch_props_func; + shim_callbacks->fetch_base_func = fetch_base_func; + shim_callbacks->fetch_baton = eb; + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, + result_pool, result_pool)); + + return SVN_NO_ERROR; +} |