diff options
Diffstat (limited to 'subversion/libsvn_client/repos_diff.c')
-rw-r--r-- | subversion/libsvn_client/repos_diff.c | 1313 |
1 files changed, 670 insertions, 643 deletions
diff --git a/subversion/libsvn_client/repos_diff.c b/subversion/libsvn_client/repos_diff.c index c303af0..6a7725f 100644 --- a/subversion/libsvn_client/repos_diff.c +++ b/subversion/libsvn_client/repos_diff.c @@ -34,6 +34,7 @@ #include <apr_uri.h> #include <apr_md5.h> +#include <assert.h> #include "svn_checksum.h" #include "svn_hash.h" @@ -47,28 +48,17 @@ #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 { - /* TARGET is a working-copy directory which corresponds to the base - URL open in RA_SESSION below. */ - const char *target; - - /* A working copy context for TARGET, NULL if this is purely a - repository operation. */ - svn_wc_context_t *wc_ctx; - /* The passed depth */ svn_depth_t depth; - /* The callback and calback argument that implement the file comparison - function */ - const svn_wc_diff_callbacks4_t *diff_callbacks; - void *diff_cmd_baton; - - /* DRY_RUN is TRUE if this is a dry-run diff, false otherwise. */ - svn_boolean_t dry_run; + /* 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; @@ -88,18 +78,8 @@ struct edit_baton { /* Empty hash used for adds. */ apr_hash_t *empty_hash; - /* Hash used to check replaced paths. Key is path relative CWD, - * Value is *deleted_path_notify_t. - * All allocations are from edit_baton's pool. */ - apr_hash_t *deleted_paths; - - /* If the func is non-null, send notifications of actions. */ - svn_wc_notify_func2_t notify_func; - void *notify_baton; - - /* TRUE if the operation needs to walk deleted dirs on the "old" side. - FALSE otherwise. */ - svn_boolean_t walk_deleted_repos_dirs; + /* Whether to report text deltas */ + svn_boolean_t text_deltas; /* A callback used to see if the client wishes to cancel the running operation. */ @@ -140,12 +120,9 @@ struct dir_baton { /* The path of the directory within the repository */ const char *path; - /* The path of the directory in the wc, relative to cwd */ - const char *wcpath; - /* The baton for the parent directory, or null if this is the root of the hierarchy to be compared. */ - struct dir_baton *dir_baton; + struct dir_baton *parent_baton; /* The overall crawler editor baton. */ struct edit_baton *edit_baton; @@ -153,17 +130,31 @@ struct dir_baton { /* A cache of any property changes (svn_prop_t) received for this dir. */ apr_array_header_t *propchanges; - /* The pristine-property list attached to this directory. */ - apr_hash_t *pristine_props; + /* 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; @@ -179,9 +170,6 @@ struct file_baton { /* The path of the file within the repository */ const char *path; - /* The path of the file in the wc, relative to cwd */ - const char *wcpath; - /* The path and APR file handle to the temporary file that contains the first repository version. Also, the pristine-property list of this file. */ @@ -212,6 +200,14 @@ struct file_baton { /* 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; @@ -229,12 +225,13 @@ make_dir_baton(const char *path, struct dir_baton *parent_baton, struct edit_baton *edit_baton, svn_boolean_t added, - apr_pool_t *pool) + svn_revnum_t base_revision, + apr_pool_t *result_pool) { - apr_pool_t *dir_pool = svn_pool_create(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->dir_baton = parent_baton; + dir_baton->parent_baton = parent_baton; dir_baton->edit_baton = edit_baton; dir_baton->added = added; dir_baton->tree_conflicted = FALSE; @@ -242,12 +239,38 @@ make_dir_baton(const char *path, dir_baton->skip_children = FALSE; dir_baton->pool = dir_pool; dir_baton->path = apr_pstrdup(dir_pool, path); - dir_baton->wcpath = svn_dirent_join(edit_baton->target, path, dir_pool); - dir_baton->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t)); + 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 @@ -255,77 +278,44 @@ make_dir_baton(const char *path, */ static struct file_baton * make_file_baton(const char *path, + struct dir_baton *parent_baton, svn_boolean_t added, - struct edit_baton *edit_baton, - apr_pool_t *pool) + apr_pool_t *result_pool) { - apr_pool_t *file_pool = svn_pool_create(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->edit_baton = edit_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->wcpath = svn_dirent_join(edit_baton->target, path, file_pool); - file_baton->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t)); - file_baton->base_revision = edit_baton->revision; + file_baton->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t)); + file_baton->base_revision = parent_baton->edit_baton->revision; - return file_baton; -} + parent_baton->users++; -/* Helper function: return up to two svn:mime-type values buried - * within a file baton. Set *MIMETYPE1 to the value within the file's - * pristine properties, or NULL if not available. Set *MIMETYPE2 to - * the value within the "new" file's propchanges, or NULL if not - * available. - */ -static void -get_file_mime_types(const char **mimetype1, - const char **mimetype2, - struct file_baton *b) -{ - /* Defaults */ - *mimetype1 = NULL; - *mimetype2 = NULL; - - if (b->pristine_props) - { - svn_string_t *pristine_val; - pristine_val = apr_hash_get(b->pristine_props, SVN_PROP_MIME_TYPE, - strlen(SVN_PROP_MIME_TYPE)); - if (pristine_val) - *mimetype2 = *mimetype1 = pristine_val->data; - } - - if (b->propchanges) - { - int i; - svn_prop_t *propchange; - - for (i = 0; i < b->propchanges->nelts; i++) - { - propchange = &APR_ARRAY_IDX(b->propchanges, i, svn_prop_t); - if (strcmp(propchange->name, SVN_PROP_MIME_TYPE) == 0) - { - if (propchange->value) - *mimetype2 = propchange->value->data; - break; - } - } - } + return file_baton; } - -/* Get revision REVISION of the file described by B from the repository. - * Set B->path_start_revision to the path of a new temporary file containing - * the file's text. Set B->pristine_props to a new hash containing the - * file's properties. Install a pool cleanup handler on B->pool to delete - * the file. +/* 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 *b, +get_file_from_ra(struct file_baton *fb, svn_boolean_t props_only, apr_pool_t *scratch_pool) { @@ -333,44 +323,47 @@ get_file_from_ra(struct file_baton *b, { svn_stream_t *fstream; - SVN_ERR(svn_stream_open_unique(&fstream, &(b->path_start_revision), NULL, - svn_io_file_del_on_pool_cleanup, b->pool, - b->pool)); + 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, &b->start_md5_checksum, - svn_checksum_md5, TRUE, b->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(b->edit_baton->ra_session, - b->path, - b->base_revision, + SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session, + fb->path, + fb->base_revision, fstream, NULL, - &(b->pristine_props), - b->pool)); + &(fb->pristine_props), + fb->pool)); SVN_ERR(svn_stream_close(fstream)); } else { - SVN_ERR(svn_ra_get_file(b->edit_baton->ra_session, - b->path, - b->base_revision, + SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session, + fb->path, + fb->base_revision, NULL, NULL, - &(b->pristine_props), - b->pool)); + &(fb->pristine_props), + fb->pool)); } return SVN_NO_ERROR; } -/* Issue #3657 'dav update report handler in skelta mode can cause - spurious conflicts'. When communicating with the repository via ra_serf - and ra_neon, the change_dir_prop and change_file_prop svn_delta_editor_t +/* 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_neon does this for diff/merge and ra_serf does it - for diff/merge/update/switch). + (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 @@ -389,8 +382,6 @@ get_file_from_ra(struct file_baton *b, 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. - - This function filters these property changes from the change hash */ static void remove_non_prop_changes(apr_hash_t *pristine_props, @@ -404,9 +395,8 @@ remove_non_prop_changes(apr_hash_t *pristine_props, if (change->value) { - const svn_string_t *old_val = apr_hash_get(pristine_props, - change->name, - APR_HASH_KEY_STRING); + const svn_string_t *old_val = svn_hash_gets(pristine_props, + change->name); if (old_val && svn_string_compare(old_val, change->value)) { @@ -424,18 +414,6 @@ remove_non_prop_changes(apr_hash_t *pristine_props, } } -/* Get the props attached to a directory in the repository at BASE_REVISION. */ -static svn_error_t * -get_dirprops_from_ra(struct dir_baton *b, svn_revnum_t base_revision) -{ - return svn_ra_get_dir2(b->edit_baton->ra_session, - NULL, NULL, &(b->pristine_props), - b->path, - base_revision, - 0, - b->pool); -} - /* Get the empty file associated with the edit baton. This is cached so * that it can be reused, all empty files are the same. */ @@ -456,7 +434,7 @@ get_empty_file(struct edit_baton *eb, return SVN_NO_ERROR; } -/* An editor function. The root of the comparison hierarchy */ +/* An svn_delta_editor_t function. */ static svn_error_t * set_target_revision(void *edit_baton, svn_revnum_t target_revision, @@ -468,7 +446,7 @@ set_target_revision(void *edit_baton, return SVN_NO_ERROR; } -/* An editor function. The root of the comparison hierarchy */ +/* 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, @@ -476,101 +454,168 @@ open_root(void *edit_baton, void **root_baton) { struct edit_baton *eb = edit_baton; - struct dir_baton *b = make_dir_baton("", NULL, eb, FALSE, pool); + 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; - /* Override the wcpath in our baton. */ - b->wcpath = apr_pstrdup(pool, eb->target); + SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool)); - SVN_ERR(get_dirprops_from_ra(b, base_revision)); + SVN_ERR(eb->processor->file_deleted(fb->path, + left_source, + fb->path_start_revision, + fb->pristine_props, + fb->pfb, + eb->processor, + scratch_pool)); - *root_baton = b; return SVN_NO_ERROR; } -/* Recursively walk tree rooted at DIR (at REVISION) in the repository, - * reporting all files as deleted. Part of a workaround for issue 2333. +/* 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 RA_SESSION. REVISION - * must be a valid revision number, not SVN_INVALID_REVNUM. EB is the - * overall crawler editor baton. If CANCEL_FUNC is not NULL, then it - * should refer to a cancellation function (along with CANCEL_BATON). + * 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 *dir, - svn_revnum_t revision, - svn_ra_session_t *ra_session, - struct edit_baton *eb, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *pool) +diff_deleted_dir(const char *path, + struct dir_baton *pb, + apr_pool_t *scratch_pool) { - apr_hash_t *dirents; - apr_pool_t *iterpool = svn_pool_create(pool); - apr_hash_index_t *hi; - - SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); - - if (cancel_func) - SVN_ERR(cancel_func(cancel_baton)); - - SVN_ERR(svn_ra_get_dir2(ra_session, - &dirents, - NULL, NULL, - dir, - revision, - SVN_DIRENT_KIND, - 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)); - for (hi = apr_hash_first(pool, dirents); hi; - hi = apr_hash_next(hi)) + /* 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) { - const char *path; - const char *name = svn__apr_hash_index_key(hi); - svn_dirent_t *dirent = svn__apr_hash_index_val(hi); - - svn_pool_clear(iterpool); - - path = svn_relpath_join(dir, name, iterpool); + apr_hash_index_t *hi; - if (dirent->kind == svn_node_file) + for (hi = apr_hash_first(scratch_pool, dirents); hi; + hi = apr_hash_next(hi)) { - struct file_baton *b; - const char *mimetype1, *mimetype2; + const char *child_path; + const char *name = svn__apr_hash_index_key(hi); + svn_dirent_t *dirent = svn__apr_hash_index_val(hi); - /* Compare a file being deleted against an empty file */ - b = make_file_baton(path, FALSE, eb, iterpool); - SVN_ERR(get_file_from_ra(b, FALSE, iterpool)); + svn_pool_clear(iterpool); - SVN_ERR(get_empty_file(b->edit_baton, &(b->path_end_revision))); + child_path = svn_relpath_join(path, name, iterpool); - get_file_mime_types(&mimetype1, &mimetype2, b); - - SVN_ERR(eb->diff_callbacks->file_deleted( - NULL, NULL, b->wcpath, - b->path_start_revision, - b->path_end_revision, - mimetype1, mimetype2, - b->pristine_props, - b->edit_baton->diff_cmd_baton, - 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 (dirent->kind == svn_node_dir) - SVN_ERR(diff_deleted_dir(path, - revision, - ra_session, - eb, - cancel_func, - cancel_baton, - 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 editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * delete_entry(const char *path, svn_revnum_t base_revision, @@ -580,14 +625,10 @@ delete_entry(const char *path, struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; svn_node_kind_t kind; - svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; - svn_wc_notify_action_t action = svn_wc_notify_skip; - svn_boolean_t tree_conflicted = FALSE; apr_pool_t *scratch_pool; - /* Skip *everything* within a newly tree-conflicted directory, - * and directories the children of which should be skipped. */ - if (pb->skip || pb->tree_conflicted || pb->skip_children) + /* Process skips. */ + if (pb->skip_children) return SVN_NO_ERROR; scratch_pool = svn_pool_create(eb->pool); @@ -600,78 +641,24 @@ delete_entry(const char *path, { case svn_node_file: { - const char *mimetype1, *mimetype2; - struct file_baton *b; - - /* Compare a file being deleted against an empty file */ - b = make_file_baton(path, FALSE, eb, scratch_pool); - SVN_ERR(get_file_from_ra(b, FALSE, scratch_pool)); - SVN_ERR(get_empty_file(b->edit_baton, &(b->path_end_revision))); - - get_file_mime_types(&mimetype1, &mimetype2, b); - - SVN_ERR(eb->diff_callbacks->file_deleted( - &state, &tree_conflicted, b->wcpath, - b->path_start_revision, - b->path_end_revision, - mimetype1, mimetype2, - b->pristine_props, - b->edit_baton->diff_cmd_baton, - scratch_pool)); - + SVN_ERR(diff_deleted_file(path, pb, scratch_pool)); break; } case svn_node_dir: { - SVN_ERR(eb->diff_callbacks->dir_deleted( - &state, &tree_conflicted, - svn_dirent_join(eb->target, path, pool), - eb->diff_cmd_baton, scratch_pool)); - - if (eb->walk_deleted_repos_dirs) - { - /* A workaround for issue 2333. The "old" dir will be - skipped by the repository report. Crawl it recursively, - diffing each file against the empty file. */ - SVN_ERR(diff_deleted_dir(path, - eb->revision, - eb->ra_session, - eb, - eb->cancel_func, - eb->cancel_baton, - scratch_pool)); - } + SVN_ERR(diff_deleted_dir(path, pb, scratch_pool)); break; } default: break; } - if ((state != svn_wc_notify_state_missing) - && (state != svn_wc_notify_state_obstructed) - && !tree_conflicted) - { - action = svn_wc_notify_update_delete; - } - - if (eb->notify_func) - { - const char* deleted_path; - deleted_path_notify_t *dpn = apr_pcalloc(eb->pool, sizeof(*dpn)); - deleted_path = svn_dirent_join(eb->target, path, eb->pool); - dpn->kind = kind; - dpn->action = tree_conflicted ? svn_wc_notify_tree_conflict : action; - dpn->state = state; - dpn->tree_conflicted = tree_conflicted; - apr_hash_set(eb->deleted_paths, deleted_path, APR_HASH_KEY_STRING, dpn); - } - svn_pool_destroy(scratch_pool); return SVN_NO_ERROR; } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_directory(const char *path, void *parent_baton, @@ -682,86 +669,40 @@ add_directory(const char *path, { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; - struct dir_baton *b; - svn_wc_notify_state_t state; + struct dir_baton *db; /* ### TODO: support copyfrom? */ - b = make_dir_baton(path, pb, eb, TRUE, pool); - b->pristine_props = eb->empty_hash; - *child_baton = b; + 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 || pb->tree_conflicted || pb->skip_children) + if (pb->skip_children) { - b->skip = TRUE; + 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->diff_callbacks->dir_added( - &state, &b->tree_conflicted, - &b->skip, &b->skip_children, b->wcpath, - eb->target_revision, copyfrom_path, copyfrom_revision, - eb->diff_cmd_baton, pool)); - - /* Notifications for directories are done at close_directory time. - * But for paths at which the editor drive adds directories, we make an - * exception to this rule, so that the path appears in the output before - * any children of the newly added directory. Since a deletion at this path - * must have happened before this addition, we can safely notify about - * replaced directories here, too. */ - if (eb->notify_func) - { - deleted_path_notify_t *dpn; - svn_wc_notify_t *notify; - svn_wc_notify_action_t action; - svn_node_kind_t kind = svn_node_dir; - - /* Find out if a pending delete notification for this path is - * still around. */ - dpn = apr_hash_get(eb->deleted_paths, b->wcpath, APR_HASH_KEY_STRING); - if (dpn) - { - /* If any was found, we will handle the pending 'deleted path - * notification' (DPN) here. Remove it from the list. */ - apr_hash_set(eb->deleted_paths, b->wcpath, - APR_HASH_KEY_STRING, NULL); - - /* the pending delete might be on a different node kind. */ - kind = dpn->kind; - state = dpn->state; - } - - /* Determine what the notification (ACTION) should be. - * In case of a pending 'delete', this might become a 'replace'. */ - if (b->tree_conflicted) - action = svn_wc_notify_tree_conflict; - else if (dpn) - { - if (dpn->action == svn_wc_notify_update_delete) - action = svn_wc_notify_update_replace; - else - /* Note: dpn->action might be svn_wc_notify_tree_conflict */ - action = dpn->action; - } - else if (state == svn_wc_notify_state_missing || - state == svn_wc_notify_state_obstructed) - action = svn_wc_notify_skip; - else - action = svn_wc_notify_update_add; - - notify = svn_wc_create_notify(b->wcpath, action, pool); - notify->kind = kind; - notify->content_state = notify->prop_state = state; - (*eb->notify_func)(eb->notify_baton, notify, 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 editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_directory(const char *path, void *parent_baton, @@ -771,31 +712,38 @@ open_directory(const char *path, { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; - struct dir_baton *b; + struct dir_baton *db; - b = make_dir_baton(path, pb, pb->edit_baton, FALSE, pool); - *child_baton = b; + db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool); - /* Skip *everything* within a newly tree-conflicted directory - * and directories the children of which should be skipped. */ - if (pb->skip || pb->tree_conflicted || pb->skip_children) + *child_baton = db; + + /* Process Skips. */ + if (pb->skip_children) { - b->skip = TRUE; + db->skip = TRUE; + db->skip_children = TRUE; return SVN_NO_ERROR; } - SVN_ERR(get_dirprops_from_ra(b, base_revision)); + 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->diff_callbacks->dir_opened( - &b->tree_conflicted, &b->skip, - &b->skip_children, b->wcpath, base_revision, - b->edit_baton->diff_cmd_baton, 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 editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_file(const char *path, void *parent_baton, @@ -805,27 +753,39 @@ add_file(const char *path, void **file_baton) { struct dir_baton *pb = parent_baton; - struct file_baton *b; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb; /* ### TODO: support copyfrom? */ - b = make_file_baton(path, TRUE, pb->edit_baton, pool); - *file_baton = b; + fb = make_file_baton(path, pb, TRUE, pb->pool); + *file_baton = fb; - /* Skip *everything* within a newly tree-conflicted directory. - * and directories the children of which should be skipped. */ - if (pb->skip || pb->tree_conflicted || pb->skip_children) + /* Process Skips. */ + if (pb->skip_children) { - b->skip = TRUE; + fb->skip = TRUE; return SVN_NO_ERROR; } - b->pristine_props = pb->edit_baton->empty_hash; + 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 editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_file(const char *path, void *parent_baton, @@ -834,24 +794,32 @@ open_file(const char *path, void **file_baton) { struct dir_baton *pb = parent_baton; - struct file_baton *b; + struct file_baton *fb; struct edit_baton *eb = pb->edit_baton; - b = make_file_baton(path, FALSE, pb->edit_baton, pool); - *file_baton = b; + fb = make_file_baton(path, pb, FALSE, pb->pool); + *file_baton = fb; - /* Skip *everything* within a newly tree-conflicted directory - * and directories the children of which should be skipped. */ - if (pb->skip || pb->tree_conflicted || pb->skip_children) + /* Process Skips. */ + if (pb->skip_children) { - b->skip = TRUE; + fb->skip = TRUE; return SVN_NO_ERROR; } - b->base_revision = base_revision; + fb->base_revision = base_revision; - SVN_ERR(eb->diff_callbacks->file_opened( - &b->tree_conflicted, &b->skip, - b->wcpath, base_revision, eb->diff_cmd_baton, pool)); + 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; } @@ -861,21 +829,52 @@ static svn_error_t * window_handler(svn_txdelta_window_t *window, void *window_baton) { - struct file_baton *b = window_baton; + struct file_baton *fb = window_baton; - SVN_ERR(b->apply_handler(window, b->apply_baton)); + SVN_ERR(fb->apply_handler(window, fb->apply_baton)); if (!window) { - b->result_md5_checksum = svn_checksum__from_digest(b->result_digest, - svn_checksum_md5, - b->pool); + fb->result_md5_checksum = svn_checksum__from_digest_md5( + fb->result_digest, + fb->pool); } return SVN_NO_ERROR; } -/* An editor function. */ +/* 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, @@ -883,26 +882,39 @@ apply_textdelta(void *file_baton, svn_txdelta_window_handler_t *handler, void **handler_baton) { - struct file_baton *b = file_baton; + struct file_baton *fb = file_baton; svn_stream_t *src_stream; svn_stream_t *result_stream; - apr_pool_t *scratch_pool = b->pool; + apr_pool_t *scratch_pool = fb->pool; /* Skip *everything* within a newly tree-conflicted directory. */ - if (b->skip) + 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 (!b->added) - SVN_ERR(get_file_from_ra(b, FALSE, b->pool)); + if (!fb->added) + SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool)); else - SVN_ERR(get_empty_file(b->edit_baton, &(b->path_start_revision))); + SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision))); - SVN_ERR_ASSERT(b->path_start_revision != NULL); + SVN_ERR_ASSERT(fb->path_start_revision != NULL); if (base_md5_digest != NULL) { @@ -911,30 +923,29 @@ apply_textdelta(void *file_baton, SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5, base_md5_digest, scratch_pool)); - if (!svn_checksum_match(base_md5_checksum, b->start_md5_checksum)) + if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum)) return svn_error_trace(svn_checksum_mismatch_err( base_md5_checksum, - b->start_md5_checksum, + fb->start_md5_checksum, scratch_pool, _("Base checksum mismatch for '%s'"), - b->path)); + fb->path)); } /* Open the file to be used as the base for second revision */ - SVN_ERR(svn_stream_open_readonly(&src_stream, b->path_start_revision, - scratch_pool, scratch_pool)); + 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 */ - SVN_ERR(svn_stream_open_unique(&result_stream, &b->path_end_revision, NULL, - svn_io_file_del_on_pool_cleanup, - scratch_pool, scratch_pool)); + result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE, + scratch_pool); svn_txdelta_apply(src_stream, result_stream, - b->result_digest, - b->path, b->pool, - &(b->apply_handler), &(b->apply_baton)); + fb->result_digest, + fb->path, fb->pool, + &(fb->apply_handler), &(fb->apply_baton)); *handler = window_handler; *handler_baton = file_baton; @@ -942,7 +953,7 @@ apply_textdelta(void *file_baton, return SVN_NO_ERROR; } -/* An editor function. When the file is closed we have a temporary +/* 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. * @@ -957,265 +968,208 @@ close_file(void *file_baton, const char *expected_md5_digest, apr_pool_t *pool) { - struct file_baton *b = file_baton; - struct edit_baton *eb = b->edit_baton; - svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown; - svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; + 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 (b->skip) + if (fb->skip) { - svn_pool_destroy(b->pool); + svn_pool_destroy(fb->pool); + SVN_ERR(release_dir(pb)); return SVN_NO_ERROR; } - scratch_pool = b->pool; + scratch_pool = fb->pool; - if (expected_md5_digest) + 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, b->result_md5_checksum)) + if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum)) return svn_error_trace(svn_checksum_mismatch_err( expected_md5_checksum, - b->result_md5_checksum, + fb->result_md5_checksum, pool, _("Checksum mismatch for '%s'"), - b->path)); + fb->path)); } - if (!b->added && b->propchanges->nelts > 0) + if (fb->added || fb->path_end_revision || fb->has_propchange) { - if (!b->pristine_props) + 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(b, TRUE, scratch_pool)); + SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool)); } - remove_non_prop_changes(b->pristine_props, b->propchanges); - } - - if (b->path_end_revision || b->propchanges->nelts > 0) - { - const char *mimetype1, *mimetype2; - get_file_mime_types(&mimetype1, &mimetype2, b); - - - if (b->added) - SVN_ERR(eb->diff_callbacks->file_added( - &content_state, &prop_state, &b->tree_conflicted, - b->wcpath, - b->path_end_revision ? b->path_start_revision : NULL, - b->path_end_revision, - 0, - b->edit_baton->target_revision, - mimetype1, mimetype2, - NULL, SVN_INVALID_REVNUM, - b->propchanges, b->pristine_props, - b->edit_baton->diff_cmd_baton, - 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->diff_callbacks->file_changed( - &content_state, &prop_state, - &b->tree_conflicted, b->wcpath, - b->path_end_revision ? b->path_start_revision : NULL, - b->path_end_revision, - b->edit_baton->revision, - b->edit_baton->target_revision, - mimetype1, mimetype2, - b->propchanges, b->pristine_props, - b->edit_baton->diff_cmd_baton, - scratch_pool)); + 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 */ - if (eb->notify_func) - { - deleted_path_notify_t *dpn; - svn_wc_notify_t *notify; - svn_wc_notify_action_t action; - svn_node_kind_t kind = svn_node_file; - - /* Find out if a pending delete notification for this path is - * still around. */ - dpn = apr_hash_get(eb->deleted_paths, b->wcpath, APR_HASH_KEY_STRING); - if (dpn) - { - /* If any was found, we will handle the pending 'deleted path - * notification' (DPN) here. Remove it from the list. */ - apr_hash_set(eb->deleted_paths, b->wcpath, - APR_HASH_KEY_STRING, NULL); - - /* the pending delete might be on a different node kind. */ - kind = dpn->kind; - content_state = prop_state = dpn->state; - } - - /* Determine what the notification (ACTION) should be. - * In case of a pending 'delete', this might become a 'replace'. */ - if (b->tree_conflicted) - action = svn_wc_notify_tree_conflict; - else if (dpn) - { - if (dpn->action == svn_wc_notify_update_delete - && b->added) - action = svn_wc_notify_update_replace; - else - /* Note: dpn->action might be svn_wc_notify_tree_conflict */ - action = dpn->action; - } - else if ((content_state == svn_wc_notify_state_missing) - || (content_state == svn_wc_notify_state_obstructed)) - action = svn_wc_notify_skip; - else if (b->added) - action = svn_wc_notify_update_add; - else - action = svn_wc_notify_update_update; - - notify = svn_wc_create_notify(b->wcpath, action, scratch_pool); - notify->kind = kind; - notify->content_state = content_state; - notify->prop_state = prop_state; - (*eb->notify_func)(eb->notify_baton, notify, scratch_pool); - } - - svn_pool_destroy(b->pool); /* Destroy file and scratch pool */ + SVN_ERR(release_dir(pb)); return SVN_NO_ERROR; } -/* An editor function. */ +/* 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 *b = dir_baton; - struct edit_baton *eb = b->edit_baton; - svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown; - svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; - svn_boolean_t skipped = FALSE; + 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; - /* Skip *everything* within a newly tree-conflicted directory. */ - if (b->skip) - { - svn_pool_destroy(b->pool); - return SVN_NO_ERROR; - } - - scratch_pool = b->pool; - - if (!b->added && b->propchanges->nelts > 0) - remove_non_prop_changes(b->pristine_props, b->propchanges); + scratch_pool = db->pool; - /* Don't do the props_changed stuff if this is a dry_run and we don't - have an access baton, since in that case the directory will already - have been recognised as added, in which case they cannot conflict. */ - if (b->propchanges->nelts > 0) + if ((db->has_propchange || db->added) && !db->skip) { - svn_boolean_t tree_conflicted = FALSE; - SVN_ERR(eb->diff_callbacks->dir_props_changed( - &prop_state, &tree_conflicted, - b->wcpath, b->added, - b->propchanges, b->pristine_props, - b->edit_baton->diff_cmd_baton, scratch_pool)); - if (tree_conflicted) - b->tree_conflicted = TRUE; - - if (prop_state == svn_wc_notify_state_obstructed - || prop_state == svn_wc_notify_state_missing) + if (db->added) { - content_state = prop_state; - skipped = TRUE; + pristine_props = eb->empty_hash; } - } - - SVN_ERR(eb->diff_callbacks->dir_closed(NULL, NULL, NULL, - b->wcpath, b->added, - b->edit_baton->diff_cmd_baton, - scratch_pool)); - - /* Don't notify added directories as they triggered notification - in add_directory. */ - if (!skipped && !b->added && eb->notify_func) - { - apr_hash_index_t *hi; - - for (hi = apr_hash_first(pool, eb->deleted_paths); hi; - hi = apr_hash_next(hi)) + else { - svn_wc_notify_t *notify; - const char *deleted_path = svn__apr_hash_index_key(hi); - deleted_path_notify_t *dpn = svn__apr_hash_index_val(hi); - - notify = svn_wc_create_notify(deleted_path, dpn->action, pool); - notify->kind = dpn->kind; - notify->content_state = notify->prop_state = dpn->state; - notify->lock_state = svn_wc_notify_lock_state_inapplicable; - (*eb->notify_func)(eb->notify_baton, notify, pool); - apr_hash_set(eb->deleted_paths, deleted_path, - APR_HASH_KEY_STRING, NULL); + SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props, + db->path, db->base_revision, 0, scratch_pool)); } - } - if (!b->added && eb->notify_func) - { - svn_wc_notify_t *notify; - svn_wc_notify_action_t action; + if (db->propchanges->nelts > 0) + { + remove_non_prop_changes(pristine_props, db->propchanges); + } - if (b->tree_conflicted) - action = svn_wc_notify_tree_conflict; - else if (skipped) - action = svn_wc_notify_skip; - else - action = svn_wc_notify_update_update; + if (db->propchanges->nelts > 0 || db->added) + { + apr_hash_t *right_props; - notify = svn_wc_create_notify(b->wcpath, action, pool); - notify->kind = svn_node_dir; + right_props = svn_prop__patch(pristine_props, db->propchanges, + scratch_pool); - /* In case of a tree conflict during merge, the diff callback - * sets content_state appropriately. So copy the state into the - * notify_t to make sure conflicts get displayed. */ - notify->content_state = content_state; + 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)); + } - notify->prop_state = prop_state; - notify->lock_state = svn_wc_notify_lock_state_inapplicable; - (*eb->notify_func)(eb->notify_baton, notify, scratch_pool); + send_changed = TRUE; /* Skip dir_closed */ + } } - svn_pool_destroy(b->pool); /* Destroy baton and scratch_pool */ + 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; } -/* An editor function. */ +/* 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 *b = file_baton; + struct file_baton *fb = file_baton; svn_prop_t *propchange; + svn_prop_kind_t propkind; /* Skip *everything* within a newly tree-conflicted directory. */ - if (b->skip) + 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(b->propchanges); - propchange->name = apr_pstrdup(b->pool, name); - propchange->value = value ? svn_string_dup(value, b->pool) : NULL; + 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 editor function. */ +/* 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, @@ -1224,11 +1178,18 @@ change_dir_prop(void *dir_baton, { 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; @@ -1237,7 +1198,7 @@ change_dir_prop(void *dir_baton, } -/* An editor function. */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_edit(void *edit_baton, apr_pool_t *pool) @@ -1249,7 +1210,8 @@ close_edit(void *edit_baton, return SVN_NO_ERROR; } -/* An editor function. */ +/* 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, @@ -1258,27 +1220,14 @@ absent_directory(const char *path, struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; - /* ### TODO: Raise a tree-conflict?? I sure hope not.*/ - - if (eb->notify_func) - { - svn_wc_notify_t *notify - = svn_wc_create_notify(svn_dirent_join(pb->wcpath, - svn_relpath_basename(path, - NULL), - pool), - svn_wc_notify_skip, pool); - notify->kind = svn_node_dir; - notify->content_state = notify->prop_state - = svn_wc_notify_state_missing; - (*eb->notify_func)(eb->notify_baton, notify, pool); - } + SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool)); return SVN_NO_ERROR; } -/* An editor function. */ +/* 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, @@ -1287,68 +1236,138 @@ absent_file(const char *path, struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; - /* ### TODO: Raise a tree-conflict?? I sure hope not.*/ + 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; - if (eb->notify_func) + 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_wc_notify_t *notify - = svn_wc_create_notify(svn_dirent_join(pb->wcpath, - svn_relpath_basename(path, - pool), - pool), - svn_wc_notify_skip, pool); - notify->kind = svn_node_file; - notify->content_state = notify->prop_state - = svn_wc_notify_state_missing; - (*eb->notify_func)(eb->notify_baton, notify, pool); + 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_editor(const svn_delta_editor_t **editor, - void **edit_baton, - svn_wc_context_t *wc_ctx, - const char *target, - svn_depth_t depth, - svn_ra_session_t *ra_session, - svn_revnum_t revision, - svn_boolean_t walk_deleted_dirs, - svn_boolean_t dry_run, - const svn_wc_diff_callbacks4_t *diff_callbacks, - void *diff_cmd_baton, - svn_cancel_func_t cancel_func, - void *cancel_baton, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +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)); - const char *target_abspath; - - SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, editor_pool)); + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(editor_pool); eb->pool = editor_pool; - eb->target = target; - eb->wc_ctx = wc_ctx; eb->depth = depth; - eb->diff_callbacks = diff_callbacks; - eb->diff_cmd_baton = diff_cmd_baton; - eb->dry_run = dry_run; + + eb->processor = processor; + eb->ra_session = ra_session; eb->revision = revision; eb->empty_file = NULL; eb->empty_hash = apr_hash_make(eb->pool); - eb->deleted_paths = apr_hash_make(eb->pool); - eb->notify_func = notify_func; - eb->notify_baton = notify_baton; - eb->walk_deleted_repos_dirs = walk_deleted_dirs; + eb->text_deltas = text_deltas; eb->cancel_func = cancel_func; eb->cancel_baton = cancel_baton; @@ -1368,11 +1387,19 @@ svn_client__get_diff_editor(const svn_delta_editor_t **editor, tree_editor->absent_directory = absent_directory; tree_editor->absent_file = absent_file; - return svn_delta_get_cancellation_editor(cancel_func, - cancel_baton, - tree_editor, - eb, - editor, - edit_baton, - eb->pool); + 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; } |