Hey Charles! I don't have a chance to do full review of this patch
(26 hours until thesis deadline), but just wondering --- what did you
end up deciding about applying text deltas? Looks like from below
that you're going for the "apply deltas based on the gzipped block,
ignoring the unidiff" route... is that true? If so you should
probably update the docs that talk about /usr/bin/patch...
--dave
On 8/20/07, cacknin@tigris.org <cacknin@tigris.org> wrote:
> Author: cacknin
> Date: Mon Aug 20 11:42:44 2007
> New Revision: 26210
>
> Log:
> Implementation of 'svn patch' guts.
>
> * subversion/svn/patch-cmd.c
> (svn_cl__patch): set up the command line notifier for 'svn patch'.
>
> * subversion/svn/main.c
> (svn_cl__cmd_table): provide 'patch' with svn_cl__force_opt option.
>
> * subversion/include/svn_client.h
> (svn_client_patch): add the 'force' argument.
>
> * subversion/include/svn_wc.h
> (svn_wc_read_item, svn_wc_parse_tuple, svn_wc_read_tuple,
> svn_wc_apply_patch): New functions to deal with svnpatch-reading.
>
> * subversion/libsvn_wc/util.c
> (readbuf_getchar_skip_whitespace, read_string, read_item,
> vparse_tuple): New helper functions.
> (svn_wc_read_item, svn_wc_parse_tuple, svn_wc_read_tuple): New
> functions to read Editor Commands chunks.
>
> * subversion/libsvn_wc/patch.c
> Similar to what libsvn_ra_svn/editorp.c does with ra_svn-based
> communications.
> New file. Read Editor Commands from a decoded patch file and drive
> the editor.
>
> * subversion/libsvn_client/patch.c
> A lot of this file was imported from both merge.c and repos_diff.c.
> (patch_cmd_baton): analogous to merge_cmd_baton.
> (patch_callbacks, merge_props_changed, merge_file_changed,
> merge_file_added, merge_file_deleted, merge_dir_added,
> merge_dir_deleted): add svn_wc_diff_callbacks2_t functions to merge
> changes from the (svn)patch.
> (merge_delete_notify_baton_t, merge_delete_notify_func):
> Notify-wrapper and its baton from merge.c.
> (edit_baton, dir_baton, file_baton, make_dir_baton, make_file_baton,
> make_editor_baton): new baton structures/functions dealing with tree
> traversal.
> (create_empty_file, get_path_access, get_parent_access,
> get_empty_file): helper functions from repos_diff.c.
> (open_root, delete_entry, add_directory, open_directory,
> add_file, open_file, window_handler, apply_textdelta, close_file,
> close_directory, change_file_prop, change_dir_prop, close_edit):
> Editor functions, imported from repos_diff.c.
> (extract_svnpatch): Pull out the svnpatch block from patches and
> decode data (uncompress + base64-decode).
> (svn_client_patch): add 'force' flag and define the function to call
> svn_wc_apply_patch.
>
>
> Added:
> branches/svnpatch-diff/subversion/libsvn_wc/patch.c
> Modified:
> branches/svnpatch-diff/subversion/include/svn_client.h
> branches/svnpatch-diff/subversion/include/svn_wc.h
> branches/svnpatch-diff/subversion/libsvn_client/patch.c
> branches/svnpatch-diff/subversion/libsvn_wc/util.c
> branches/svnpatch-diff/subversion/svn/main.c
> branches/svnpatch-diff/subversion/svn/patch-cmd.c
>
> Modified: branches/svnpatch-diff/subversion/include/svn_client.h
> URL: http://svn.collab.net/viewvc/svn/branches/svnpatch-diff/subversion/include/svn_client.h?pathrev=26210&r1=26209&r2=26210
> ==============================================================================
> --- branches/svnpatch-diff/subversion/include/svn_client.h (original)
> +++ branches/svnpatch-diff/subversion/include/svn_client.h Mon Aug 20 11:42:44 2007
> @@ -3952,11 +3952,15 @@
> * The patch might carry Unified diffs, svnpatch diffs, or both.
> * Although we're pretty much able to handle the svnpatch block, we'll
> * be using external tools to process the Unidiff. On Unix-like systems
> - * we'll invoke the patch(1) binary while we'll leave the user with its
> + * we'll invoke the patch(1) binary while we'll leave the user with her
> * own tools on other platforms.
> * Note: hopefuly this is temporary and we'll have our own implementation
> * one day to cut off the dependency.
> *
> + * If @a force is not set and the patch involves deleting locally modified or
> + * unversioned items the operation will fail. If @a force is set such items
> + * will be deleted.
> + *
> * If @a dry_run is true, the patch is carried out, and full notification
> * feedback is provided, but the working copy is not modified.
> *
> @@ -3965,6 +3969,7 @@
> svn_error_t *
> svn_client_patch(const char *patch_path,
> const char *wc_path,
> + svn_boolean_t force,
> svn_boolean_t dry_run,
> svn_client_ctx_t *ctx,
> apr_pool_t *pool);
>
> Modified: branches/svnpatch-diff/subversion/include/svn_wc.h
> URL: http://svn.collab.net/viewvc/svn/branches/svnpatch-diff/subversion/include/svn_wc.h?pathrev=26210&r1=26209&r2=26210
> ==============================================================================
> --- branches/svnpatch-diff/subversion/include/svn_wc.h (original)
> +++ branches/svnpatch-diff/subversion/include/svn_wc.h Mon Aug 20 11:42:44 2007
> @@ -44,6 +44,7 @@
> #include "svn_error.h"
> #include "svn_opt.h"
> #include "svn_ra.h" /* for svn_ra_reporter_t type */
> +#include "svn_ra_svn.h"
>
> #ifdef __cplusplus
> extern "C" {
> @@ -4339,6 +4340,8 @@
> *
> */
>
> +/* Output -- Writing */
> +
> /* Append @a number into @a target stream. */
> svn_error_t *
> svn_wc_write_number(svn_stream_t *target,
> @@ -4392,6 +4395,29 @@
> const char *cmdname,
> const char *fmt, ...);
>
> +/* Input -- Reading */
> +
> +svn_error_t *
> +svn_wc_read_item(svn_stream_t *from,
> + apr_pool_t *pool,
> + svn_ra_svn_item_t **item);
> +
> +svn_error_t *
> +svn_wc_parse_tuple(apr_array_header_t *list,
> + apr_pool_t *pool,
> + const char *fmt, ...);
> +
> +svn_error_t *
> +svn_wc_read_tuple(svn_stream_t *from,
> + apr_pool_t *pool,
> + const char *fmt, ...);
> +
> +svn_error_t *
> +svn_wc_apply_patch(apr_file_t *decoded_patch_file,
> + const svn_delta_editor_t *diff_editor,
> + void *diff_edit_baton,
> + apr_pool_t *pool);
> +
> /** @} end group: svnpatch related functions */
>
>
>
> Modified: branches/svnpatch-diff/subversion/libsvn_client/patch.c
> URL: http://svn.collab.net/viewvc/svn/branches/svnpatch-diff/subversion/libsvn_client/patch.c?pathrev=26210&r1=26209&r2=26210
> ==============================================================================
> --- branches/svnpatch-diff/subversion/libsvn_client/patch.c (original)
> +++ branches/svnpatch-diff/subversion/libsvn_client/patch.c Mon Aug 20 11:42:44 2007
> @@ -2,7 +2,7 @@
> * patch.c: wrapper around wc patch functionality.
> *
> * ====================================================================
> - * Copyright (c) 2000-2004 CollabNet. All rights reserved.
> + * Copyright (c) 2000-2006 CollabNet. All rights reserved.
> *
> * This software is licensed as described in the file COPYING, which
> * you should have received as part of this distribution. The terms
> @@ -27,19 +27,1659 @@
> #include "svn_client.h"
> #include "svn_config.h"
> #include "client.h"
> +#include "svn_io.h"
> +#include "svn_path.h"
> +#include "svn_pools.h"
> +#include "svn_base64.h"
> +#include "svn_string.h"
> +#include <assert.h>
>
> #include "svn_private_config.h"
>
>
> /*** Code. ***/
>
> +static const char equal_string[] =
> + "=========================";
> +
> +/*-----------------------------------------------------------------------*/
> +
> +/*** Utilities. ***/
> +/* Sanity check -- ensure that we have valid revisions to look at. */
> +#define ENSURE_VALID_REVISION_KINDS(rev1_kind, rev2_kind) \
> + if ((rev1_kind == svn_opt_revision_unspecified) \
> + || (rev2_kind == svn_opt_revision_unspecified)) \
> + { \
> + return svn_error_create \
> + (SVN_ERR_CLIENT_BAD_REVISION, NULL, \
> + _("Not all required revisions are specified")); \
> + }
> +
> +/*-----------------------------------------------------------------------*/
> +
> +
> +struct patch_cmd_baton {
> + svn_boolean_t force;
> + svn_boolean_t dry_run;
> +
> + /* Set to the dir path whenever the dir is added as a child of a
> + * versioned dir (dry-run only). */
> + const char *added_path;
> +
> + /* Working copy target path. */
> + const char *target;
> +
> + /* Client context for callbacks, etc. */
> + svn_client_ctx_t *ctx;
> +
> + /* The list of paths for entries we've deleted, used only when in
> + * dry_run mode. */
> + apr_hash_t *dry_run_deletions;
> +
> + apr_pool_t *pool;
> +};
> +
> +/* Used to avoid spurious notifications (e.g. conflicts) from a merge
> + attempt into an existing target which would have been deleted if we
> + weren't in dry_run mode (issue #2584). Assumes that WCPATH is
> + still versioned (e.g. has an associated entry). */
> +static APR_INLINE svn_boolean_t
> +dry_run_deleted_p(struct patch_cmd_baton *patch_b, const char *wcpath)
> +{
> + return (patch_b->dry_run &&
> + apr_hash_get(patch_b->dry_run_deletions, wcpath,
> + APR_HASH_KEY_STRING) != NULL);
> +}
> +
> +
> +
> +/* A svn_wc_diff_callbacks2_t function. Used for both file and directory
> + property merges. */
> +static svn_error_t *
> +merge_props_changed(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *path,
> + const apr_array_header_t *propchanges,
> + apr_hash_t *original_props,
> + void *baton)
> +{
> + apr_array_header_t *props;
> + struct patch_cmd_baton *patch_b = baton;
> + apr_pool_t *subpool = svn_pool_create(patch_b->pool);
> + svn_error_t *err;
> +
> + SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, subpool));
> +
> + /* We only want to merge "regular" version properties: by
> + definition, 'svn merge' shouldn't touch any data within .svn/,
> + so does 'svn patch'. */
> + if (props->nelts)
> + {
> + /* svn_wc_merge_props() requires ADM_ACCESS to be the access for
> + the parent of PATH. Since the advent of merge tracking,
> + discover_and_merge_children() may call this (indirectly) with
> + the access for the patch_b->target instead (issue #2781).
> + So, if we have the wrong access, get the right one. */
> + if (svn_path_compare_paths(svn_wc_adm_access_path(adm_access),
> + path) != 0)
> + SVN_ERR(svn_wc_adm_probe_try3(&adm_access, adm_access, path,
> + TRUE, -1, patch_b->ctx->cancel_func,
> + patch_b->ctx->cancel_baton, subpool));
> +
> + err = svn_wc_merge_props(state, path, adm_access, original_props, props,
> + FALSE, patch_b->dry_run, subpool);
> + if (err && (err->apr_err == SVN_ERR_ENTRY_NOT_FOUND
> + || err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE))
> + {
> + /* if the entry doesn't exist in the wc, just 'skip' over
> + this part of the tree-delta. */
> + if (state)
> + *state = svn_wc_notify_state_missing;
> + svn_error_clear(err);
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> + else if (err)
> + return err;
> + }
> +
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +/* A svn_wc_diff_callbacks2_t function. */
> +static svn_error_t *
> +merge_file_changed(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *content_state,
> + svn_wc_notify_state_t *prop_state,
> + const char *mine,
> + const char *older,
> + const char *yours,
> + svn_revnum_t older_rev,
> + svn_revnum_t yours_rev,
> + const char *mimetype1,
> + const char *mimetype2,
> + const apr_array_header_t *prop_changes,
> + apr_hash_t *original_props,
> + void *baton)
> +{
> + struct patch_cmd_baton *patch_b = baton;
> + apr_pool_t *subpool = svn_pool_create(patch_b->pool);
> + svn_boolean_t merge_required = TRUE;
> + enum svn_wc_merge_outcome_t merge_outcome;
> +
> + /* Easy out: no access baton means there ain't no merge target */
> + if (adm_access == NULL)
> + {
> + if (content_state)
> + *content_state = svn_wc_notify_state_missing;
> + if (prop_state)
> + *prop_state = svn_wc_notify_state_missing;
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> +
> + /* Other easy outs: if the merge target isn't under version
> + control, or is just missing from disk, fogettaboutit. There's no
> + way svn_wc_merge3() can do the merge. */
> + {
> + const svn_wc_entry_t *entry;
> + svn_node_kind_t kind;
> +
> + SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
> + SVN_ERR(svn_io_check_path(mine, &kind, subpool));
> +
> + /* ### a future thought: if the file is under version control,
> + but the working file is missing, maybe we can 'restore' the
> + working file from the text-base, and then allow the merge to run? */
> +
> + if ((! entry) || (kind != svn_node_file))
> + {
> + if (content_state)
> + *content_state = svn_wc_notify_state_missing;
> + if (prop_state)
> + *prop_state = svn_wc_notify_state_missing;
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> + }
> +
> + /* Do property merge before content merge so that keyword expansion takes
> + into account the new property values. */
> + if (prop_changes->nelts > 0)
> + SVN_ERR(merge_props_changed(adm_access, prop_state, mine, prop_changes,
> + original_props, baton));
> + else
> + if (prop_state)
> + *prop_state = svn_wc_notify_state_unchanged;
> +
> + /* Now with content modifications */
> + {
> + svn_boolean_t has_local_mods;
> + SVN_ERR(svn_wc_text_modified_p(&has_local_mods, mine, FALSE,
> + adm_access, subpool));
> +
> + /* Special case: if a binary file isn't locally modified, and is
> + exactly identical to the file content from the patch, then don't
> + allow svn_wc_merge to produce a conflict. Instead, just
> + overwrite the working file with the one from the patch. */
> + if (!has_local_mods && !patch_b->dry_run
> + && (mimetype2 && svn_mime_type_is_binary(mimetype2)))
> + {
> + SVN_ERR(svn_io_file_rename(yours, mine, subpool));
> + merge_outcome = svn_wc_merge_merged;
> + merge_required = FALSE;
> + }
> +
> + /* The binary file has local modifications, we'll use svn_wc_merge
> + * conflict facility to prompt the user and spawn backup files.
> + * Workaround: since svn_wc_merge needs 3 input files, we create an
> + * empty file which we remove when returning from svn_wc_merge. */
> + if (merge_required)
> + {
> + const char *target_label = _(".working");
> + const char *right_label = _(".patch");
> + const char *left_label = _(".empty");
> + const char *left;
> + SVN_ERR(svn_wc_create_tmp_file2
> + (NULL, &left,
> + svn_wc_adm_access_path(adm_access),
> + svn_io_file_del_on_pool_cleanup, subpool));
> + SVN_ERR(svn_wc_merge3(&merge_outcome,
> + left, yours, mine, adm_access,
> + left_label, right_label, target_label,
> + patch_b->dry_run,
> + NULL, /* no diff3 */
> + NULL, /* no merge_options */
> + prop_changes,
> + patch_b->ctx->conflict_func,
> + patch_b->ctx->conflict_baton,
> + subpool));
> + SVN_ERR(svn_io_remove_file
> + (apr_pstrcat(subpool, mine, left_label, NULL),
> + subpool));
> + }
> +
> + if (content_state)
> + {
> + if (merge_outcome == svn_wc_merge_conflict)
> + *content_state = svn_wc_notify_state_conflicted;
> + else if (has_local_mods
> + && merge_outcome != svn_wc_merge_unchanged)
> + *content_state = svn_wc_notify_state_merged;
> + else if (merge_outcome == svn_wc_merge_merged)
> + *content_state = svn_wc_notify_state_changed;
> + else if (merge_outcome == svn_wc_merge_no_merge)
> + *content_state = svn_wc_notify_state_missing;
> + else /* merge_outcome == svn_wc_merge_unchanged */
> + *content_state = svn_wc_notify_state_unchanged;
> + }
> + }
> +
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +/* A svn_wc_diff_callbacks2_t function. */
> +static svn_error_t *
> +merge_file_added(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *content_state,
> + svn_wc_notify_state_t *prop_state,
> + const char *mine,
> + const char *older,
> + const char *yours,
> + svn_revnum_t rev1,
> + svn_revnum_t rev2,
> + const char *mimetype1,
> + const char *mimetype2,
> + const apr_array_header_t *prop_changes,
> + apr_hash_t *original_props,
> + void *baton)
> +{
> + struct patch_cmd_baton *patch_b = baton;
> + apr_pool_t *subpool = svn_pool_create(patch_b->pool);
> + svn_node_kind_t kind;
> + const char *copyfrom_url;
> + const char *child;
> + int i;
> + apr_hash_t *new_props;
> +
> + /* In most cases, we just leave prop_state as unknown, and let the
> + content_state what happened, so we set prop_state here to avoid that
> + below. */
> + if (prop_state)
> + *prop_state = svn_wc_notify_state_unknown;
> +
> + /* Apply the prop changes to a new hash table. */
> + new_props = apr_hash_make(subpool);
> + for (i = 0; i < prop_changes->nelts; ++i)
> + {
> + const svn_prop_t *prop = &APR_ARRAY_IDX(prop_changes, i, svn_prop_t);
> + apr_hash_set(new_props, prop->name, APR_HASH_KEY_STRING, prop->value);
> + }
> +
> + /* Easy out: if we have no adm_access for the parent directory,
> + then this portion of the tree-delta "patch" must be inapplicable.
> + Send a 'missing' state back; the repos-diff editor should then
> + send a 'skip' notification. */
> + if (! adm_access)
> + {
> + if (patch_b->dry_run && patch_b->added_path
> + && svn_path_is_child(patch_b->added_path, mine, subpool))
> + {
> + if (content_state)
> + *content_state = svn_wc_notify_state_changed;
> + if (prop_state && apr_hash_count(new_props))
> + *prop_state = svn_wc_notify_state_changed;
> + }
> + else
> + *content_state = svn_wc_notify_state_missing;
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> +
> + SVN_ERR(svn_io_check_path(mine, &kind, subpool));
> + switch (kind)
> + {
> + case svn_node_none:
> + {
> + const svn_wc_entry_t *entry;
> + SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
> + if (entry && entry->schedule != svn_wc_schedule_delete)
> + {
> + /* It's versioned but missing. */
> + if (content_state)
> + *content_state = svn_wc_notify_state_obstructed;
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> + if (! patch_b->dry_run)
> + {
> + /* Since 'mine' doesn't exist, and this is
> + 'merge_file_added', I hope it's safe to assume that
> + 'older' is empty, and 'yours' is the full file. Merely
> + copying 'yours' to 'mine', isn't enough; we need to get
> + the whole text-base and props installed too, just as if
> + we had called 'svn cp wc wc'. */
> + SVN_ERR(svn_wc_add_repos_file2(mine, adm_access, yours, NULL,
> + new_props, NULL, NULL,
> + SVN_IGNORED_REVNUM, subpool));
> + }
> + if (content_state)
> + *content_state = svn_wc_notify_state_changed;
> + if (prop_state && apr_hash_count(new_props))
> + *prop_state = svn_wc_notify_state_changed;
> + }
> + break;
> + case svn_node_dir:
> + if (content_state)
> + {
> + /* directory already exists, is it under version control? */
> + const svn_wc_entry_t *entry;
> + SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
> +
> + if (entry && dry_run_deleted_p(patch_b, mine))
> + *content_state = svn_wc_notify_state_changed;
> + else
> + /* this will make the repos_editor send a 'skipped' message */
> + *content_state = svn_wc_notify_state_obstructed;
> + }
> + break;
> + case svn_node_file:
> + {
> + /* file already exists, is it under version control? */
> + const svn_wc_entry_t *entry;
> + SVN_ERR(svn_wc_entry(&entry, mine, adm_access, FALSE, subpool));
> +
> + /* If it's an unversioned file, don't touch it. If it's scheduled
> + for deletion, then rm removed it from the working copy and the
> + user must have recreated it, don't touch it */
> + if (!entry || entry->schedule == svn_wc_schedule_delete)
> + {
> + /* this will make the repos_editor send a 'skipped' message */
> + if (content_state)
> + *content_state = svn_wc_notify_state_obstructed;
> + }
> + else
> + {
> + if (dry_run_deleted_p(patch_b, mine))
> + {
> + if (content_state)
> + *content_state = svn_wc_notify_state_changed;
> + }
> + else
> + {
> + SVN_ERR(merge_file_changed
> + (adm_access, content_state,
> + prop_state, mine, NULL, yours,
> + SVN_IGNORED_REVNUM, SVN_IGNORED_REVNUM,
> + mimetype1, mimetype2,
> + prop_changes, original_props,
> + baton));
> + }
> + }
> + break;
> + }
> + default:
> + if (content_state)
> + *content_state = svn_wc_notify_state_unknown;
> + break;
> + }
> +
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +/* A svn_wc_diff_callbacks2_t function. */
> +static svn_error_t *
> +merge_file_deleted(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *mine,
> + const char *older,
> + const char *yours,
> + const char *mimetype1,
> + const char *mimetype2,
> + apr_hash_t *original_props,
> + void *baton)
> +{
> + struct patch_cmd_baton *patch_b = baton;
> + apr_pool_t *subpool = svn_pool_create(patch_b->pool);
> + svn_node_kind_t kind;
> + svn_wc_adm_access_t *parent_access;
> + const char *parent_path;
> + svn_error_t *err;
> +
> + /* Easy out: if we have no adm_access for the parent directory,
> + then this portion of the tree-delta "patch" must be inapplicable.
> + Send a 'missing' state back; the repos-diff editor should then
> + send a 'skip' notification. */
> + if (! adm_access)
> + {
> + if (state)
> + *state = svn_wc_notify_state_missing;
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> +
> + SVN_ERR(svn_io_check_path(mine, &kind, subpool));
> + switch (kind)
> + {
> + case svn_node_file:
> + svn_path_split(mine, &parent_path, NULL, subpool);
> + SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent_path,
> + subpool));
> + /* Passing NULL for the notify_func and notify_baton because
> + delete_entry() will do it for us. */
> + err = svn_client__wc_delete(mine, parent_access, patch_b->force,
> + patch_b->dry_run, FALSE, NULL, NULL,
> + patch_b->ctx, subpool);
> + if (err && state)
> + {
> + *state = svn_wc_notify_state_obstructed;
> + svn_error_clear(err);
> + }
> + else if (state)
> + {
> + *state = svn_wc_notify_state_changed;
> + }
> + break;
> + case svn_node_dir:
> + if (state)
> + *state = svn_wc_notify_state_obstructed;
> + break;
> + case svn_node_none:
> + /* file is already non-existent, this is a no-op. */
> + if (state)
> + *state = svn_wc_notify_state_missing;
> + break;
> + default:
> + if (state)
> + *state = svn_wc_notify_state_unknown;
> + break;
> + }
> +
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +/* A svn_wc_diff_callbacks2_t function. */
> +static svn_error_t *
> +merge_dir_added(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *path,
> + svn_revnum_t rev,
> + void *baton)
> +{
> + struct patch_cmd_baton *patch_b = baton;
> + apr_pool_t *subpool = svn_pool_create(patch_b->pool);
> + svn_node_kind_t kind;
> + const svn_wc_entry_t *entry;
> + const char *copyfrom_url, *child;
> +
> + /* Easy out: if we have no adm_access for the parent directory,
> + then this portion of the tree-delta "patch" must be inapplicable.
> + Send a 'missing' state back; the repos-diff editor should then
> + send a 'skip' notification. */
> + if (! adm_access)
> + {
> + if (state)
> + {
> + if (patch_b->dry_run && patch_b->added_path
> + && svn_path_is_child(patch_b->added_path, path, subpool))
> + *state = svn_wc_notify_state_changed;
> + else
> + *state = svn_wc_notify_state_missing;
> + }
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> +
> + child = svn_path_is_child(patch_b->target, path, subpool);
> + assert(child != NULL);
> +
> + SVN_ERR(svn_io_check_path(path, &kind, subpool));
> + switch (kind)
> + {
> + case svn_node_none:
> + SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, subpool));
> + if (entry && entry->schedule != svn_wc_schedule_delete)
> + {
> + /* Versioned but missing */
> + if (state)
> + *state = svn_wc_notify_state_obstructed;
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> + if (! patch_b->dry_run)
> + {
> + SVN_ERR(svn_io_make_dir_recursively(path, subpool));
> + SVN_ERR(svn_wc_add2(path, adm_access,
> + NULL, SVN_IGNORED_REVNUM,
> + patch_b->ctx->cancel_func,
> + patch_b->ctx->cancel_baton,
> + NULL, NULL, /* don't pass notification func! */
> + subpool));
> +
> + }
> + if (patch_b->dry_run)
> + patch_b->added_path = apr_pstrdup(patch_b->pool, path);
> + if (state)
> + *state = svn_wc_notify_state_changed;
> + break;
> + case svn_node_dir:
> + /* Adding an unversioned directory doesn't destroy data */
> + SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, subpool));
> + if (! entry || entry->schedule == svn_wc_schedule_delete)
> + {
> + if (!patch_b->dry_run)
> + SVN_ERR(svn_wc_add2(path, adm_access,
> + copyfrom_url, rev,
> + patch_b->ctx->cancel_func,
> + patch_b->ctx->cancel_baton,
> + NULL, NULL, /* no notification func! */
> + subpool));
> + if (patch_b->dry_run)
> + patch_b->added_path = apr_pstrdup(patch_b->pool, path);
> + if (state)
> + *state = svn_wc_notify_state_changed;
> + }
> + else if (state)
> + {
> + if (dry_run_deleted_p(patch_b, path))
> + *state = svn_wc_notify_state_changed;
> + else
> + *state = svn_wc_notify_state_obstructed;
> + }
> + break;
> + case svn_node_file:
> + if (patch_b->dry_run)
> + patch_b->added_path = NULL;
> +
> + if (state)
> + {
> + SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, subpool));
> +
> + if (entry && dry_run_deleted_p(patch_b, path))
> + /* ### TODO: Retain record of this dir being added to
> + ### avoid problems from subsequent edits which try to
> + ### add children. */
> + *state = svn_wc_notify_state_changed;
> + else
> + *state = svn_wc_notify_state_obstructed;
> + }
> + break;
> + default:
> + if (patch_b->dry_run)
> + patch_b->added_path = NULL;
> + if (state)
> + *state = svn_wc_notify_state_unknown;
> + break;
> + }
> +
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +/* Struct used for as the baton for calling merge_delete_notify_func(). */
> +typedef struct merge_delete_notify_baton_t
> +{
> + svn_client_ctx_t *ctx;
> +
> + /* path to skip */
> + const char *path_skip;
> +} merge_delete_notify_baton_t;
> +
> +/* Notify callback function that wraps the normal callback
> + * function to remove a notification that will be sent twice
> + * and set the proper action. */
> +static void
> +merge_delete_notify_func(void *baton,
> + const svn_wc_notify_t *notify,
> + apr_pool_t *pool)
> +{
> + merge_delete_notify_baton_t *mdb = baton;
> + svn_wc_notify_t *new_notify;
> +
> + /* Skip the notification for the path we called svn_client__wc_delete() with,
> + * because it will be outputed by repos_diff.c:delete_item */
> + if (strcmp(notify->path, mdb->path_skip) == 0)
> + return;
> +
> + /* svn_client__wc_delete() is written primarily for scheduling operations not
> + * update operations. Since merges are update operations we need to alter
> + * the delete notification to show as an update not a schedule so alter
> + * the action. */
> + if (notify->action == svn_wc_notify_delete)
> + {
> + /* We need to copy it since notify is const. */
> + new_notify = svn_wc_dup_notify(notify, pool);
> + new_notify->action = svn_wc_notify_update_delete;
> + notify = new_notify;
> + }
> +
> + if (mdb->ctx->notify_func2)
> + (*mdb->ctx->notify_func2)(mdb->ctx->notify_baton2, notify, pool);
> +}
> +
> +/* A svn_wc_diff_callbacks2_t function. */
> +static svn_error_t *
> +merge_dir_deleted(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *path,
> + void *baton)
> +{
> + struct patch_cmd_baton *patch_b = baton;
> + apr_pool_t *subpool = svn_pool_create(patch_b->pool);
> + svn_node_kind_t kind;
> + svn_wc_adm_access_t *parent_access;
> + const char *parent_path;
> + svn_error_t *err;
> +
> + /* Easy out: if we have no adm_access for the parent directory,
> + then this portion of the tree-delta "patch" must be inapplicable.
> + Send a 'missing' state back; the repos-diff editor should then
> + send a 'skip' notification. */
> + if (! adm_access)
> + {
> + if (state)
> + *state = svn_wc_notify_state_missing;
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> + }
> +
> + SVN_ERR(svn_io_check_path(path, &kind, subpool));
> + switch (kind)
> + {
> + case svn_node_dir:
> + {
> + merge_delete_notify_baton_t mdb;
> +
> + mdb.ctx = patch_b->ctx;
> + mdb.path_skip = path;
> +
> + svn_path_split(path, &parent_path, NULL, subpool);
> + SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent_path,
> + subpool));
> + err = svn_client__wc_delete(path, parent_access, patch_b->force,
> + patch_b->dry_run, FALSE,
> + merge_delete_notify_func, &mdb,
> + patch_b->ctx, subpool);
> + if (err && state)
> + {
> + *state = svn_wc_notify_state_obstructed;
> + svn_error_clear(err);
> + }
> + else if (state)
> + {
> + *state = svn_wc_notify_state_changed;
> + }
> + }
> + break;
> + case svn_node_file:
> + if (state)
> + *state = svn_wc_notify_state_obstructed;
> + break;
> + case svn_node_none:
> + /* dir is already non-existent, this is a no-op. */
> + if (state)
> + *state = svn_wc_notify_state_missing;
> + break;
> + default:
> + if (state)
> + *state = svn_wc_notify_state_unknown;
> + break;
> + }
> +
> + svn_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +/* The main callback table for 'svn patch'. We leave merge callback
> + * names as (a) they are pretty much merge operations (b) even if
> + * tweaked them to meet 'svn patch' needs, they do pretty much what
> + * their real sibblings do. */
> +static const svn_wc_diff_callbacks2_t
> +patch_callbacks =
> + {
> + merge_file_changed,
> + merge_file_added,
> + merge_file_deleted,
> + merge_dir_added,
> + merge_dir_deleted,
> + merge_props_changed
> + };
> +
> +struct edit_baton {
> + /* Directory against which 'svn patch' is run. */
> + const char *target;
> +
> + /* ADM_ACCESS is an access baton that includes the TARGET directory. */
> + svn_wc_adm_access_t *adm_access;
> +
> + /* Is it a dry-run patch application? */
> + svn_boolean_t dry_run;
> +
> + /* Empty hash used for adds. */
> + apr_hash_t *empty_hash;
> +
> + /* The path to a temporary empty file used for adds. The path is
> + * cached here so that it can be reused, since all empty files are the
> + * same. */
> + const char *empty_file;
> +
> + /* The merge callbacks array and its baton. */
> + const svn_wc_diff_callbacks2_t *diff_callbacks;
> + void *diff_cmd_baton;
> +
> + /* If the func is non-null, send notifications of actions. */
> + svn_wc_notify_func2_t notify_func;
> + void *notify_baton;
> +
> + apr_pool_t *pool;
> +};
> +
> +/* Directory level baton. */
> +struct dir_baton {
> + /* Gets set if the directory is added rather than replaced/unchanged. */
> + svn_boolean_t added;
> +
> + /* 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;
> +
> + /* 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;
> +
> + /* 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;
> +};
> +
> +/* File level baton. */
> +struct file_baton {
> + /* Gets set if the file is added rather than replaced. */
> + svn_boolean_t added;
> +
> + /* 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
> + * an incoming binary file from the patch's guts. */
> + const char *path_incoming;
> + apr_file_t *file_incoming;
> +
> + /* Whether this file is considered as binary. This flag is set upon
> + * apply-textdelta calls. */
> + svn_boolean_t is_binary;
> +
> + /* 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;
> +
> + /* The directory that contains the file. */
> + struct dir_baton *dir_baton;
> +
> + /* A cache of any property changes (svn_prop_t) received for this file. */
> + apr_array_header_t *propchanges;
> +
> + /* 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,
> + apr_pool_t *pool)
> +{
> + struct dir_baton *dir_baton = apr_pcalloc(pool, sizeof(*dir_baton));
> +
> + dir_baton->dir_baton = parent_baton;
> + dir_baton->edit_baton = edit_baton;
> + dir_baton->added = added;
> + dir_baton->pool = pool;
> + dir_baton->path = apr_pstrdup(pool, path);
> + dir_baton->wcpath = svn_path_join(edit_baton->target, path, pool);
> + dir_baton->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t));
> +
> + return dir_baton;
> +}
> +
> +/* 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,
> + svn_boolean_t added,
> + void *edit_baton,
> + struct dir_baton *parent_baton,
> + apr_pool_t *pool)
> +{
> + struct file_baton *file_baton = apr_pcalloc(pool, sizeof(*file_baton));
> + struct edit_baton *eb = edit_baton;
> +
> + file_baton->edit_baton = edit_baton;
> + file_baton->added = added;
> + file_baton->pool = pool;
> + file_baton->path = apr_pstrdup(pool, path);
> + file_baton->wcpath = svn_path_join(eb->target, path, pool);
> + file_baton->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t));
> + file_baton->dir_baton = parent_baton;
> + file_baton->is_binary = FALSE;
> +
> + return file_baton;
> +}
> +
> +/* Some utility functions. */
> +
> +/* Create an empty file, the path to the file is returned in
> + EMPTY_FILE_PATH. If ADM_ACCESS is not NULL and a lock is held,
> + create the file in the adm tmp/ area, otherwise use a system temp
> + directory.
> +
> + If FILE is non-NULL, an open file is returned in *FILE. */
> +static svn_error_t *
> +create_empty_file(apr_file_t **file,
> + const char **empty_file_path,
> + svn_wc_adm_access_t *adm_access,
> + svn_io_file_del_t delete_when,
> + apr_pool_t *pool)
> +{
> + if (adm_access && svn_wc_adm_locked(adm_access))
> + SVN_ERR(svn_wc_create_tmp_file2(file, empty_file_path,
> + svn_wc_adm_access_path(adm_access),
> + delete_when, pool));
> + else
> + {
> + const char *temp_dir;
> +
> + SVN_ERR(svn_io_temp_dir(&temp_dir, pool));
> + SVN_ERR(svn_io_open_unique_file2(file, empty_file_path,
> + svn_path_join(temp_dir, "tmp", pool),
> + "", delete_when, pool));
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Return in *PATH_ACCESS the access baton for the directory PATH by
> + searching the access baton set of ADM_ACCESS. If ADM_ACCESS is NULL
> + then *PATH_ACCESS will be NULL. If LENIENT is TRUE then failure to find
> + an access baton will not return an error but will set *PATH_ACCESS to
> + NULL instead. */
> +static svn_error_t *
> +get_path_access(svn_wc_adm_access_t **path_access,
> + svn_wc_adm_access_t *adm_access,
> + const char *path,
> + svn_boolean_t lenient,
> + apr_pool_t *pool)
> +{
> + if (! adm_access)
> + *path_access = NULL;
> + else
> + {
> + svn_error_t *err = svn_wc_adm_retrieve(path_access, adm_access, path,
> + pool);
> + if (err)
> + {
> + if (! lenient)
> + return err;
> + svn_error_clear(err);
> + *path_access = NULL;
> + }
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Like get_path_access except the returned access baton, in
> + *PARENT_ACCESS, is for the parent of PATH rather than for PATH
> + itself. */
> +static svn_error_t *
> +get_parent_access(svn_wc_adm_access_t **parent_access,
> + svn_wc_adm_access_t *adm_access,
> + const char *path,
> + svn_boolean_t lenient,
> + apr_pool_t *pool)
> +{
> + if (! adm_access)
> + *parent_access = NULL; /* Avoid messing around with paths */
> + else
> + {
> + const char *parent_path = svn_path_dirname(path, pool);
> + SVN_ERR(get_path_access(parent_access, adm_access, parent_path,
> + lenient, pool));
> + }
> + return SVN_NO_ERROR;
> +}
> +
> +/* 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 r17220, but
> + that won't work on Windows: it's impossible to stat NUL */
> + if (!eb->empty_file)
> + SVN_ERR(create_empty_file(NULL, &(eb->empty_file), eb->adm_access,
> + svn_io_file_del_on_pool_cleanup, eb->pool));
> +
> +
> + *empty_file_path = eb->empty_file;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Implementation of svn_delta_editor_t vtable. */
> +
> +/* An editor 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 *b = make_dir_baton("", NULL, eb, FALSE, pool);
> +
> + /* Override the wcpath in our baton. */
> + b->wcpath = apr_pstrdup(pool, eb->target);
> +
> + *root_baton = b;
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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;
> + svn_wc_adm_access_t *adm_access;
> + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable;
> + svn_wc_notify_action_t action = svn_wc_notify_skip;
> +
> + /* We need to know if this is a directory or a file. Unfortunately,
> + * if @a path is missing (e.g. user removes manually), this check
> + * below returns svn_node_none and a discrepancy shows up when
> + * notifying the world: we get a 'D' instead of a 'Skipped missing
> + * target'. One day we want to provide svnpatch's delete-entry
> + * command with a hint on what this thing -- path -- really is, since
> + * svnpatch application takes place offline as opposed to merge. This
> + * would help the following switch fall in the right case, and thus
> + * clean our discrepancy. */
> + SVN_ERR(svn_io_check_path(path, &kind, pool));
> + SVN_ERR(get_path_access(&adm_access, eb->adm_access, pb->wcpath,
> + TRUE, pool));
> + if ((! eb->adm_access) || adm_access)
> + {
> + switch (kind)
> + {
> + case svn_node_file:
> + {
> + struct file_baton *b;
> +
> + /* Compare a file being deleted against an empty file */
> + b = make_file_baton(path, FALSE, eb, pb, pool);
> +
> + SVN_ERR(eb->diff_callbacks->file_deleted
> + (adm_access, &state, b->wcpath,
> + NULL, NULL, NULL, NULL, NULL, /* useless for del */
> + b->edit_baton->diff_cmd_baton));
> +
> + break;
> + }
> + case svn_node_dir:
> + {
> + SVN_ERR(eb->diff_callbacks->dir_deleted
> + (adm_access, &state,
> + svn_path_join(eb->target, path, pool),
> + eb->diff_cmd_baton));
> + break;
> + }
> + default:
> + break;
> + }
> +
> + if ((state != svn_wc_notify_state_missing)
> + && (state != svn_wc_notify_state_obstructed))
> + {
> + action = svn_wc_notify_update_delete;
> + if (eb->dry_run)
> + {
> + /* Remember what we _would've_ deleted (issue #2584). */
> + const char *wcpath = svn_path_join(eb->target, path, pb->pool);
> + apr_hash_set(svn_client__dry_run_deletions(eb->diff_cmd_baton),
> + wcpath, APR_HASH_KEY_STRING, wcpath);
> +
> + /* ### TODO: if (kind == svn_node_dir), record all
> + ### children as deleted to avoid collisions from
> + ### subsequent edits. */
> + }
> + }
> + }
> +
> + if (eb->notify_func)
> + {
> + svn_wc_notify_t *notify
> + = svn_wc_create_notify(svn_path_join(eb->target, path, pool),
> + action, pool);
> + notify->kind = kind;
> + notify->content_state = notify->prop_state = state;
> + notify->lock_state = svn_wc_notify_lock_state_inapplicable;
> + (*eb->notify_func)(eb->notify_baton, notify, pool);
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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 *b;
> + svn_wc_adm_access_t *adm_access;
> + svn_wc_notify_state_t state;
> + svn_wc_notify_action_t action;
> +
> + /* ### TODO: support copyfrom? */
> +
> + b = make_dir_baton(path, pb, eb, TRUE, pool);
> + *child_baton = b;
> +
> + SVN_ERR(get_path_access(&adm_access, eb->adm_access, pb->wcpath, TRUE,
> + pool));
> +
> + SVN_ERR(eb->diff_callbacks->dir_added
> + (adm_access, &state, b->wcpath, SVN_IGNORED_REVNUM,
> + eb->diff_cmd_baton));
> +
> + 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;
> +
> + if (eb->notify_func)
> + {
> + svn_wc_notify_t *notify = svn_wc_create_notify(b->wcpath, action, pool);
> + notify->kind = svn_node_dir;
> + (*eb->notify_func)(eb->notify_baton, notify, pool);
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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 dir_baton *b;
> + struct edit_baton *eb = pb->edit_baton;
> +
> + b = make_dir_baton(path, pb, pb->edit_baton, FALSE, pool);
> + *child_baton = b;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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 *b;
> +
> + /* ### TODO: support copyfrom? */
> +
> + b = make_file_baton(path, TRUE, pb->edit_baton, pb, pool);
> + *file_baton = b;
> +
> + /* We want to schedule this file for addition. */
> + SVN_ERR(get_empty_file(b->edit_baton, &(b->path_incoming)));
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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 *b;
> + struct edit_baton *eb = pb->edit_baton;
> +
> + b = make_file_baton(path, FALSE, pb->edit_baton, pb, pool);
> + *file_baton = b;
> +
> + 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 *b = window_baton;
> +
> + SVN_ERR(b->apply_handler(window, b->apply_baton));
> +
> + if (!window)
> + SVN_ERR(svn_io_file_close(b->file_incoming, b->pool));
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor function. */
> +static svn_error_t *
> +apply_textdelta(void *file_baton,
> + const char *base_checksum,
> + apr_pool_t *pool,
> + svn_txdelta_window_handler_t *handler,
> + void **handler_baton)
> +{
> + struct file_baton *b = file_baton;
> + struct edit_baton *eb = b->edit_baton;
> + svn_wc_adm_access_t *adm_access;
> +
> + /* This must be a binary file since, in a svnpatch context, we're only
> + * carrying txdeltas from binary files. */
> + b->is_binary = TRUE;
> +
> + if (b->edit_baton->adm_access)
> + {
> + svn_error_t *err;
> +
> + err = svn_wc_adm_probe_retrieve(&adm_access, b->edit_baton->adm_access,
> + b->wcpath, pool);
> + if (err)
> + {
> + svn_error_clear(err);
> + adm_access = NULL;
> + }
> + }
> + else
> + adm_access = NULL;
> +
> + SVN_ERR(create_empty_file(&(b->file_incoming),
> + &(b->path_incoming), adm_access,
> + svn_io_file_del_none, b->pool));
> +
> + /* svnpatch's txdeltas are svn_txdelta_source-action-less, i.e. we
> + * don't need any source stream here as bytes are written directly to
> + * the target stream. */
> + svn_txdelta_apply(NULL,
> + svn_stream_from_aprfile(b->file_incoming, b->pool),
> + NULL, b->path, b->pool,
> + &(b->apply_handler), &(b->apply_baton));
> +
> + *handler = window_handler;
> + *handler_baton = file_baton;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor function. When the file is closed we have a temporary file
> + * containing a pristine version of the file from the patch. 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 *text_checksum,
> + apr_pool_t *pool)
> +{
> + struct file_baton *b = file_baton;
> + struct edit_baton *eb = b->edit_baton;
> + svn_wc_adm_access_t *adm_access;
> + svn_error_t *err;
> + svn_wc_notify_action_t action;
> + svn_wc_notify_state_t
> + content_state = svn_wc_notify_state_unknown,
> + prop_state = svn_wc_notify_state_unknown;
> +
> + err = get_parent_access(&adm_access, eb->adm_access,
> + b->wcpath, eb->dry_run, b->pool);
> +
> + if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
> + {
> + /* ### maybe try to stat the local b->wcpath? */
> + /* If the file path doesn't exist, then send a 'skipped' notification. */
> + if (eb->notify_func)
> + {
> + svn_wc_notify_t *notify = svn_wc_create_notify(b->wcpath,
> + svn_wc_notify_skip,
> + pool);
> + notify->kind = svn_node_file;
> + notify->content_state = svn_wc_notify_state_missing;
> + notify->prop_state = prop_state;
> + (*eb->notify_func)(eb->notify_baton, notify, pool);
> + }
> +
> + svn_error_clear(err);
> + return SVN_NO_ERROR;
> + }
> + else if (err)
> + return err;
> +
> + if (b->path_incoming || b->propchanges->nelts > 0)
> + {
> + const char *mimetype
> + = b->is_binary ? "application/octet-stream" : NULL;
> +
> + if (b->added)
> + SVN_ERR(eb->diff_callbacks->file_added
> + (adm_access, &content_state, &prop_state,
> + b->wcpath,
> + NULL,
> + b->path_incoming,
> + SVN_IGNORED_REVNUM,
> + SVN_IGNORED_REVNUM,
> + NULL, mimetype,
> + b->propchanges, NULL,
> + b->edit_baton->diff_cmd_baton));
> + else
> + SVN_ERR(eb->diff_callbacks->file_changed
> + (adm_access, &content_state, &prop_state,
> + b->wcpath,
> + NULL,
> + b->path_incoming,
> + SVN_IGNORED_REVNUM,
> + SVN_IGNORED_REVNUM,
> + NULL, mimetype,
> + b->propchanges, NULL,
> + b->edit_baton->diff_cmd_baton));
> + }
> +
> +
> + 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;
> +
> + if (eb->notify_func)
> + {
> + svn_wc_notify_t *notify = svn_wc_create_notify(b->wcpath, action,
> + pool);
> + notify->kind = svn_node_file;
> + notify->content_state = content_state;
> + notify->prop_state = prop_state;
> + (*eb->notify_func)(eb->notify_baton, notify, pool);
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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 prop_state = svn_wc_notify_state_unknown;
> + svn_error_t *err;
> +
> + if (eb->dry_run)
> + svn_hash__clear(svn_client__dry_run_deletions(eb->diff_cmd_baton));
> +
> + if (b->propchanges->nelts > 0)
> + {
> + svn_wc_adm_access_t *adm_access;
> + err = get_path_access(&adm_access, eb->adm_access, b->wcpath,
> + eb->dry_run, b->pool);
> +
> + if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
> + {
> + /* ### maybe try to stat the local b->wcpath? */
> + /* If the path doesn't exist, then send a 'skipped' notification. */
> + if (eb->notify_func)
> + {
> + svn_wc_notify_t *notify
> + = svn_wc_create_notify(b->wcpath, 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_error_clear(err);
> + return SVN_NO_ERROR;
> + }
> + else if (err)
> + return err;
> +
> + /* 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 (! eb->dry_run || adm_access)
> + SVN_ERR(eb->diff_callbacks->dir_props_changed
> + (adm_access, &prop_state,
> + b->wcpath,
> + b->propchanges, NULL,
> + b->edit_baton->diff_cmd_baton));
> + }
> +
> + /* ### Don't notify added directories as they triggered notification
> + in add_directory. Does this mean that directory notification
> + isn't getting all the information? */
> + if (!b->added && eb->notify_func)
> + {
> + svn_wc_notify_t *notify
> + = svn_wc_create_notify(b->wcpath, svn_wc_notify_update_update, pool);
> + notify->kind = svn_node_dir;
> + notify->content_state = svn_wc_notify_state_inapplicable;
> + notify->prop_state = prop_state;
> + notify->lock_state = svn_wc_notify_lock_state_inapplicable;
> + (*eb->notify_func)(eb->notify_baton, notify, pool);
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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 edit_baton *eb = b->edit_baton;
> + svn_prop_t *propchange;
> +
> + propchange = apr_array_push(b->propchanges);
> + propchange->name = apr_pstrdup(b->pool, name);
> + propchange->value = value ? svn_string_dup(value, b->pool) : NULL;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* An editor 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;
> + struct edit_baton *eb = db->edit_baton;
> + svn_prop_t *propchange;
> +
> + 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 editor 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;
> +}
> +
> +static struct edit_baton *
> +make_editor_baton(const char *target,
> + svn_wc_adm_access_t *adm_access,
> + svn_boolean_t dry_run,
> + const svn_wc_diff_callbacks2_t *callbacks,
> + void *patch_cmd_baton,
> + svn_wc_notify_func2_t notify_func,
> + void *notify_baton,
> + const svn_delta_editor_t **editor,
> + apr_pool_t *pool)
> +{
> + apr_pool_t *subpool = svn_pool_create(pool);
> + struct edit_baton *eb = apr_pcalloc(subpool, sizeof(*eb));
> + svn_delta_editor_t *tree_editor = svn_delta_default_editor(subpool);
> +
> + eb->target = target;
> + eb->adm_access = adm_access;
> + eb->dry_run = dry_run;
> + eb->empty_hash = apr_hash_make(subpool);
> + eb->diff_callbacks = callbacks;
> + eb->diff_cmd_baton = patch_cmd_baton;
> + eb->notify_func = notify_func;
> + eb->notify_baton = notify_baton;
> + eb->pool = subpool;
> +
> + 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->change_dir_prop = change_dir_prop;
> + 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->close_file = close_file;
> + tree_editor->close_edit = close_edit;
> +
> + *editor = tree_editor;
> +
> + /* subpool is destroyed upon close_edit() */
> + return eb;
> +}
> +
> +
> +/* Extract and uncompress-decode the svnpatch block that's in @a
> + * original_patch_path, and fill @a *patch_file with its clear-text
> + * format. */
> +svn_error_t *
> +extract_svnpatch(const char *original_patch_path,
> + apr_file_t **patch_file,
> + apr_pool_t *pool)
> +{
> + apr_file_t *original_patch_file; /* gzip-base64'ed */
> + svn_stream_t *original_patch_stream;
> + apr_file_t *compressed_file; /* base64-decoded, gzip-compressed */
> + svn_stream_t *compressed_stream;
> + svn_stream_t *svnpatch_stream; /* clear-text, attached to @a patch_file */
> + const char *tempdir;
> + svn_string_t *svnpatch_header;
> + svn_stringbuf_t *patch_line;
> + apr_pool_t *subpool = svn_pool_create(pool);
> +
> + /* We assume both clients have the same version for now. */
> + svnpatch_header = svn_string_createf(pool,
> + "%s SVNPATCH%d BLOCK %s",
> + equal_string,
> + SVN_CLIENT_SVNPATCH_VERSION,
> + equal_string);
> +
> + SVN_ERR(svn_io_file_open(&original_patch_file, original_patch_path,
> + APR_READ, APR_OS_DEFAULT, pool));
> + original_patch_stream = svn_stream_from_aprfile2(original_patch_file,
> + FALSE, pool);
> +
> + while (1)
> + {
> + svn_pool_clear(subpool);
> + svn_boolean_t eof;
> + SVN_ERR(svn_stream_readline(original_patch_stream, &patch_line, "\n",
> + &eof, subpool));
> + /* No need to go deeper down the stack when the first char isn't
> + * even '='. */
> + if (*patch_line->data == '='
> + && svn_string_compare_stringbuf(svnpatch_header, patch_line))
> + break;
> +
> + /* @a original_patch_path doesn't contain the svnpatch block
> + * we're looking for. */
> + if (eof)
> + {
> + *patch_file = NULL;
> + return SVN_NO_ERROR;
> + }
> + }
> + svn_pool_destroy(subpool);
> +
> + /* At this point, original_patch_stream's cursor points right after the
> + * svnpatch header, that is, the bytes we're interested in,
> + * gzip-base64'ed. So create the temp file that will carry clear-text
> + * Editor commands for later work, decode the svnpatch chunk we have
> + * in hand, and write to it. */
> + SVN_ERR(svn_io_temp_dir(&tempdir, pool));
> + SVN_ERR(svn_io_open_unique_file2
> + (patch_file, NULL,
> + svn_path_join(tempdir, "patch", pool),
> + "", svn_io_file_del_none, pool));
> + svnpatch_stream = svn_stream_from_aprfile(*patch_file, pool);
> +
> + /* Oh, and we can't gzip-base64 decode in one step since
> + * svn_base64_decode wraps a write-decode handler and
> + * svn_stream_compressed wraps a write-compress handler. We split the
> + * pipe out in two here, with an intermediate temp-file. If someone
> + * feels like hacking libsvn_subr, we could either add a read-decode
> + * handler to svn_base64_decode or have a new svn_stream_decompressed
> + * (as opposed to svn_stream_compressed) to skip this
> + * {time,IO}-consuming workaround. */
> + SVN_ERR(svn_io_open_unique_file2
> + (&compressed_file, NULL,
> + svn_path_join(tempdir, "compressedpatch", pool),
> + "", svn_io_file_del_on_close, pool));
> + compressed_stream = svn_base64_decode
> + (svn_stream_from_aprfile2
> + (compressed_file, FALSE, pool), pool);
> + SVN_ERR(svn_stream_copy(original_patch_stream, compressed_stream, pool));
> +
> + {
> + /* Rewind. */
> + apr_off_t offset = 0;
> + SVN_ERR(svn_io_file_seek(compressed_file,
> + APR_SET, &offset, pool));
> + }
> +
> + compressed_stream = svn_stream_compressed
> + (svn_stream_from_aprfile2
> + (compressed_file, FALSE, pool), pool);
> + SVN_ERR(svn_stream_copy(compressed_stream, svnpatch_stream, pool));
> + SVN_ERR(svn_stream_close(svnpatch_stream));
> +
> + /* TODO: wrap errors? */
> +
> + {
> + /* Rewind so that next reads don't get it wrong. */
> + apr_off_t offset = 0;
> + SVN_ERR(svn_io_file_seek(*patch_file,
> + APR_SET, &offset, pool));
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> svn_error_t *
> svn_client_patch(const char *patch_path,
> const char *wc_path,
> + svn_boolean_t force,
> svn_boolean_t dry_run,
> svn_client_ctx_t *ctx,
> apr_pool_t *pool)
> {
> - /*...*/
> + apr_file_t *decoded_patch_file;
> + struct patch_cmd_baton patch_cmd_baton;
> + const svn_delta_editor_t *diff_editor;
> + svn_wc_adm_access_t *adm_access;
> + struct edit_baton *eb;
> +
> + /* Pull out the svnpatch block. */
> + SVN_ERR(extract_svnpatch(patch_path, &decoded_patch_file, pool));
> +
> + if (decoded_patch_file)
> + {
> + /* Get ready with the editor baton. */
> + patch_cmd_baton.force = force;
> + patch_cmd_baton.dry_run = dry_run;
> + patch_cmd_baton.added_path = NULL;
> + patch_cmd_baton.target = wc_path;
> + patch_cmd_baton.ctx = ctx;
> + patch_cmd_baton.dry_run_deletions = (dry_run ? apr_hash_make(pool)
> + : NULL);
> + patch_cmd_baton.pool = pool;
> +
> + SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, wc_path,
> + TRUE, -1, NULL, NULL, pool));
> + eb = make_editor_baton(wc_path, adm_access, dry_run,
> + &patch_callbacks, &patch_cmd_baton,
> + ctx->notify_func2, ctx->notify_baton2,
> + &diff_editor, pool);
> +
> + /* It's time to drive diff_editor and apply changes to the working
> + * copy. */
> + SVN_ERR(svn_wc_apply_patch(decoded_patch_file, diff_editor,
> + eb, pool));
> +
> + }
> +
> return SVN_NO_ERROR;
> }
>
> Added: branches/svnpatch-diff/subversion/libsvn_wc/patch.c
> URL: http://svn.collab.net/viewvc/svn/branches/svnpatch-diff/subversion/libsvn_wc/patch.c?pathrev=26210
> ==============================================================================
> --- (empty file)
> +++ branches/svnpatch-diff/subversion/libsvn_wc/patch.c Mon Aug 20 11:42:44 2007
> @@ -0,0 +1,435 @@
> +/*
> + * patch.c: apply a patch to a working tree.
> + *
> + * ====================================================================
> + * Copyright (c) 2000-2006 CollabNet. All rights reserved.
> + *
> + * This software is licensed as described in the file COPYING, which
> + * you should have received as part of this distribution. The terms
> + * are also available at http://subversion.tigris.org/license-1.html.
> + * If newer versions of this license are posted there, you may use a
> + * newer version instead, at your option.
> + *
> + * This software consists of voluntary contributions made by many
> + * individuals. For exact contribution history, see the revision
> + * history and logs, available at http://subversion.tigris.org/.
> + * ====================================================================
> + */
> +
> +/* ==================================================================== */
> +
> +
> +
> +/*** Includes. ***/
> +
> +#include "svn_wc.h"
> +#include "svn_client.h"
> +#include "svn_config.h"
> +#include "svn_io.h"
> +#include "svn_path.h"
> +#include "svn_pools.h"
> +#include "svn_string.h"
> +
> +#include "svn_private_config.h"
> +
> +/* Note: although 'svn patch' application is an offline operation, we'll
> + * find some ra_svn* structures below. This is because svnpatch is made
> + * of Editor Commands that ra_svn uses, thus it makes sense to use
> + * those here. */
> +
> +
> +/*** Code. ***/
> +
> +typedef struct {
> + const svn_delta_editor_t *editor;
> + void *edit_baton;
> + apr_hash_t *tokens;
> + apr_pool_t *pool;
> + apr_pool_t *file_pool;
> + int file_refs;
> +} ra_svn_driver_state_t;
> +
> +typedef struct {
> + const char *token;
> + void *baton;
> + svn_boolean_t is_file;
> + svn_stream_t *dstream; /* svndiff stream for apply_textdelta */
> + apr_pool_t *pool;
> +} ra_svn_token_entry_t;
> +
> +/* Store a token entry. The token string will be copied into pool. */
> +static ra_svn_token_entry_t *
> +store_token(ra_svn_driver_state_t *ds,
> + void *baton,
> + const char *token,
> + svn_boolean_t is_file,
> + apr_pool_t *pool)
> +{
> + ra_svn_token_entry_t *entry;
> +
> + entry = apr_palloc(pool, sizeof(*entry));
> + entry->token = apr_pstrdup(pool, token);
> + entry->baton = baton;
> + entry->is_file = is_file;
> + entry->dstream = NULL;
> + entry->pool = pool;
> + apr_hash_set(ds->tokens, entry->token, APR_HASH_KEY_STRING, entry);
> + return entry;
> +}
> +
> +static svn_error_t *
> +lookup_token(ra_svn_driver_state_t *ds,
> + const char *token,
> + svn_boolean_t is_file,
> + ra_svn_token_entry_t **entry)
> +{
> + *entry = apr_hash_get(ds->tokens, token, APR_HASH_KEY_STRING);
> + if (!*entry || (*entry)->is_file != is_file)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Invalid file or dir token during edit"));
> + return SVN_NO_ERROR;
> +}
> +
> +
> +static svn_error_t *
> +handle_open_root(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + apr_pool_t *subpool;
> + const char *token;
> + void *root_baton;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "c", &token));
> + subpool = svn_pool_create(ds->pool);
> + SVN_CMD_ERR(ds->editor->open_root(ds->edit_baton,
> + SVN_INVALID_REVNUM, subpool,
> + &root_baton));
> + store_token(ds, root_baton, token, FALSE, subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_delete_entry(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *path, *token;
> + ra_svn_token_entry_t *entry;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "cc", &path, &token));
> + SVN_ERR(lookup_token(ds, token, FALSE, &entry));
> + path = svn_path_canonicalize(path, pool);
> + SVN_CMD_ERR(ds->editor->delete_entry(path, SVN_INVALID_REVNUM,
> + entry->baton, pool));
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_add_dir(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *path, *token, *child_token, *copy_path;
> + ra_svn_token_entry_t *entry;
> + apr_pool_t *subpool;
> + void *child_baton;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "ccc(?c)", &path, &token,
> + &child_token, ©_path));
> + SVN_ERR(lookup_token(ds, token, FALSE, &entry));
> + subpool = svn_pool_create(entry->pool);
> + path = svn_path_canonicalize(path, pool);
> + if (copy_path)
> + copy_path = svn_path_canonicalize(copy_path, pool);
> + SVN_CMD_ERR(ds->editor->add_directory(path, entry->baton, copy_path,
> + SVN_INVALID_REVNUM, subpool,
> + &child_baton));
> + store_token(ds, child_baton, child_token, FALSE, subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_open_dir(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *path, *token, *child_token;
> + ra_svn_token_entry_t *entry;
> + apr_pool_t *subpool;
> + void *child_baton;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "ccc", &path, &token,
> + &child_token));
> + SVN_ERR(lookup_token(ds, token, FALSE, &entry));
> + subpool = svn_pool_create(entry->pool);
> + path = svn_path_canonicalize(path, pool);
> + SVN_CMD_ERR(ds->editor->open_directory(path, entry->baton,
> + SVN_INVALID_REVNUM, subpool,
> + &child_baton));
> + store_token(ds, child_baton, child_token, FALSE, subpool);
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_change_dir_prop(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *token, *name;
> + svn_string_t *value;
> + ra_svn_token_entry_t *entry;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "cc(?s)", &token, &name,
> + &value));
> + SVN_ERR(lookup_token(ds, token, FALSE, &entry));
> + SVN_CMD_ERR(ds->editor->change_dir_prop(entry->baton, name, value,
> + entry->pool));
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_close_dir(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *token;
> + ra_svn_token_entry_t *entry;
> +
> + /* Parse and look up the directory token. */
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "c", &token));
> + SVN_ERR(lookup_token(ds, token, FALSE, &entry));
> +
> + /* Close the directory and destroy the baton. */
> + SVN_CMD_ERR(ds->editor->close_directory(entry->baton, pool));
> + apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL);
> + apr_pool_destroy(entry->pool);
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_add_file(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *path, *token, *file_token, *copy_path;
> + ra_svn_token_entry_t *entry, *file_entry;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "ccc(?c)", &path, &token,
> + &file_token, ©_path));
> + SVN_ERR(lookup_token(ds, token, FALSE, &entry));
> + ds->file_refs++;
> + path = svn_path_canonicalize(path, pool);
> + if (copy_path)
> + copy_path = svn_path_canonicalize(copy_path, pool);
> + file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
> + SVN_CMD_ERR(ds->editor->add_file(path, entry->baton, copy_path,
> + SVN_INVALID_REVNUM, ds->file_pool,
> + &file_entry->baton));
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_open_file(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *path, *token, *file_token;
> + ra_svn_token_entry_t *entry, *file_entry;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "ccc", &path, &token,
> + &file_token));
> + SVN_ERR(lookup_token(ds, token, FALSE, &entry));
> + ds->file_refs++;
> + path = svn_path_canonicalize(path, pool);
> + file_entry = store_token(ds, NULL, file_token, TRUE, ds->file_pool);
> + SVN_CMD_ERR(ds->editor->open_file(path, entry->baton,
> + SVN_INVALID_REVNUM, ds->file_pool,
> + &file_entry->baton));
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_apply_textdelta(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *token;
> + ra_svn_token_entry_t *entry;
> + svn_txdelta_window_handler_t wh;
> + void *wh_baton;
> + char *base_checksum;
> +
> + /* Parse arguments and look up the token. */
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "c(?c)",
> + &token, &base_checksum));
> + SVN_ERR(lookup_token(ds, token, TRUE, &entry));
> + if (entry->dstream)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Apply-textdelta already active"));
> + entry->pool = svn_pool_create(ds->file_pool);
> + SVN_CMD_ERR(ds->editor->apply_textdelta(entry->baton, base_checksum,
> + entry->pool, &wh, &wh_baton));
> + entry->dstream = svn_txdelta_parse_svndiff(wh, wh_baton, TRUE, entry->pool);
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_textdelta_chunk(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *token;
> + ra_svn_token_entry_t *entry;
> + svn_string_t *str;
> +
> + /* Parse arguments and look up the token. */
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "cs", &token, &str));
> + SVN_ERR(lookup_token(ds, token, TRUE, &entry));
> + if (!entry->dstream)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Apply-textdelta not active"));
> + SVN_CMD_ERR(svn_stream_write(entry->dstream, str->data, &str->len));
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_textdelta_end(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *token;
> + ra_svn_token_entry_t *entry;
> +
> + /* Parse arguments and look up the token. */
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "c", &token));
> + SVN_ERR(lookup_token(ds, token, TRUE, &entry));
> + if (!entry->dstream)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Apply-textdelta not active"));
> + SVN_CMD_ERR(svn_stream_close(entry->dstream));
> + entry->dstream = NULL;
> + apr_pool_destroy(entry->pool);
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_change_file_prop(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *token, *name;
> + svn_string_t *value;
> + ra_svn_token_entry_t *entry;
> +
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "cc(?s)", &token, &name,
> + &value));
> + SVN_ERR(lookup_token(ds, token, TRUE, &entry));
> + SVN_CMD_ERR(ds->editor->change_file_prop(entry->baton, name, value, pool));
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_close_file(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + const char *token;
> + ra_svn_token_entry_t *entry;
> + const char *text_checksum;
> +
> + /* Parse arguments and look up the file token. */
> + SVN_ERR(svn_wc_parse_tuple(params, pool, "c(?c)",
> + &token, &text_checksum));
> + SVN_ERR(lookup_token(ds, token, TRUE, &entry));
> +
> + /* Close the file and destroy the baton. */
> + SVN_CMD_ERR(ds->editor->close_file(entry->baton, text_checksum, pool));
> + apr_hash_set(ds->tokens, token, APR_HASH_KEY_STRING, NULL);
> + if (--ds->file_refs == 0)
> + apr_pool_clear(ds->file_pool);
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +handle_close_edit(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds)
> +{
> + SVN_CMD_ERR(ds->editor->close_edit(ds->edit_baton, pool));
> + return SVN_NO_ERROR;
> +}
> +
> +static const struct {
> + const char *cmd;
> + svn_error_t *(*handler)(apr_pool_t *pool,
> + apr_array_header_t *params,
> + ra_svn_driver_state_t *ds);
> +} edit_cmds[] = {
> + { "open-root", handle_open_root },
> + { "delete-entry", handle_delete_entry },
> + { "add-dir", handle_add_dir },
> + { "open-dir", handle_open_dir },
> + { "change-dir-prop", handle_change_dir_prop },
> + { "close-dir", handle_close_dir },
> + { "add-file", handle_add_file },
> + { "open-file", handle_open_file },
> + { "apply-textdelta", handle_apply_textdelta },
> + { "textdelta-chunk", handle_textdelta_chunk },
> + { "textdelta-end", handle_textdelta_end },
> + { "change-file-prop", handle_change_file_prop },
> + { "close-file", handle_close_file },
> + { "close-edit", handle_close_edit },
> + { NULL }
> +};
> +
> +
> +/* Drive @a diff_editor against @a decoded_patch_file's Editor Commands. */
> +svn_error_t *
> +svn_wc_apply_patch(apr_file_t *decoded_patch_file,
> + const svn_delta_editor_t *diff_editor,
> + void *diff_edit_baton,
> + apr_pool_t *pool)
> +{
> + svn_stream_t *patch_stream;
> + ra_svn_driver_state_t state;
> + apr_pool_t *subpool = svn_pool_create(pool);
> + const char *cmd;
> + int i;
> + svn_error_t *err;
> + apr_array_header_t *params;
> +
> + patch_stream = svn_stream_from_aprfile2(decoded_patch_file, FALSE, pool);
> +
> + state.editor = diff_editor;
> + state.edit_baton = diff_edit_baton;
> + state.tokens = apr_hash_make(pool);
> + state.pool = pool;
> + state.file_pool = svn_pool_create(pool);
> + state.file_refs = 0;
> +
> + while (1)
> + {
> + apr_pool_clear(subpool);
> + SVN_ERR(svn_wc_read_tuple(patch_stream, subpool, "wl", &cmd, ¶ms));
> + for (i = 0; edit_cmds[i].cmd; i++)
> + if (strcmp(cmd, edit_cmds[i].cmd) == 0)
> + break;
> + if (edit_cmds[i].cmd)
> + err = (*edit_cmds[i].handler)(subpool, params, &state);
> + else
> + {
> + err = svn_error_createf(SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
> + _("Unknown command '%s'"), cmd);
> + err = svn_error_create(SVN_ERR_RA_SVN_CMD_ERR, err, NULL);
> + }
> + SVN_ERR(err);
> + /* TODO: handle & wrap errors */
> +
> + if (strcmp(edit_cmds[i].cmd, "close-edit") == 0)
> + break;
> + }
> +
> + apr_pool_destroy(subpool);
> + return SVN_NO_ERROR;
> +}
>
> Modified: branches/svnpatch-diff/subversion/libsvn_wc/util.c
> URL: http://svn.collab.net/viewvc/svn/branches/svnpatch-diff/subversion/libsvn_wc/util.c?pathrev=26210&r1=26209&r2=26210
> ==============================================================================
> --- branches/svnpatch-diff/subversion/libsvn_wc/util.c (original)
> +++ branches/svnpatch-diff/subversion/libsvn_wc/util.c Mon Aug 20 11:42:44 2007
> @@ -24,6 +24,7 @@
> #include <assert.h>
> #include <apr_pools.h>
> #include <apr_file_io.h>
> +#include <apr_lib.h>
> #include "svn_io.h"
> #include "svn_types.h"
> #include "svn_error.h"
> @@ -295,6 +296,8 @@
>
> /* --- SVNPATCH ROUTINES --- */
>
> +#define svn_iswhitespace(c) ((c) == ' ' || (c) == '\n')
> +
> /* --- WRITING DATA ITEMS --- */
>
> svn_error_t *
> @@ -491,3 +494,284 @@
>
> return SVN_NO_ERROR;
> }
> +
> +static svn_error_t *
> +readbuf_getchar_skip_whitespace(svn_stream_t *from,
> + char *result)
> +{
> + apr_size_t lenone = 1;
> + do
> + SVN_ERR(svn_stream_read(from, result, &lenone));
> + while (svn_iswhitespace(*result));
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +read_string(svn_stream_t *from,
> + apr_pool_t *pool,
> + svn_ra_svn_item_t *item,
> + apr_uint64_t len)
> +{
> + char readbuf[4096];
> + apr_size_t readbuf_len;
> + svn_stringbuf_t *stringbuf = svn_stringbuf_create("", pool);
> +
> + /* We can't store strings longer than the maximum size of apr_size_t,
> + * so check for wrapping */
> + if (((apr_size_t) len) < len)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("String length larger than maximum"));
> +
> + while (len)
> + {
> + readbuf_len = len > sizeof(readbuf) ? sizeof(readbuf) : len;
> +
> + SVN_ERR(svn_stream_read(from, readbuf, &readbuf_len));
> + /* Read into a stringbuf_t to so we don't allow the sender to allocate
> + * an arbitrary amount of memory without actually sending us that much
> + * data */
> + svn_stringbuf_appendbytes(stringbuf, readbuf, readbuf_len);
> + len -= readbuf_len;
> + }
> +
> + item->kind = SVN_RA_SVN_STRING;
> + item->u.string = apr_palloc(pool, sizeof(*item->u.string));
> + item->u.string->data = stringbuf->data;
> + item->u.string->len = stringbuf->len;
> +
> + return SVN_NO_ERROR;
> +}
> +
> +
> +static svn_error_t *
> +read_item(svn_stream_t *from,
> + apr_pool_t *pool,
> + svn_ra_svn_item_t *item,
> + char first_char,
> + int level)
> +{
> + char c = first_char;
> + apr_uint64_t val, prev_val=0;
> + svn_stringbuf_t *str;
> + svn_ra_svn_item_t *listitem;
> + apr_size_t lenone = 1;
> +
> + if (++level >= 64)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Too many nested items"));
> +
> +
> + /* Determine the item type and read it in. Make sure that c is the
> + * first character at the end of the item so we can test to make
> + * sure it's whitespace. */
> + if (apr_isdigit(c))
> + {
> + /* It's a number or a string. Read the number part, either way. */
> + val = c - '0';
> + while (1)
> + {
> + prev_val = val;
> + SVN_ERR(svn_stream_read(from, &c, &lenone));
> + if (!apr_isdigit(c))
> + break;
> + val = val * 10 + (c - '0');
> + if ((val / 10) != prev_val) /* val wrapped past maximum value */
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Number is larger than maximum"));
> + }
> + if (c == ':')
> + {
> + /* It's a string. */
> + SVN_ERR(read_string(from, pool, item, val));
> + SVN_ERR(svn_stream_read(from, &c, &lenone));
> + }
> + else
> + {
> + /* It's a number. */
> + item->kind = SVN_RA_SVN_NUMBER;
> + item->u.number = val;
> + }
> + }
> + else if (apr_isalpha(c))
> + {
> + /* It's a word. */
> + str = svn_stringbuf_ncreate(&c, 1, pool);
> + while (1)
> + {
> + SVN_ERR(svn_stream_read(from, &c, &lenone));
> + if (!apr_isalnum(c) && c != '-')
> + break;
> + svn_stringbuf_appendbytes(str, &c, 1);
> + }
> + item->kind = SVN_RA_SVN_WORD;
> + item->u.word = str->data;
> + }
> + else if (c == '(')
> + {
> + /* Read in the list items. */
> + item->kind = SVN_RA_SVN_LIST;
> + item->u.list = apr_array_make(pool, 0, sizeof(svn_ra_svn_item_t));
> + while (1)
> + {
> + SVN_ERR(readbuf_getchar_skip_whitespace(from, &c));
> + if (c == ')')
> + break;
> + listitem = apr_array_push(item->u.list);
> + SVN_ERR(read_item(from, pool, listitem, c, level));
> + }
> + SVN_ERR(svn_stream_read(from, &c, &lenone));
> + }
> +
> + if (!svn_iswhitespace(c))
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Malformed patch data"));
> + return SVN_NO_ERROR;
> +}
> +
> +svn_error_t *
> +svn_wc_read_item(svn_stream_t *from,
> + apr_pool_t *pool,
> + svn_ra_svn_item_t **item)
> +{
> + char c;
> +
> + /* Allocate space, read the first character, and then do the rest of
> + * the work. This makes sense because of the way lists are read. */
> + *item = apr_palloc(pool, sizeof(**item));
> + SVN_ERR(readbuf_getchar_skip_whitespace(from, &c));
> + return read_item(from, pool, *item, c, 0);
> +}
> +
> +/* Parse a tuple of svn_ra_svn_item_t *'s. Advance *FMT to the end of the
> + * tuple specification and advance AP by the corresponding arguments. */
> +static svn_error_t *
> +vparse_tuple(apr_array_header_t *items,
> + apr_pool_t *pool,
> + const char **fmt,
> + va_list *ap)
> +{
> + int count, nesting_level;
> + svn_ra_svn_item_t *elt;
> +
> + for (count = 0; **fmt && count < items->nelts; (*fmt)++, count++)
> + {
> + /* '?' just means the tuple may stop; skip past it. */
> + if (**fmt == '?')
> + (*fmt)++;
> + elt = &APR_ARRAY_IDX(items, count, svn_ra_svn_item_t);
> + if (**fmt == 'n' && elt->kind == SVN_RA_SVN_NUMBER)
> + *va_arg(*ap, apr_uint64_t *) = elt->u.number;
> + else if (**fmt == 'r' && elt->kind == SVN_RA_SVN_NUMBER)
> + *va_arg(*ap, svn_revnum_t *) = elt->u.number;
> + else if (**fmt == 's' && elt->kind == SVN_RA_SVN_STRING)
> + *va_arg(*ap, svn_string_t **) = elt->u.string;
> + else if (**fmt == 'c' && elt->kind == SVN_RA_SVN_STRING)
> + *va_arg(*ap, const char **) = elt->u.string->data;
> + else if (**fmt == 'w' && elt->kind == SVN_RA_SVN_WORD)
> + *va_arg(*ap, const char **) = elt->u.word;
> + else if (**fmt == 'b' && elt->kind == SVN_RA_SVN_WORD)
> + {
> + if (strcmp(elt->u.word, "true") == 0)
> + *va_arg(*ap, svn_boolean_t *) = TRUE;
> + else if (strcmp(elt->u.word, "false") == 0)
> + *va_arg(*ap, svn_boolean_t *) = FALSE;
> + else
> + break;
> + }
> + else if (**fmt == 'B' && elt->kind == SVN_RA_SVN_WORD)
> + {
> + if (strcmp(elt->u.word, "true") == 0)
> + *va_arg(*ap, apr_uint64_t *) = TRUE;
> + else if (strcmp(elt->u.word, "false") == 0)
> + *va_arg(*ap, apr_uint64_t *) = FALSE;
> + else
> + break;
> + }
> + else if (**fmt == 'l' && elt->kind == SVN_RA_SVN_LIST)
> + *va_arg(*ap, apr_array_header_t **) = elt->u.list;
> + else if (**fmt == '(' && elt->kind == SVN_RA_SVN_LIST)
> + {
> + (*fmt)++;
> + SVN_ERR(vparse_tuple(elt->u.list, pool, fmt, ap));
> + }
> + else if (**fmt == ')')
> + return SVN_NO_ERROR;
> + else
> + break;
> + }
> + if (**fmt == '?')
> + {
> + nesting_level = 0;
> + for (; **fmt; (*fmt)++)
> + {
> + switch (**fmt)
> + {
> + case '?':
> + break;
> + case 'r':
> + *va_arg(*ap, svn_revnum_t *) = SVN_INVALID_REVNUM;
> + break;
> + case 's':
> + *va_arg(*ap, svn_string_t **) = NULL;
> + break;
> + case 'c':
> + case 'w':
> + *va_arg(*ap, const char **) = NULL;
> + break;
> + case 'l':
> + *va_arg(*ap, apr_array_header_t **) = NULL;
> + break;
> + case 'B':
> + case 'n':
> + *va_arg(*ap, apr_uint64_t *) = SVN_RA_SVN_UNSPECIFIED_NUMBER;
> + break;
> + case '(':
> + nesting_level++;
> + break;
> + case ')':
> + if (--nesting_level < 0)
> + return SVN_NO_ERROR;
> + break;
> + default:
> + abort();
> + }
> + }
> + }
> + if (**fmt && **fmt != ')')
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Malformed patch data"));
> + return SVN_NO_ERROR;
> +}
> +
> +svn_error_t *
> +svn_wc_parse_tuple(apr_array_header_t *list,
> + apr_pool_t *pool,
> + const char *fmt, ...)
> +{
> + svn_error_t *err;
> + va_list ap;
> +
> + va_start(ap, fmt);
> + err = vparse_tuple(list, pool, &fmt, &ap);
> + va_end(ap);
> + return err;
> +}
> +
> +svn_error_t *
> +svn_wc_read_tuple(svn_stream_t *from,
> + apr_pool_t *pool,
> + const char *fmt, ...)
> +{
> + va_list ap;
> + svn_ra_svn_item_t *item;
> + svn_error_t *err;
> +
> + SVN_ERR(svn_wc_read_item(from, pool, &item));
> + if (item->kind != SVN_RA_SVN_LIST)
> + return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
> + _("Malformed patch data"));
> + va_start(ap, fmt);
> + err = vparse_tuple(item->u.list, pool, &fmt, &ap);
> + va_end(ap);
> + return err;
> +}
>
> Modified: branches/svnpatch-diff/subversion/svn/main.c
> URL: http://svn.collab.net/viewvc/svn/branches/svnpatch-diff/subversion/svn/main.c?pathrev=26210&r1=26209&r2=26210
> ==============================================================================
> --- branches/svnpatch-diff/subversion/svn/main.c (original)
> +++ branches/svnpatch-diff/subversion/svn/main.c Mon Aug 20 11:42:44 2007
> @@ -608,7 +608,7 @@
> " This command allows some amount of fuzzing as Unidiff is contextual\n"
> " and svnpatch revisionless. However, you might see failure warnings\n"
> " when pushing this feature too hard.\n"),
> - {'q', svn_cl__dry_run_opt, svn_cl__config_dir_opt} },
> + {'q', svn_cl__force_opt, svn_cl__dry_run_opt, svn_cl__config_dir_opt} },
>
> { "propdel", svn_cl__propdel, {"pdel", "pd"}, N_
> ("Remove a property from files, dirs, or revisions.\n"
>
> Modified: branches/svnpatch-diff/subversion/svn/patch-cmd.c
> URL: http://svn.collab.net/viewvc/svn/branches/svnpatch-diff/subversion/svn/patch-cmd.c?pathrev=26210&r1=26209&r2=26210
> ==============================================================================
> --- branches/svnpatch-diff/subversion/svn/patch-cmd.c (original)
> +++ branches/svnpatch-diff/subversion/svn/patch-cmd.c Mon Aug 20 11:42:44 2007
> @@ -84,9 +84,14 @@
> return svn_error_create(SVN_ERR_WC_NOT_DIRECTORY, 0, NULL);
> }
>
> + if (! opt_state->quiet)
> + svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, FALSE,
> + FALSE, FALSE, pool);
> +
> /* OK we're good. */
> SVN_ERR(svn_client_patch(patch_path,
> target_path,
> + opt_state->force,
> opt_state->dry_run,
> ctx,
> pool));
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: svn-unsubscribe@subversion.tigris.org
> For additional commands, e-mail: svn-help@subversion.tigris.org
>
>
--
David Glasser | glasser_at_davidglasser.net | http://www.davidglasser.net/
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Mon Aug 20 21:03:11 2007