On Sat, Nov 22, 2008 at 05:58:46AM -0800, rhuijben_at_tigris.org wrote:
> Author: rhuijben
> Date: Sat Nov 22 05:58:46 2008
> New Revision: 34332
>
> Log:
> On the tc_url_rev branch:
> Allow passing conflict versions to the conflict resolver for text conflicts.
>
> * subversion/include/svn_wc.h
> (svn_wc_conflict_version_t): Update documentation to state that the
> structure can be extended in future versions and is new in 1.6.
> * subversion/libsvn_wc/wc.h
> (svn_wc__merge_internal): Add left_version and right_version arguments and
> update documentation to refer to svn_wc_merge3 instead of svn_wc_merge2.
> * subversion/libsvn_wc/merge.c
> (svn_wc__merge_internal): Set older and their version in the conflict
> description to left_version and right_version.
> (svn_wc_merge3): Pass NULL to svn_wc__merge_internal for left_version and
> right_version.
> * subversion/libsvn_wc/log.c
> (log_do_merge): Pass NULL to svn_wc__merge_internal for left_version and
> right_version.
> * subversion/libsvn_wc/update_editor.c
> (merge_file): Pass NULL to svn_wc__merge_internal for left_version and
> right_version.
>
> Modified:
> branches/tc_url_rev/subversion/include/svn_wc.h
> branches/tc_url_rev/subversion/libsvn_wc/log.c
> branches/tc_url_rev/subversion/libsvn_wc/merge.c
> branches/tc_url_rev/subversion/libsvn_wc/update_editor.c
> branches/tc_url_rev/subversion/libsvn_wc/wc.h
Note on libsvn_wc/merge.c diff: This is NOT what got committed.
The mailer sent a bogus diff for merge.c.
Stefan
> Modified: branches/tc_url_rev/subversion/libsvn_wc/merge.c
> URL: http://svn.collab.net/viewvc/svn/branches/tc_url_rev/subversion/libsvn_wc/merge.c?pathrev=34332&r1=34331&r2=34332
> ==============================================================================
> --- branches/tc_url_rev/subversion/libsvn_wc/merge.c Fri Nov 21 23:41:51 2008 (r34331)
> +++ branches/tc_url_rev/subversion/libsvn_wc/merge.c Sat Nov 22 05:58:46 2008 (r34332)
> @@ -1,8 +1,8 @@
> /*
> - * update_editor.c : main editor for checkouts and updates
> + * merge.c: merging changes into a working file
> *
> * ====================================================================
> - * Copyright (c) 2000-2008 CollabNet. All rights reserved.
> + * Copyright (c) 2000-2006, 2008 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
> @@ -18,4912 +18,981 @@
>
>
>
> -#include <stdlib.h>
> -#include <string.h>
> -
> -#include <apr_pools.h>
> -#include <apr_hash.h>
> -#include <apr_md5.h>
> -#include <apr_tables.h>
> -#include <apr_file_io.h>
> -#include <apr_strings.h>
> -
> -#include "svn_types.h"
> -#include "svn_pools.h"
> -#include "svn_delta.h"
> -#include "svn_string.h"
> -#include "svn_dirent_uri.h"
> +#include "svn_wc.h"
> +#include "svn_diff.h"
> #include "svn_path.h"
> -#include "svn_xml.h"
> -#include "svn_error.h"
> -#include "svn_io.h"
> -#include "svn_private_config.h"
> -#include "svn_time.h"
> -#include "svn_config.h"
> -#include "svn_iter.h"
>
> #include "wc.h"
> -#include "questions.h"
> -#include "log.h"
> -#include "adm_files.h"
> -#include "adm_ops.h"
> #include "entries.h"
> -#include "lock.h"
> -#include "props.h"
> #include "translate.h"
> -#include "tree_conflicts.h"
> -
> -#include "private/svn_wc_private.h"
> -
> +#include "questions.h"
> +#include "log.h"
>
> -
> -/*
> - * This code handles "checkout" and "update" and "switch".
> - * A checkout is similar to an update that is only adding new items.
> - *
> - * The intended behaviour of "update" and "switch", focusing on the checks
> - * to be made before applying a change, is:
> - *
> - * For each incoming change:
> - * if target is already in conflict or obstructed:
> - * skip this change
> - * else
> - * if this action will cause a tree conflict:
> - * record the tree conflict
> - * skip this change
> - * else:
> - * make this change
> - *
> - * In more detail:
> - *
> - * For each incoming change:
> - *
> - * 1. if # Incoming change is inside an item already in conflict:
> - * a. tree/text/prop change to node beneath tree-conflicted dir
> - * then # Skip all changes in this conflicted subtree [*1]:
> - * do not update the Base nor the Working
> - * notify "skipped because already in conflict" just once
> - * for the whole conflicted subtree
> - *
> - * if # Incoming change affects an item already in conflict:
> - * b. tree/text/prop change to tree-conflicted dir/file, or
> - * c. tree change to a text/prop-conflicted file/dir, or
> - * d. text/prop change to a text/prop-conflicted file/dir [*2], or
> - * e. tree change to a dir tree containing any conflicts,
> - * then # Skip this change [*1]:
> - * do not update the Base nor the Working
> - * notify "skipped because already in conflict"
> - *
> - * 2. if # Incoming change affects an item that's "obstructed":
> - * a. on-disk node kind doesn't match recorded Working node kind
> - * (including an absence/presence mis-match),
> - * then # Skip this change [*1]:
> - * do not update the Base nor the Working
> - * notify "skipped because obstructed"
> - *
> - * 3. if # Incoming change raises a tree conflict:
> - * a. tree/text/prop change to node beneath sched-delete dir, or
> - * b. tree/text/prop change to sched-delete dir/file, or
> - * c. text/prop change to tree-scheduled dir/file,
> - * then # Skip this change:
> - * do not update the Base nor the Working [*3]
> - * notify "tree conflict"
> - *
> - * 4. Apply the change:
> - * update the Base
> - * update the Working, possibly raising text/prop conflicts
> - * notify
> - *
> - * Notes:
> - *
> - * "Tree change" here refers to an add or delete of the target node,
> - * including the add or delete part of a copy or move or rename.
> - *
> - * [*1] We should skip changes to an entire node, as the base revision number
> - * applies to the entire node. Not sure how this affects attempts to
> - * handle text and prop changes separately.
> - *
> - * [*2] Details of which combinations of property and text changes conflict
> - * are not specified here.
> - *
> - * [*3] For now, we skip the update, and require the user to:
> - * - Modify the WC to be compatible with the incoming change;
> - * - Mark the conflict as resolved;
> - * - Repeat the update.
> - * Ideally, it would be possible to resolve any conflict without
> - * repeating the update. To achieve this, we would have to store the
> - * necessary data at conflict detection time, and delay the update of
> - * the Base until the time of resolving.
> - */
> +#include "svn_private_config.h"
>
>
> -/*** batons ***/
> -
> -struct edit_baton
> +/* Return a pointer to the svn_prop_t structure from PROP_DIFF
> + belonging to PROP_NAME, if any. NULL otherwise.*/
> +static const svn_prop_t *
> +get_prop(const apr_array_header_t *prop_diff,
> + const char *prop_name)
> {
> - /* For updates, the "destination" of the edit is the ANCHOR (the
> - directory at which the edit is rooted) plus the TARGET (the
> - actual thing we wish to update). Target may be the empty string,
> - but it is never NULL; for example, for checkouts and for updates
> - that do not specify a target path, ANCHOR holds the whole path,
> - and TARGET is empty. */
> - const char *anchor;
> - const char *target;
> -
> - /* ADM_ACCESS is an access baton that includes the ANCHOR directory */
> - svn_wc_adm_access_t *adm_access;
> -
> - /* Array of file extension patterns to preserve as extensions in
> - generated conflict files. */
> - apr_array_header_t *ext_patterns;
> -
> - /* The revision we're targeting...or something like that. This
> - starts off as a pointer to the revision to which we are updating,
> - or SVN_INVALID_REVNUM, but by the end of the edit, should be
> - pointing to the final revision. */
> - svn_revnum_t *target_revision;
> -
> - /* The requested depth of this edit. */
> - svn_depth_t requested_depth;
> -
> - /* Is the requested depth merely an operational limitation, or is
> - also the new sticky ambient depth of the update target? */
> - svn_boolean_t depth_is_sticky;
> -
> - /* Need to know if the user wants us to overwrite the 'now' times on
> - edited/added files with the last-commit-time. */
> - svn_boolean_t use_commit_times;
> -
> - /* Was the root actually opened (was this a non-empty edit)? */
> - svn_boolean_t root_opened;
> -
> - /* Was the update-target deleted? This is a special situation. */
> - svn_boolean_t target_deleted;
> -
> - /* Allow unversioned obstructions when adding a path. */
> - svn_boolean_t allow_unver_obstructions;
> -
> - /* Non-null if this is a 'switch' operation. */
> - const char *switch_url;
> -
> - /* The URL to the root of the repository, or NULL. */
> - const char *repos;
> -
> - /* External diff3 to use for merges (can be null, in which case
> - internal merge code is used). */
> - const char *diff3_cmd;
> -
> - /* Object for gathering info to be accessed after the edit is
> - complete. */
> - svn_wc_traversal_info_t *traversal_info;
> -
> - /* This editor sends back notifications as it edits. */
> - svn_wc_notify_func2_t notify_func;
> - void *notify_baton;
> -
> - /* This editor is normally wrapped in a cancellation editor anyway,
> - so it doesn't bother to check for cancellation itself. However,
> - it needs a cancel_func and cancel_baton available to pass to
> - long-running functions. */
> - svn_cancel_func_t cancel_func;
> - void *cancel_baton;
> -
> - /* This editor will invoke a interactive conflict-resolution
> - callback, if available. */
> - svn_wc_conflict_resolver_func_t conflict_func;
> - void *conflict_baton;
> -
> - /* If the server sends add_file(copyfrom=...) and we don't have the
> - copyfrom file in the working copy, we use this callback to fetch
> - it directly from the repository. */
> - svn_wc_get_file_t fetch_func;
> - void *fetch_baton;
> -
> - /* Paths that were skipped during the edit, and therefore shouldn't
> - have their revision/url info updated at the end. If a path is a
> - directory, its descendants will also be skipped. The keys are
> - pathnames and the values unspecified. */
> - apr_hash_t *skipped_paths;
> -
> - /* Path of the tree- or prop-conflicted directory, if the edit is
> - currently inside a conflicted tree. NULL if the edit is not
> - inside a conflicted tree. */
> - const char *current_conflict;
> -
> - apr_pool_t *pool;
> -};
> -
> + if (prop_diff)
> + {
> + int i;
> + for (i = 0; i < prop_diff->nelts; i++)
> + {
> + const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t);
> + if (strcmp(elt->name,prop_name) == 0)
> + return elt;
> + }
> + }
>
> -/* Record in the edit baton EB that PATH's base version is not being updated.
> - *
> - * Add to EB->skipped_paths a copy (allocated in EB->pool) of the string
> - * PATH.
> - */
> -static void
> -remember_skipped_path(struct edit_baton *eb, const char *path)
> -{
> - apr_hash_set(eb->skipped_paths, apr_pstrdup(eb->pool, path),
> - APR_HASH_KEY_STRING, (void*)1);
> + return NULL;
> }
>
>
> -struct dir_baton
> -{
> - /* The path to this directory. */
> - const char *path;
> -
> - /* Basename of this directory. */
> - const char *name;
> -
> - /* The repository URL this directory will correspond to. */
> - const char *new_URL;
> -
> - /* The global edit baton. */
> - struct edit_baton *edit_baton;
> -
> - /* Baton for this directory's parent, or NULL if this is the root
> - directory. */
> - struct dir_baton *parent_baton;
> -
> - /* Gets set iff this is a new directory that is not yet versioned and not
> - yet in the parent's list of entries */
> - svn_boolean_t added;
> -
> - /* Set if an unversioned dir of the same name already existed in
> - this directory. */
> - svn_boolean_t existed;
> -
> - /* Set if a dir of the same name already exists and is
> - scheduled for addition without history. */
> - svn_boolean_t add_existed;
> -
> - /* An array of svn_prop_t structures, representing all the property
> - changes to be applied to this directory. */
> - apr_array_header_t *propchanges;
> +/* Detranslate a working copy file MERGE_TARGET to achieve the effect of:
>
> - /* The bump information for this directory. */
> - struct bump_dir_info *bump_info;
> + 1. Detranslate
> + 2. Install new props
> + 3. Retranslate
> + 4. Detranslate
>
> - /* The current log file number. */
> - int log_number;
> + in 1 pass to get a file which can be compared with the left and right
> + files which were created with the 'new props' above.
>
> - /* The current log buffer. The content of this accumulator may be
> - flushed and run at any time (in pool cleanup), so only append
> - complete sets of operations to it; you may need to build up a
> - buffer of operations and append it atomically with
> - svn_stringbuf_appendstr. */
> - svn_stringbuf_t *log_accum;
> + Property changes make this a little complex though. Changes in
>
> - /* The depth of the directory in the wc (or inferred if added). Not
> - used for filtering; we have a separate wrapping editor for that. */
> - svn_depth_t ambient_depth;
> + - svn:mime-type
> + - svn:eol-style
> + - svn:keywords
> + - svn:special
>
> - /* Was the directory marked as incomplete before the update?
> - (In other words, are we resuming an interrupted update?) */
> - svn_boolean_t was_incomplete;
> + may change the way a file is translated.
>
> - /* The pool in which this baton itself is allocated. */
> - apr_pool_t *pool;
> -};
> + Effect for svn:mime-type:
>
> + The value for svn:mime-type affects the translation wrt keywords
> + and eol-style settings.
>
> -/* The bump information is tracked separately from the directory batons.
> - This is a small structure kept in the edit pool, while the heavier
> - directory baton is managed by the editor driver.
> -
> - In a postfix delta case, the directory batons are going to disappear.
> - The files will refer to these structures, rather than the full
> - directory baton. */
> -struct bump_dir_info
> -{
> - /* ptr to the bump information for the parent directory */
> - struct bump_dir_info *parent;
> -
> - /* how many entries are referring to this bump information? */
> - int ref_count;
> -
> - /* the path of the directory to bump */
> - const char *path;
> -
> - /* Set if this directory is skipped due to prop or tree conflicts.
> - This does NOT mean that children are skipped. */
> - svn_boolean_t skipped;
> -};
> -
> -
> -struct handler_baton
> -{
> - svn_txdelta_window_handler_t apply_handler;
> - void *apply_baton;
> - apr_pool_t *pool;
> - struct file_baton *fb;
> -
> - /* Where we are assembling the new file. */
> - const char *work_path;
> -
> - /* This is initialized to all zeroes when the baton is created, then
> - populated with the MD5 digest of the resultant fulltext after the
> - last window is handled by the handler returned from
> - apply_textdelta(). */
> - unsigned char digest[APR_MD5_DIGESTSIZE];
> -};
> + I) both old and new mime-types are texty
> + -> just do the translation dance (as lined out below)
>
> + II) the old one is texty, the new one is binary
> + -> detranslate with the old eol-style and keywords
> + (the new re+detranslation is a no-op)
>
> -/* Return the url for NAME in DIR, allocated in POOL, or null if
> - * unable to obtain a url. If NAME is null, get the url for DIR.
> - *
> - * Use ASSOCIATED_ACCESS to retrieve an access baton for PATH, and do
> - * all temporary allocation in POOL.
> - */
> -static const char *
> -get_entry_url(svn_wc_adm_access_t *associated_access,
> - const char *dir,
> - const char *name,
> - apr_pool_t *pool)
> -{
> - svn_error_t *err;
> - const svn_wc_entry_t *entry;
> - svn_wc_adm_access_t *adm_access;
> -
> - err = svn_wc_adm_retrieve(&adm_access, associated_access, dir, pool);
> -
> - if (! err)
> - {
> - /* Note that `name' itself may be NULL. */
> - err = svn_wc_entry(&entry, svn_path_join_many(pool, dir, name, NULL),
> - adm_access, FALSE, pool);
> - }
> - if (err || (! entry) || (! entry->url))
> - {
> - svn_error_clear(err);
> - return NULL;
> - }
> + III) the old one is binary, the new one texty
> + -> detranslate with the new eol-style
> + (the old detranslation is a no-op)
>
> - return entry->url;
> -}
> + IV) the old and new ones are binary
> + -> don't detranslate, just make a straight copy
>
> -/* Flush accumulated log entries to a log file on disk for DIR_BATON and
> - * increase the log number of the dir baton.
> - * Use POOL for temporary allocations. */
> -static svn_error_t *
> -flush_log(struct dir_baton *db, apr_pool_t *pool)
> -{
> - if (! svn_stringbuf_isempty(db->log_accum))
> - {
> - svn_wc_adm_access_t *adm_access;
>
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, db->edit_baton->adm_access,
> - db->path, pool));
> - SVN_ERR(svn_wc__write_log(adm_access, db->log_number, db->log_accum,
> - pool));
> - db->log_number++;
> - svn_stringbuf_setempty(db->log_accum);
> - }
> + Effect for svn:eol-style
>
> - return SVN_NO_ERROR;
> -}
> + I) On add or change use the new value
>
> -/* An APR pool cleanup handler. This runs the log file for a
> - directory baton. */
> -static apr_status_t
> -cleanup_dir_baton(void *dir_baton)
> -{
> - struct dir_baton *db = dir_baton;
> - svn_error_t *err;
> - apr_status_t apr_err;
> - svn_wc_adm_access_t *adm_access;
> - apr_pool_t *pool = apr_pool_parent_get(db->pool);
> + II) otherwise: use the old value (absent means 'no translation')
>
> - err = flush_log(db, pool);
> - if (! err && db->log_number > 0)
> - {
> - err = svn_wc_adm_retrieve(&adm_access, db->edit_baton->adm_access,
> - db->path, pool);
>
> - if (! err)
> - {
> - err = svn_wc__run_log(adm_access, NULL, pool);
> + Effect for svn:keywords
>
> - if (! err)
> - return APR_SUCCESS;
> - }
> - }
> + Always use old settings (re+detranslation are no-op)
>
> - if (err)
> - apr_err = err->apr_err;
> - else
> - apr_err = APR_SUCCESS;
> - svn_error_clear(err);
> - return apr_err;
> -}
>
> -/* An APR pool cleanup handler. This is a child handler, it removes
> - the mail pool handler. */
> -static apr_status_t
> -cleanup_dir_baton_child(void *dir_baton)
> -{
> - struct dir_baton *db = dir_baton;
> - apr_pool_cleanup_kill(db->pool, db, cleanup_dir_baton);
> - return APR_SUCCESS;
> -}
> + Effect for svn:special
>
> + Always use the old settings (same reasons as for svn:keywords)
>
> -/* Return a new dir_baton to represent NAME (a subdirectory of
> - PARENT_BATON). If PATH is NULL, this is the root directory of the
> - edit. */
> +*/
> static svn_error_t *
> -make_dir_baton(struct dir_baton **d_p,
> - const char *path,
> - struct edit_baton *eb,
> - struct dir_baton *pb,
> - svn_boolean_t added,
> - apr_pool_t *pool)
> +detranslate_wc_file(const char **detranslated_file,
> + const char *merge_target,
> + svn_wc_adm_access_t *adm_access,
> + svn_boolean_t force_copy,
> + const apr_array_header_t *prop_diff,
> + apr_pool_t *pool)
> {
> - struct dir_baton *d;
> - struct bump_dir_info *bdi;
> -
> - SVN_ERR_ASSERT(path || (! pb));
> -
> - /* Okay, no easy out, so allocate and initialize a dir baton. */
> - d = apr_pcalloc(pool, sizeof(*d));
> -
> - /* Construct the PATH and baseNAME of this directory. */
> - d->path = apr_pstrdup(pool, eb->anchor);
> - if (path)
> - {
> - d->path = svn_path_join(d->path, path, pool);
> - d->name = svn_path_basename(path, pool);
> + svn_boolean_t is_binary;
> + const svn_prop_t *prop;
> + svn_subst_eol_style_t style;
> + const char *eol;
> + apr_hash_t *keywords;
> + svn_boolean_t special;
> +
> + /* Decide if the merge target currently is a text or binary file. */
> + SVN_ERR(svn_wc_has_binary_prop(&is_binary,
> + merge_target, adm_access, pool));
> +
> +
> + /* See if we need to do a straight copy:
> + - old and new mime-types are binary, or
> + - old mime-type is binary and no new mime-type specified */
> + if (is_binary
> + && (((prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
> + && prop->value && svn_mime_type_is_binary(prop->value->data))
> + || prop == NULL))
> + {
> + /* this is case IV above */
> + keywords = NULL;
> + special = FALSE;
> + eol = NULL;
> + style = svn_subst_eol_style_none;
> + }
> + else if ((!is_binary)
> + && (prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
> + && prop->value && svn_mime_type_is_binary(prop->value->data))
> + {
> + /* Old props indicate texty, new props indicate binary:
> + detranslate keywords and old eol-style */
> + SVN_ERR(svn_wc__get_keywords(&keywords, merge_target,
> + adm_access, NULL, pool));
> + SVN_ERR(svn_wc__get_special(&special, merge_target, adm_access, pool));
> }
> else
> {
> - d->name = NULL;
> - }
> + /* New props indicate texty, regardless of old props */
>
> - /* Figure out the new_URL for this directory. */
> - if (eb->switch_url)
> - {
> - /* Switches are, shall we say, complex. If this directory is
> - the root directory (it has no parent), then it either gets
> - the SWITCH_URL for its own (if it is both anchor and target)
> - or the parent of the SWITCH_URL (if it is anchor, but there's
> - another target). */
> - if (! pb)
> + /* In case the file used to be special, detranslate specially */
> + SVN_ERR(svn_wc__get_special(&special, merge_target, adm_access, pool));
> +
> + if (special)
> {
> - if (! *eb->target) /* anchor is also target */
> - d->new_URL = apr_pstrdup(pool, eb->switch_url);
> - else
> - d->new_URL = svn_path_dirname(eb->switch_url, pool);
> + keywords = NULL;
> + eol = NULL;
> + style = svn_subst_eol_style_none;
> }
> - /* Else this directory is *not* the root (has a parent). If it
> - is the target (there is a target, and this directory has no
> - grandparent), then it gets the SWITCH_URL for its own.
> - Otherwise, it gets a child of its parent's URL. */
> else
> {
> - if (*eb->target && (! pb->parent_baton))
> - d->new_URL = apr_pstrdup(pool, eb->switch_url);
> - else
> - d->new_URL = svn_path_url_add_component(pb->new_URL,
> - d->name, pool);
> - }
> - }
> - else /* must be an update */
> - {
> - /* updates are the odds ones. if we're updating a path already
> - present on disk, we use its original URL. otherwise, we'll
> - telescope based on its parent's URL. */
> - d->new_URL = get_entry_url(eb->adm_access, d->path, NULL, pool);
> - if ((! d->new_URL) && pb)
> - d->new_URL = svn_path_url_add_component(pb->new_URL, d->name, pool);
> - }
> -
> - /* the bump information lives in the edit pool */
> - bdi = apr_palloc(eb->pool, sizeof(*bdi));
> - bdi->parent = pb ? pb->bump_info : NULL;
> - bdi->ref_count = 1;
> - bdi->path = apr_pstrdup(eb->pool, d->path);
> - bdi->skipped = FALSE;
> -
> - /* the parent's bump info has one more referer */
> - if (pb)
> - ++bdi->parent->ref_count;
> -
> - d->edit_baton = eb;
> - d->parent_baton = pb;
> - d->pool = svn_pool_create(pool);
> - d->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t));
> - d->added = added;
> - d->existed = FALSE;
> - d->add_existed = FALSE;
> - d->bump_info = bdi;
> - d->log_number = 0;
> - d->log_accum = svn_stringbuf_create("", pool);
> -
> - /* The caller of this function needs to fill these in. */
> - d->ambient_depth = svn_depth_unknown;
> - d->was_incomplete = FALSE;
> -
> - apr_pool_cleanup_register(d->pool, d, cleanup_dir_baton,
> - cleanup_dir_baton_child);
> -
> - *d_p = d;
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Forward declaration. */
> -static svn_error_t *
> -do_entry_deletion(struct edit_baton *eb,
> - const char *parent_path,
> - const char *path,
> - const char *their_path,
> - int *log_number,
> - apr_pool_t *pool);
> -
> -/* Helper for maybe_bump_dir_info():
> -
> - In a single atomic action, (1) remove any 'deleted' entries from a
> - directory, (2) remove any 'absent' entries whose revision numbers
> - are different from the parent's new target revision, (3) remove any
> - 'missing' dir entries, and (4) remove the directory's 'incomplete'
> - flag. */
> -static svn_error_t *
> -complete_directory(struct edit_baton *eb,
> - const char *path,
> - svn_boolean_t is_root_dir,
> - apr_pool_t *pool)
> -{
> - svn_wc_adm_access_t *adm_access;
> - apr_hash_t *entries;
> - svn_wc_entry_t *entry;
> - apr_hash_index_t *hi;
> - apr_pool_t *subpool;
> - svn_wc_entry_t *current_entry;
> - const char *name;
> -
> - /* If inside a tree conflict, do nothing. */
> - if (eb->current_conflict)
> - return SVN_NO_ERROR;
> -
> - /* If this is the root directory and there is a target, we can't
> - mark this directory complete. */
> - if (is_root_dir && *eb->target)
> - {
> - /* Before we can finish, we may need to clear the exclude flag for
> - target. Also give a chance to the target that is explicitly pulled
> - in. */
> - if (eb->depth_is_sticky || *eb->target)
> - {
> - svn_wc_adm_access_t *target_access;
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access,
> - eb->adm_access, path, pool));
> - SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
> - entry = apr_hash_get(entries, eb->target, APR_HASH_KEY_STRING);
> - if (entry && entry->depth == svn_depth_exclude)
> + /* In case a new eol style was set, use that for detranslation */
> + if ((prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE)) && prop->value)
> {
> - char * full_target;
> - /* There is a small chance that the target is gone in the
> - repository. If so, we should get rid of the entry
> - (and thus get rid of the exclude flag) now. */
> - full_target = svn_path_join(eb->anchor, eb->target, pool);
> - SVN_ERR(svn_wc__adm_retrieve_internal
> - (&target_access, eb->adm_access, full_target, pool));
> - if (!target_access && entry->kind == svn_node_dir)
> - {
> - int log_number = 0;
> - SVN_ERR(do_entry_deletion(eb, eb->anchor, eb->target, NULL,
> - &log_number, pool));
> - }
> - else
> - {
> - entry->depth = svn_depth_infinity;
> - SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
> - }
> + /* Value added or changed */
> + svn_subst_eol_style_from_value(&style, &eol, prop->value->data);
> }
> - }
> - return SVN_NO_ERROR;
> - }
> -
> - /* All operations are on the in-memory entries hash. */
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access, path, pool));
> - SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
> -
> - /* Mark THIS_DIR complete. */
> - entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
> - if (! entry)
> - return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
> - _("No '.' entry in: '%s'"),
> - svn_path_local_style(path, pool));
> - entry->incomplete = FALSE;
> -
> - /* After a depth upgrade the entry must reflect the new depth.
> - Upgrading to infinity changes the depth of *all* directories,
> - upgrading to something else only changes the target. */
> - if (eb->depth_is_sticky &&
> - (eb->requested_depth == svn_depth_infinity
> - || (strcmp(path, svn_path_join(eb->anchor, eb->target, pool)) == 0
> - && eb->requested_depth > entry->depth)))
> - entry->depth = eb->requested_depth;
> -
> - /* Remove any deleted or missing entries. */
> - subpool = svn_pool_create(pool);
> - for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
> - {
> - const void *key;
> - void *val;
> -
> - svn_pool_clear(subpool);
> - apr_hash_this(hi, &key, NULL, &val);
> - name = key;
> - current_entry = val;
> -
> - /* Any entry still marked as deleted (and not schedule add) can now
> - be removed -- if it wasn't undeleted by the update, then it
> - shouldn't stay in the updated working set. Schedule add items
> - should remain.
> - */
> - if (current_entry->deleted)
> - {
> - if (current_entry->schedule != svn_wc_schedule_add)
> - svn_wc__entry_remove(entries, name);
> + else if (!is_binary)
> + SVN_ERR(svn_wc__get_eol_style(&style, &eol, merge_target,
> + adm_access, pool));
> else
> {
> - svn_wc_entry_t tmpentry;
> - tmpentry.deleted = FALSE;
> - SVN_ERR(svn_wc__entry_modify(adm_access, current_entry->name,
> - &tmpentry,
> - SVN_WC__ENTRY_MODIFY_DELETED,
> - FALSE, subpool));
> + eol = NULL;
> + style = svn_subst_eol_style_none;
> }
> - }
> - /* An absent entry might have been reconfirmed as absent, and the way
> - we can tell is by looking at its revision number: a revision
> - number different from the target revision of the update means the
> - update never mentioned the item, so the entry should be
> - removed. */
> - else if (current_entry->absent
> - && (current_entry->revision != *(eb->target_revision)))
> - {
> - svn_wc__entry_remove(entries, name);
> - }
> - else if (current_entry->kind == svn_node_dir)
> - {
> - const char *child_path = svn_path_join(path, name, subpool);
>
> - if (current_entry->depth == svn_depth_exclude)
> - {
> - /* Clear the exclude flag if it is pulled in again. */
> - if (eb->depth_is_sticky
> - && eb->requested_depth >= svn_depth_immediates)
> - current_entry->depth = svn_depth_infinity;
> - } else if ((svn_wc__adm_missing(adm_access, child_path))
> - && (! current_entry->absent)
> - && (current_entry->schedule != svn_wc_schedule_add))
> - {
> - svn_wc__entry_remove(entries, name);
> - if (eb->notify_func)
> - {
> - svn_wc_notify_t *notify
> - = svn_wc_create_notify(child_path,
> - svn_wc_notify_update_delete,
> - subpool);
> - notify->kind = current_entry->kind;
> - (* eb->notify_func)(eb->notify_baton, notify, subpool);
> - }
> - }
> + /* In case there were keywords, detranslate with keywords
> + (iff we were texty) */
> + if (!is_binary)
> + SVN_ERR(svn_wc__get_keywords(&keywords, merge_target,
> + adm_access, NULL, pool));
> + else
> + keywords = NULL;
> }
> }
>
> - svn_pool_destroy(subpool);
> + /* Now, detranslate with the settings we created above */
>
> - /* An atomic write of the whole entries file. */
> - return svn_wc__entries_write(entries, adm_access, pool);
> -}
> + if (force_copy || keywords || eol || special)
> + {
> + const char *detranslated;
> + /* Force a copy into the temporary wc area to avoid having
> + temporary files created below to appear in the actual wc. */
>
> + SVN_ERR(svn_wc_create_tmp_file2
> + (NULL, &detranslated,
> + svn_wc_adm_access_path(adm_access),
> + svn_io_file_del_none, pool));
>
> + /* Always 'repair' EOLs here, so that we can apply a diff that changes
> + * from inconsistent newlines and no 'svn:eol-style' to consistent
> + * newlines and 'svn:eol-style' set. */
>
> -/* Decrement the bump_dir_info's reference count. If it hits zero,
> - then this directory is "done". This means it is safe to remove the
> - 'incomplete' flag attached to the THIS_DIR entry.
> + SVN_ERR(svn_subst_translate_to_normal_form(merge_target,
> + detranslated,
> + style,
> + eol,
> + TRUE /* always_repair_eols */,
> + keywords,
> + special,
> + pool));
> + *detranslated_file = detranslated;
> + }
> + else
> + *detranslated_file = merge_target;
>
> - In addition, when the directory is "done", we loop onto the parent's
> - bump information to possibly mark it as done, too.
> -*/
> + return SVN_NO_ERROR;
> +}
> +
> +/* Updates (by copying and translating) the eol style in
> + OLD_TARGET returning the filename containing the
> + correct eol style in NEW_TARGET, if an eol style
> + change is contained in PROP_DIFF */
> static svn_error_t *
> -maybe_bump_dir_info(struct edit_baton *eb,
> - struct bump_dir_info *bdi,
> - apr_pool_t *pool)
> +maybe_update_target_eols(const char **new_target,
> + const char *old_target,
> + svn_wc_adm_access_t *adm_access,
> + const apr_array_header_t *prop_diff,
> + apr_pool_t *pool)
> {
> - /* Keep moving up the tree of directories until we run out of parents,
> - or a directory is not yet "done". */
> - for ( ; bdi != NULL; bdi = bdi->parent)
> + const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE);
> +
> + if (prop && prop->value)
> {
> - if (--bdi->ref_count > 0)
> - return SVN_NO_ERROR; /* directory isn't done yet */
> + const char *eol;
> + const char *tmp_new;
>
> - /* The refcount is zero, so we remove any 'dead' entries from
> - the directory and mark it 'complete'. */
> - if (! bdi->skipped)
> - SVN_ERR(complete_directory(eb, bdi->path,
> - bdi->parent ? FALSE : TRUE, pool));
> + svn_subst_eol_style_from_value(NULL, &eol, prop->value->data);
> + SVN_ERR(svn_wc_create_tmp_file2(NULL, &tmp_new,
> + svn_wc_adm_access_path(adm_access),
> + svn_io_file_del_none,
> + pool));
> +
> + /* Always 'repair' EOLs here, so that we can apply a diff that changes
> + * from inconsistent newlines and no 'svn:eol-style' to consistent
> + * newlines and 'svn:eol-style' set. */
> +
> + SVN_ERR(svn_subst_copy_and_translate3(old_target,
> + tmp_new,
> + eol, TRUE /* repair EOLs */,
> + NULL, FALSE,
> + FALSE, pool));
> + *new_target = tmp_new;
> }
> - /* we exited the for loop because there are no more parents */
> + else
> + *new_target = old_target;
>
> return SVN_NO_ERROR;
> }
>
> -struct file_baton
> +
> +svn_error_t *
> +svn_wc__merge_internal(svn_stringbuf_t **log_accum,
> + enum svn_wc_merge_outcome_t *merge_outcome,
> + const char *left,
> + svn_wc_conflict_version_t *left_version,
> + const char *right,
> + svn_wc_conflict_version_t *right_version,
> + const char *merge_target,
> + const char *copyfrom_text,
> + svn_wc_adm_access_t *adm_access,
> + const char *left_label,
> + const char *right_label,
> + const char *target_label,
> + svn_boolean_t dry_run,
> + const char *diff3_cmd,
> + const apr_array_header_t *merge_options,
> + const apr_array_header_t *prop_diff,
> + svn_wc_conflict_resolver_func_t conflict_func,
> + void *conflict_baton,
> + apr_pool_t *pool)
> {
> - /* The global edit baton. */
> - struct edit_baton *edit_baton;
> + const char *merge_dirpath;
> + const char *merge_filename;
> + const char *tmp_target, *result_target, *working_text;
> + const char *adm_path = svn_wc_adm_access_path(adm_access);
> + apr_file_t *result_f;
> + svn_boolean_t is_binary = FALSE;
> + const svn_wc_entry_t *entry;
> + svn_boolean_t contains_conflicts;
> + const svn_prop_t *mimeprop;
> + svn_diff_file_options_t *options;
> +
> + /* Sanity check: the merge target must be under revision control (unless
> + this is an add-with-history). */
> + SVN_ERR(svn_wc_entry(&entry, merge_target, adm_access, FALSE, pool));
> + if (! entry && ! copyfrom_text)
> + {
> + *merge_outcome = svn_wc_merge_no_merge;
> + return SVN_NO_ERROR;
> + }
>
> - /* The parent directory of this file. */
> - struct dir_baton *dir_baton;
> + svn_path_split(merge_target, &merge_dirpath, &merge_filename, pool);
>
> - /* Pool specific to this file_baton. */
> - apr_pool_t *pool;
> + /* Decide if the merge target is a text or binary file. */
> + if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE))
> + && mimeprop->value)
> + is_binary = svn_mime_type_is_binary(mimeprop->value->data);
> + else if (! copyfrom_text)
> + SVN_ERR(svn_wc_has_binary_prop(&is_binary, merge_target, adm_access, pool));
>
> - /* Name of this file (its entry in the directory). */
> - const char *name;
> + working_text = copyfrom_text ? copyfrom_text : merge_target;
> + SVN_ERR(detranslate_wc_file(&tmp_target, working_text, adm_access,
> + (! is_binary) && diff3_cmd != NULL,
> + prop_diff, pool));
>
> - /* Path to this file, either abs or relative to the change-root. */
> - const char *path;
> + /* We cannot depend on the left file to contain the same eols as the
> + right file. If the merge target has mods, this will mark the entire
> + file as conflicted, so we need to compensate. */
> + SVN_ERR(maybe_update_target_eols(&left, left, adm_access, prop_diff, pool));
>
> - /* The repository URL this file will correspond to. */
> - const char *new_URL;
> + if (! is_binary) /* this is a text file */
> + {
> + /* Open a second temporary file for writing; this is where diff3
> + will write the merged results. */
> + SVN_ERR(svn_wc_create_tmp_file2(&result_f, &result_target,
> + adm_path, svn_io_file_del_none,
> + pool));
>
> - /* Set if this file is new. */
> - svn_boolean_t added;
> + options = svn_diff_file_options_create(pool);
>
> - /* Set if this file is new with history. */
> - svn_boolean_t added_with_history;
> + if (merge_options)
> + SVN_ERR(svn_diff_file_options_parse(options, merge_options, pool));
>
> - /* Set if this file is skipped because it was in conflict. */
> - svn_boolean_t skipped;
>
> - /* Set if an unversioned file of the same name already existed in
> - this directory. */
> - svn_boolean_t existed;
> + /* Run an external merge if requested. */
> + if (diff3_cmd)
> + {
> + int exit_code;
>
> - /* Set if a file of the same name already exists and is
> - scheduled for addition without history. */
> - svn_boolean_t add_existed;
> + SVN_ERR(svn_io_run_diff3_2(&exit_code, ".",
> + tmp_target, left, right,
> + target_label, left_label, right_label,
> + result_f, diff3_cmd,
> + merge_options, pool));
>
> - /* Set if the dir is a tree conflict victim. */
> - svn_boolean_t tree_conflicted;
> + contains_conflicts = exit_code == 1;
> + }
> + else
> + {
> + svn_diff_t *diff;
> + const char *target_marker;
> + const char *left_marker;
> + const char *right_marker;
> + svn_stream_t *ostream;
> +
> + ostream = svn_stream_from_aprfile2(result_f, TRUE, pool);
> +
> + SVN_ERR(svn_diff_file_diff3_2(&diff,
> + left, tmp_target, right,
> + options, pool));
> +
> + /* Labels fall back to sensible defaults if not specified. */
> + if (target_label)
> + target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label);
> + else
> + target_marker = "<<<<<<< .working";
>
> - /* The path to the current text base, if any.
> - This gets set if there are file content changes. */
> - const char *text_base_path;
> + if (left_label)
> + left_marker = apr_psprintf(pool, "||||||| %s", left_label);
> + else
> + left_marker = "||||||| .old";
>
> - /* The path to the incoming text base (that is, to a text-base-file-
> - in-progress in the tmp area). This gets set if there are file
> - content changes. */
> - const char *new_text_base_path;
> + if (right_label)
> + right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label);
> + else
> + right_marker = ">>>>>>> .new";
>
> - /* The checksum for the file located at NEW_TEXT_BASE_PATH. */
> - svn_checksum_t *actual_checksum;
> + SVN_ERR(svn_diff_file_output_merge2(ostream, diff,
> + left, tmp_target, right,
> + left_marker,
> + target_marker,
> + right_marker,
> + "=======", /* separator */
> + svn_diff_conflict_display_modified_latest,
> + pool));
> + SVN_ERR(svn_stream_close(ostream));
> +
> + contains_conflicts = svn_diff_contains_conflicts(diff);
> + }
> +
> + /* Close the output file */
> + SVN_ERR(svn_io_file_close(result_f, pool));
> +
> + if (contains_conflicts && ! dry_run) /* got a conflict */
> + {
> + const char *left_copy, *right_copy, *target_copy;
> + const char *tmp_left, *tmp_right, *tmp_target_copy;
> + const char *parentt, *target_base;
> + svn_wc_adm_access_t *parent_access;
> + svn_wc_entry_t tmp_entry;
> +
> + /* Give the conflict resolution callback a chance to clean
> + up the conflict before we mark the file 'conflicted' */
> + if (conflict_func)
> + {
> + svn_wc_conflict_result_t *result = NULL;
> + svn_wc_conflict_description_t *cdesc;
> +
> + cdesc = svn_wc_conflict_description_create_text(merge_target,
> + adm_access,
> + pool);
> + cdesc->is_binary = FALSE;
> + cdesc->mime_type = (mimeprop && mimeprop->value)
> + ? mimeprop->value->data : NULL,
> + cdesc->base_file = left;
> + cdesc->their_file = right;
> + cdesc->my_file = tmp_target;
> + cdesc->merged_file = result_target;
> +
> + cdesc->older_version = left_version;
> + cdesc->their_version = right_version;
> +
> + SVN_ERR(conflict_func(&result, cdesc, conflict_baton, pool));
> + if (result == NULL)
> + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
> + NULL, _("Conflict callback violated API:"
> + " returned no results"));
>
> - /* If this file was added with history, this is the path to a copy
> - of the text base of the copyfrom file (in the temporary area). */
> - const char *copied_text_base;
> + if (result->save_merged)
> + {
> + const char *edited_copy;
>
> - /* If this file was added with history, this is the checksum of the
> - text base (see copied_text_base). May be NULL if unknown. */
> - svn_checksum_t *copied_base_checksum;
> + /* ### Should use preserved-conflict-file-exts. */
> + SVN_ERR(svn_io_open_uniquely_named(NULL,
> + &edited_copy,
> + merge_dirpath,
> + merge_filename,
> + ".edited",
> + svn_io_file_del_none,
> + pool, pool));
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + /* Look for callback's own
> + merged-file first: */
> + result->merged_file
> + ? result->merged_file
> + : result_target,
> + edited_copy,
> + pool));
> + }
>
> - /* If this file was added with history, and the copyfrom had local
> - mods, this is the path to a copy of the user's version with local
> - mods (in the temporary area). */
> - const char *copied_working_text;
> + switch (result->choice)
> + {
> + /* If the callback wants to use one of the fulltexts
> + to resolve the conflict, so be it.*/
> + case svn_wc_conflict_choose_base:
> + {
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + left, merge_target,
> + pool));
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + case svn_wc_conflict_choose_theirs_full:
> + {
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + right, merge_target,
> + pool));
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + case svn_wc_conflict_choose_mine_full:
> + {
> + /* Do nothing to merge_target, let it live untouched! */
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + case svn_wc_conflict_choose_theirs_conflict:
> + case svn_wc_conflict_choose_mine_conflict:
> + {
> + apr_file_t *chosen_f;
> + const char *chosen_path;
> + svn_stream_t *chosen_stream;
> + svn_diff_t *diff;
> + svn_diff_conflict_display_style_t style =
> + result->choice == svn_wc_conflict_choose_theirs_conflict
> + ? svn_diff_conflict_display_latest
> + : svn_diff_conflict_display_modified;
> +
> + SVN_ERR(svn_wc_create_tmp_file2(&chosen_f,
> + &chosen_path,
> + adm_path,
> + svn_io_file_del_none,
> + pool));
> + chosen_stream = svn_stream_from_aprfile2(chosen_f, FALSE,
> + pool);
> + SVN_ERR(svn_diff_file_diff3_2(&diff,
> + left, tmp_target, right,
> + options, pool));
> + SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff,
> + left,
> + tmp_target,
> + right,
> + /* markers ignored */
> + NULL, NULL,
> + NULL, NULL,
> + style,
> + pool));
> + SVN_ERR(svn_stream_close(chosen_stream));
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + chosen_path, merge_target,
> + pool));
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
>
> - /* If this file was added with history, this hash contains the base
> - properties of the copied file. */
> - apr_hash_t *copied_base_props;
> + /* For the case of 3-way file merging, we don't
> + really distinguish between these return values;
> + if the callback claims to have "generally
> + resolved" the situation, we still interpret
> + that as "OK, we'll assume the merged version is
> + good to use". */
> + case svn_wc_conflict_choose_merged:
> + {
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + /* Look for callback's own
> + merged-file first: */
> + result->merged_file
> + ? result->merged_file
> + : result_target,
> + merge_target,
> + pool));
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + case svn_wc_conflict_choose_postpone:
> + default:
> + {
> + /* Assume conflict remains, fall through to code below. */
> + }
> + }
> + }
>
> - /* If this file was added with history, this hash contains the working
> - properties of the copied file. */
> - apr_hash_t *copied_working_props;
> + /* Preserve the three pre-merge files, and modify the
> + entry (mark as conflicted, track the preserved files). */
>
> - /* Set if we've received an apply_textdelta for this file. */
> - svn_boolean_t received_textdelta;
> + /* I miss Lisp. */
>
> - /* An array of svn_prop_t structures, representing all the property
> - changes to be applied to this file. Once a file baton is
> - initialized, this is never NULL, but it may have zero elements. */
> - apr_array_header_t *propchanges;
> + SVN_ERR(svn_io_open_uniquely_named(NULL,
> + &left_copy,
> + merge_dirpath,
> + merge_filename,
> + left_label,
> + svn_io_file_del_none,
> + pool, pool));
> +
> + /* Have I mentioned how much I miss Lisp? */
> +
> + SVN_ERR(svn_io_open_uniquely_named(NULL,
> + &right_copy,
> + merge_dirpath,
> + merge_filename,
> + right_label,
> + svn_io_file_del_none,
> + pool, pool));
> +
> + /* Why, how much more pleasant to be forced to unroll my loops.
> + If I'd been writing in Lisp, I might have mapped an inline
> + lambda form over a list, or something equally disgusting.
> + Thank goodness C was here to protect me! */
> +
> + SVN_ERR(svn_io_open_uniquely_named(NULL,
> + &target_copy,
> + merge_dirpath,
> + merge_filename,
> + target_label,
> + svn_io_file_del_none,
> + pool, pool));
> +
> + /* We preserve all the files with keywords expanded and line
> + endings in local (working) form. */
> +
> + svn_path_split(target_copy, &parentt, &target_base, pool);
> + SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parentt,
> + pool));
> +
> + /* Log files require their paths to be in the subtree
> + relative to the adm_access path they are executed in.
> +
> + Make our LEFT and RIGHT files 'local' if they aren't... */
> + if (! svn_path_is_child(adm_path, left, pool))
> + {
> + SVN_ERR(svn_wc_create_tmp_file2
> + (NULL, &tmp_left,
> + adm_path, svn_io_file_del_none, pool));
> + SVN_ERR(svn_io_copy_file(left, tmp_left, TRUE, pool));
> + }
> + else
> + tmp_left = left;
>
> - /* The last-changed-date of the file. This is actually a property
> - that comes through as an 'entry prop', and will be used to set
> - the working file's timestamp if it's added. */
> - const char *last_changed_date;
> + if (! svn_path_is_child(adm_path, right, pool))
> + {
> + SVN_ERR(svn_wc_create_tmp_file2
> + (NULL, &tmp_right,
> + adm_path, svn_io_file_del_none, pool));
> + SVN_ERR(svn_io_copy_file(right, tmp_right, TRUE, pool));
> + }
> + else
> + tmp_right = right;
>
> - /* Bump information for the directory this file lives in */
> - struct bump_dir_info *bump_info;
> -};
> + /* NOTE: Callers must ensure that the svn:eol-style and
> + svn:keywords property values are correct in the currently
> + installed props. With 'svn merge', it's no big deal. But
> + when 'svn up' calls this routine, it needs to make sure that
> + this routine is using the newest property values that may
> + have been received *during* the update. Since this routine
> + will be run from within a log-command, merge_file()
> + needs to make sure that a previous log-command to 'install
> + latest props' has already executed first. Ben and I just
> + checked, and that is indeed the order in which the log items
> + are written, so everything should be fine. Really. */
> +
> + /* Create LEFT and RIGHT backup files, in expanded form.
> + We use merge_target's current properties to do the translation. */
> + /* Derive the basenames of the 3 backup files. */
> + SVN_ERR(svn_wc__loggy_translated_file(log_accum,
> + adm_access,
> + left_copy, tmp_left,
> + merge_target, pool));
> + SVN_ERR(svn_wc__loggy_translated_file(log_accum,
> + adm_access,
> + right_copy, tmp_right,
> + merge_target, pool));
>
> + /* Back up MERGE_TARGET through detranslation/retranslation:
> + the new translation properties may not match the current ones */
> + SVN_ERR(svn_wc_translated_file2(&tmp_target_copy,
> + merge_target,
> + merge_target,
> + adm_access,
> + SVN_WC_TRANSLATE_TO_NF
> + | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP,
> + pool));
> + SVN_ERR(svn_wc__loggy_translated_file
> + (log_accum, adm_access,
> + target_copy, tmp_target_copy, merge_target, pool));
> +
> + tmp_entry.conflict_old
> + = svn_path_is_child(adm_path, left_copy, pool);
> + tmp_entry.conflict_new
> + = svn_path_is_child(adm_path, right_copy, pool);
> + tmp_entry.conflict_wrk = target_base;
> +
> + /* Mark merge_target's entry as "Conflicted", and start tracking
> + the backup files in the entry as well. */
> + SVN_ERR(svn_wc__loggy_entry_modify
> + (log_accum, adm_access,
> + merge_target, &tmp_entry,
> + SVN_WC__ENTRY_MODIFY_CONFLICT_OLD
> + | SVN_WC__ENTRY_MODIFY_CONFLICT_NEW
> + | SVN_WC__ENTRY_MODIFY_CONFLICT_WRK,
> + pool));
> +
> + *merge_outcome = svn_wc_merge_conflict;
> + }
> + else if (contains_conflicts && dry_run)
> + {
> + *merge_outcome = svn_wc_merge_conflict;
> + } /* end of conflict handling */
> + else if (copyfrom_text)
> + {
> + *merge_outcome = svn_wc_merge_merged;
> + }
> + else
> + {
> + svn_boolean_t same, special;
> + /* If 'special', then use the detranslated form of the
> + target file. This is so we don't try to follow symlinks,
> + but the same treatment is probably also appropriate for
> + whatever special file types we may invent in the future. */
> + SVN_ERR(svn_wc__get_special(&special, merge_target,
> + adm_access, pool));
> + SVN_ERR(svn_io_files_contents_same_p(&same, result_target,
> + (special ?
> + tmp_target :
> + merge_target),
> + pool));
> +
> + *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged;
> + }
> +
> + if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run)
> + /* replace MERGE_TARGET with the new merged file, expanding. */
> + SVN_ERR(svn_wc__loggy_copy(log_accum,
> + adm_access,
> + result_target, merge_target,
> + pool));
>
> -/* Make a new file baton in the provided POOL, with PB as the parent baton.
> - PATH is relative to the root of the edit. */
> + } /* end of merging for text files */
>
> -static svn_error_t *
> -make_file_baton(struct file_baton **f_p,
> - struct dir_baton *pb,
> - const char *path,
> - svn_boolean_t adding,
> - apr_pool_t *pool)
> -{
> - struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
> + else if (! dry_run) /* merging procedure for binary files */
> + {
> + /* ### when making the binary-file backups, should we be honoring
> + keywords and eol stuff? */
>
> - SVN_ERR_ASSERT(path);
> + const char *left_copy, *right_copy;
> + const char *parentt, *left_base, *right_base;
> + svn_wc_entry_t tmp_entry;
>
> - /* Make the file's on-disk name. */
> - f->path = svn_path_join(pb->edit_baton->anchor, path, pool);
> - f->name = svn_path_basename(path, pool);
> + /* Give the conflict resolution callback a chance to clean
> + up the conflict before we mark the file 'conflicted' */
> + if (conflict_func)
> + {
> + svn_wc_conflict_result_t *result = NULL;
> + svn_wc_conflict_description_t *cdesc;
> +
> + cdesc = svn_wc_conflict_description_create_text(merge_target,
> + adm_access, pool);
> + cdesc->is_binary = TRUE;
> + cdesc->mime_type = (mimeprop && mimeprop->value)
> + ? mimeprop->value->data : NULL,
> + cdesc->base_file = left;
> + cdesc->their_file = right;
> + cdesc->my_file = tmp_target;
> + cdesc->merged_file = NULL; /* notice there is NO merged file! */
> +
> + cdesc->older_version = left_version;
> + cdesc->their_version = right_version;
> +
> + SVN_ERR(conflict_func(&result, cdesc, conflict_baton, pool));
> + if (result == NULL)
> + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
> + NULL, _("Conflict callback violated API:"
> + " returned no results"));
> +
> + switch (result->choice)
> + {
> + /* For a binary file, there's no merged file to look at,
> + unless the conflict-callback did the merging itself. */
> + case svn_wc_conflict_choose_base:
> + {
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + left, merge_target,
> + pool));
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + case svn_wc_conflict_choose_theirs_full:
> + {
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + right, merge_target,
> + pool));
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + /* For a binary file, if the response is to use the
> + user's file, we do nothing. We also do nothing if
> + the response claims to have already resolved the
> + problem.*/
> + case svn_wc_conflict_choose_mine_full:
> + {
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + case svn_wc_conflict_choose_merged:
> + {
> + if (! result->merged_file)
> + {
> + /* Callback asked us to choose its own
> + merged file, but didn't provide one! */
> + return svn_error_create
> + (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE,
> + NULL, _("Conflict callback violated API:"
> + " returned no merged file"));
> + }
> + else
> + {
> + SVN_ERR(svn_wc__loggy_copy(log_accum, adm_access,
> + result->merged_file,
> + merge_target,
> + pool));
> + *merge_outcome = svn_wc_merge_merged;
> + contains_conflicts = FALSE;
> + goto merge_complete;
> + }
> + }
> + case svn_wc_conflict_choose_postpone:
> + default:
> + {
> + /* Assume conflict remains, fall through to code below. */
> + }
> + }
> + }
>
> - /* Figure out the new_URL for this file. */
> - if (pb->edit_baton->switch_url)
> - {
> - f->new_URL = svn_path_url_add_component(pb->new_URL, f->name, pool);
> - }
> - else
> - {
> - f->new_URL = get_entry_url(pb->edit_baton->adm_access,
> - pb->path, f->name, pool);
> - }
> + /* reserve names for backups of left and right fulltexts */
> + SVN_ERR(svn_io_open_uniquely_named(NULL,
> + &left_copy,
> + merge_dirpath,
> + merge_filename,
> + left_label,
> + svn_io_file_del_none,
> + pool, pool));
> +
> + SVN_ERR(svn_io_open_uniquely_named(NULL,
> + &right_copy,
> + merge_dirpath,
> + merge_filename,
> + right_label,
> + svn_io_file_del_none,
> + pool, pool));
> +
> + /* create the backup files */
> + SVN_ERR(svn_io_copy_file(left,
> + left_copy, TRUE, pool));
> + SVN_ERR(svn_io_copy_file(right,
> + right_copy, TRUE, pool));
> +
> + /* Was the merge target detranslated? */
> + if (merge_target != tmp_target)
> + {
> + /* Create a .mine file too */
> + const char *mine_copy;
> +
> + SVN_ERR(svn_io_open_uniquely_named(NULL,
> + &mine_copy,
> + merge_dirpath,
> + merge_filename,
> + target_label,
> + svn_io_file_del_none,
> + pool, pool));
> + SVN_ERR(svn_wc__loggy_move(log_accum,
> + adm_access,
> + tmp_target,
> + mine_copy,
> + pool));
> + mine_copy = svn_path_is_child(adm_path, mine_copy, pool);
> + tmp_entry.conflict_wrk = mine_copy;
> + }
> + else
> + tmp_entry.conflict_wrk = NULL;
>
> - f->pool = pool;
> - f->edit_baton = pb->edit_baton;
> - f->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t));
> - f->bump_info = pb->bump_info;
> - f->added = adding;
> - f->existed = FALSE;
> - f->add_existed = FALSE;
> - f->tree_conflicted = FALSE;
> - f->dir_baton = pb;
> + /* Derive the basenames of the backup files. */
> + svn_path_split(left_copy, &parentt, &left_base, pool);
> + svn_path_split(right_copy, &parentt, &right_base, pool);
> +
> + /* Mark merge_target's entry as "Conflicted", and start tracking
> + the backup files in the entry as well. */
> + tmp_entry.conflict_old = left_base;
> + tmp_entry.conflict_new = right_base;
> + SVN_ERR(svn_wc__loggy_entry_modify
> + (log_accum,
> + adm_access, merge_target,
> + &tmp_entry,
> + SVN_WC__ENTRY_MODIFY_CONFLICT_OLD
> + | SVN_WC__ENTRY_MODIFY_CONFLICT_NEW
> + | SVN_WC__ENTRY_MODIFY_CONFLICT_WRK,
> + pool));
> +
> + *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */
> +
> + } /* end of binary conflict handling */
> + else
> + *merge_outcome = svn_wc_merge_conflict; /* dry_run for binary files. */
> +
> + merge_complete:
> + /* Merging is complete. Regardless of text or binariness, we might
> + need to tweak the executable bit on the new working file. */
> + if (! dry_run)
> + {
> + SVN_ERR(svn_wc__loggy_maybe_set_executable(log_accum,
> + adm_access, merge_target,
> + pool));
>
> - /* No need to initialize f->digest, since we used pcalloc(). */
> + SVN_ERR(svn_wc__loggy_maybe_set_readonly(log_accum,
> + adm_access, merge_target,
> + pool));
>
> - /* the directory's bump info has one more referer now */
> - ++f->bump_info->ref_count;
> + }
>
> - *f_p = f;
> return SVN_NO_ERROR;
> }
>
> -
>
> -/*** Helpers for the editor callbacks. ***/
>
> -static svn_error_t *
> -window_handler(svn_txdelta_window_t *window, void *baton)
> +svn_error_t *
> +svn_wc_merge3(enum svn_wc_merge_outcome_t *merge_outcome,
> + const char *left,
> + const char *right,
> + const char *merge_target,
> + svn_wc_adm_access_t *adm_access,
> + const char *left_label,
> + const char *right_label,
> + const char *target_label,
> + svn_boolean_t dry_run,
> + const char *diff3_cmd,
> + const apr_array_header_t *merge_options,
> + const apr_array_header_t *prop_diff,
> + svn_wc_conflict_resolver_func_t conflict_func,
> + void *conflict_baton,
> + apr_pool_t *pool)
> {
> - struct handler_baton *hb = baton;
> - struct file_baton *fb = hb->fb;
> - svn_error_t *err;
> -
> - /* Apply this window. We may be done at that point. */
> - err = hb->apply_handler(window, hb->apply_baton);
> - if (window != NULL && !err)
> - return SVN_NO_ERROR;
> -
> - if (err)
> - {
> - /* We failed to apply the delta; clean up the temporary file. */
> - svn_error_clear(svn_io_remove_file(hb->work_path, hb->pool));
> - }
> - else
> - {
> - /* Tell the file baton about the new text base. */
> - fb->new_text_base_path = apr_pstrdup(fb->pool, hb->work_path);
> + svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
>
> - /* ... and its checksum. */
> - fb->actual_checksum = svn_checksum_create(svn_checksum_md5, fb->pool);
> - fb->actual_checksum->digest = apr_pmemdup(fb->pool, hb->digest,
> - sizeof(hb->digest));
> - }
> + SVN_ERR(svn_wc__merge_internal(&log_accum, merge_outcome,
> + left, NULL,
> + right, NULL,
> + merge_target,
> + NULL,
> + adm_access,
> + left_label, right_label, target_label,
> + dry_run,
> + diff3_cmd,
> + merge_options,
> + prop_diff,
> + conflict_func, conflict_baton,
> + pool));
>
> - svn_pool_destroy(hb->pool);
> + /* Write our accumulation of log entries into a log file */
> + SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
>
> - return err;
> + return svn_wc__run_log(adm_access, NULL, pool);
> }
>
>
> -/* Prepare directory for dir_baton DB for updating or checking out.
> - * Give it depth DEPTH.
> - *
> - * If the path already exists, but is not a working copy for
> - * ANCESTOR_URL and ANCESTOR_REVISION, then an error will be returned.
> - */
> -static svn_error_t *
> -prep_directory(struct dir_baton *db,
> - const char *ancestor_url,
> - svn_revnum_t ancestor_revision,
> - apr_pool_t *pool)
> +svn_error_t *
> +svn_wc_merge2(enum svn_wc_merge_outcome_t *merge_outcome,
> + const char *left,
> + const char *right,
> + const char *merge_target,
> + svn_wc_adm_access_t *adm_access,
> + const char *left_label,
> + const char *right_label,
> + const char *target_label,
> + svn_boolean_t dry_run,
> + const char *diff3_cmd,
> + const apr_array_header_t *merge_options,
> + apr_pool_t *pool)
> {
> - const char *repos;
> + return svn_wc_merge3(merge_outcome,
> + left, right, merge_target, adm_access,
> + left_label, right_label, target_label,
> + dry_run, diff3_cmd, merge_options, NULL,
> + NULL, NULL, pool);
> +}
>
> - /* Make sure the directory exists. */
> - SVN_ERR(svn_wc__ensure_directory(db->path, pool));
> +svn_error_t *
> +svn_wc_merge(const char *left,
> + const char *right,
> + const char *merge_target,
> + svn_wc_adm_access_t *adm_access,
> + const char *left_label,
> + const char *right_label,
> + const char *target_label,
> + svn_boolean_t dry_run,
> + enum svn_wc_merge_outcome_t *merge_outcome,
> + const char *diff3_cmd,
> + apr_pool_t *pool)
> +{
> + return svn_wc_merge3(merge_outcome,
> + left, right, merge_target, adm_access,
> + left_label, right_label, target_label,
> + dry_run, diff3_cmd, NULL, NULL, NULL,
> + NULL, pool);
> +}
>
> - /* Use the repository root of the anchor, but only if it actually is an
> - ancestor of the URL of this directory. */
> - if (db->edit_baton->repos
> - && svn_path_is_ancestor(db->edit_baton->repos, ancestor_url))
> - repos = db->edit_baton->repos;
> - else
> - repos = NULL;
>
> - /* Make sure it's the right working copy, either by creating it so,
> - or by checking that it is so already. */
> - SVN_ERR(svn_wc_ensure_adm3(db->path, NULL,
> - ancestor_url, repos,
> - ancestor_revision, db->ambient_depth, pool));
> -
> - if (! db->edit_baton->adm_access
> - || strcmp(svn_wc_adm_access_path(db->edit_baton->adm_access),
> - db->path))
> - {
> - svn_wc_adm_access_t *adm_access;
> - apr_pool_t *adm_access_pool
> - = db->edit_baton->adm_access
> - ? svn_wc_adm_access_pool(db->edit_baton->adm_access)
> - : db->edit_baton->pool;
> - svn_error_t *err = svn_wc_adm_open3(&adm_access,
> - db->edit_baton->adm_access,
> - db->path, TRUE, 0, NULL, NULL,
> - adm_access_pool);
> -
> - /* db->path may be scheduled for addition without history.
> - In that case db->edit_baton->adm_access already has it locked. */
> - if (err && err->apr_err == SVN_ERR_WC_LOCKED)
> - {
> - svn_error_clear(err);
> - err = svn_wc_adm_retrieve(&adm_access,
> - db->edit_baton->adm_access,
> - db->path, adm_access_pool);
> - }
>
> - SVN_ERR(err);
> +/* Constructor for the result-structure returned by conflict callbacks. */
> +svn_wc_conflict_result_t *
> +svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice,
> + const char *merged_file,
> + apr_pool_t *pool)
> +{
> + svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result));
> + result->choice = choice;
> + result->merged_file = merged_file;
> + result->save_merged = FALSE;
>
> - if (!db->edit_baton->adm_access)
> - db->edit_baton->adm_access = adm_access;
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Accumulate tags in LOG_ACCUM to set ENTRY_PROPS for PATH.
> - ENTRY_PROPS is an array of svn_prop_t* entry props.
> - If ENTRY_PROPS contains the removal of a lock token, all entryprops
> - related to a lock will be removed and LOCK_STATE, if non-NULL, will be
> - set to svn_wc_notify_lock_state_unlocked. Else, LOCK_STATE, if non-NULL
> - will be set to svn_wc_lock_state_unchanged. */
> -static svn_error_t *
> -accumulate_entry_props(svn_stringbuf_t *log_accum,
> - svn_wc_notify_lock_state_t *lock_state,
> - svn_wc_adm_access_t *adm_access,
> - const char *path,
> - apr_array_header_t *entry_props,
> - apr_pool_t *pool)
> -{
> - int i;
> - svn_wc_entry_t tmp_entry;
> - apr_uint64_t flags = 0;
> -
> - if (lock_state)
> - *lock_state = svn_wc_notify_lock_state_unchanged;
> -
> - for (i = 0; i < entry_props->nelts; ++i)
> - {
> - const svn_prop_t *prop = &APR_ARRAY_IDX(entry_props, i, svn_prop_t);
> - const char *val;
> -
> - /* The removal of the lock-token entryprop means that the lock was
> - defunct. */
> - if (! strcmp(prop->name, SVN_PROP_ENTRY_LOCK_TOKEN))
> - {
> - SVN_ERR(svn_wc__loggy_delete_lock
> - (&log_accum, adm_access, path, pool));
> -
> - if (lock_state)
> - *lock_state = svn_wc_notify_lock_state_unlocked;
> - continue;
> - }
> - /* A prop value of NULL means the information was not
> - available. We don't remove this field from the entries
> - file; we have convention just leave it empty. So let's
> - just skip those entry props that have no values. */
> - if (! prop->value)
> - continue;
> -
> - val = prop->value->data;
> -
> - if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
> - {
> - flags |= SVN_WC__ENTRY_MODIFY_CMT_AUTHOR;
> - tmp_entry.cmt_author = val;
> - }
> - else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
> - {
> - flags |= SVN_WC__ENTRY_MODIFY_CMT_REV;
> - tmp_entry.cmt_rev = SVN_STR_TO_REV(val);
> - }
> - else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
> - {
> - flags |= SVN_WC__ENTRY_MODIFY_CMT_DATE;
> - SVN_ERR(svn_time_from_cstring(&tmp_entry.cmt_date, val, pool));
> - }
> - else if (! strcmp(prop->name, SVN_PROP_ENTRY_UUID))
> - {
> - flags |= SVN_WC__ENTRY_MODIFY_UUID;
> - tmp_entry.uuid = val;
> - }
> - }
> -
> - if (flags)
> - SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access, path,
> - &tmp_entry, flags, pool));
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Accumulate tags in LOG_ACCUM to set WCPROPS for PATH. WCPROPS is
> - an array of svn_prop_t* wc props. */
> -static svn_error_t *
> -accumulate_wcprops(svn_stringbuf_t *log_accum,
> - svn_wc_adm_access_t *adm_access,
> - const char *path,
> - apr_array_header_t *wcprops,
> - apr_pool_t *pool)
> -{
> - int i;
> -
> - /* ### The log file will rewrite the props file for each property :( It
> - ### would be better if all the changes could be combined into one
> - ### write. */
> - for (i = 0; i < wcprops->nelts; ++i)
> - {
> - const svn_prop_t *prop = &APR_ARRAY_IDX(wcprops, i, svn_prop_t);
> -
> - SVN_ERR(svn_wc__loggy_modify_wcprop
> - (&log_accum, adm_access, path,
> - prop->name, prop->value ? prop->value->data : NULL, pool));
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Check that when ADD_PATH is joined to BASE_PATH, the resulting path
> - * is still under BASE_PATH in the local filesystem. If not, return
> - * SVN_ERR_WC_OBSTRUCTED_UPDATE; else return success.
> - *
> - * This is to prevent the situation where the repository contains,
> - * say, "..\nastyfile". Although that's perfectly legal on some
> - * systems, when checked out onto Win32 it would cause "nastyfile" to
> - * be created in the parent of the current edit directory.
> - *
> - * (http://cve.mitre.org/cgi-bin/cvename.cgi?name=2007-3846)
> - */
> -static svn_error_t *
> -check_path_under_root(const char *base_path,
> - const char *add_path,
> - apr_pool_t *pool)
> -{
> - char *full_path;
> - apr_status_t path_status;
> -
> - path_status = apr_filepath_merge
> - (&full_path, base_path, add_path,
> - APR_FILEPATH_NOTABOVEROOT | APR_FILEPATH_SECUREROOTTEST,
> - pool);
> -
> - if (path_status != APR_SUCCESS)
> - {
> - return svn_error_createf
> - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
> - _("Path '%s' is not in the working copy"),
> - /* Not using full_path here because it might be NULL or
> - undefined, since apr_filepath_merge() returned error.
> - (Pity we can't pass NULL for &full_path in the first place,
> - but the APR docs don't bless that.) */
> - svn_path_local_style(svn_path_join(base_path, add_path, pool), pool));
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -set_target_revision(void *edit_baton,
> - svn_revnum_t target_revision,
> - apr_pool_t *pool)
> -{
> - struct edit_baton *eb = edit_baton;
> -
> - /* Stashing a target_revision in the baton */
> - *(eb->target_revision) = target_revision;
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -open_root(void *edit_baton,
> - svn_revnum_t base_revision, /* This is ignored in co */
> - apr_pool_t *pool,
> - void **dir_baton)
> -{
> - struct edit_baton *eb = edit_baton;
> - struct dir_baton *d;
> -
> - /* Note that something interesting is actually happening in this
> - edit run. */
> - eb->root_opened = TRUE;
> -
> - SVN_ERR(make_dir_baton(&d, NULL, eb, NULL, FALSE, pool));
> - *dir_baton = d;
> -
> - if (! *eb->target)
> - {
> - /* For an update with a NULL target, this is equivalent to open_dir(): */
> - svn_wc_adm_access_t *adm_access;
> - svn_wc_entry_t tmp_entry;
> - const svn_wc_entry_t *entry;
> - apr_uint64_t flags = SVN_WC__ENTRY_MODIFY_REVISION |
> - SVN_WC__ENTRY_MODIFY_URL | SVN_WC__ENTRY_MODIFY_INCOMPLETE;
> -
> - /* Read the depth from the entry. */
> - SVN_ERR(svn_wc_entry(&entry, d->path, eb->adm_access,
> - FALSE, pool));
> - if (entry)
> - {
> - d->ambient_depth = entry->depth;
> - d->was_incomplete = entry->incomplete;
> - }
> -
> - /* ### TODO: Skip if inside a conflicted tree. */
> -
> - /* Mark directory as being at target_revision, but incomplete. */
> - tmp_entry.revision = *(eb->target_revision);
> - tmp_entry.url = d->new_URL;
> - /* See open_directory() for why this check is necessary. */
> - if (eb->repos && svn_path_is_ancestor(eb->repos, d->new_URL))
> - {
> - tmp_entry.repos = eb->repos;
> - flags |= SVN_WC__ENTRY_MODIFY_REPOS;
> - }
> - tmp_entry.incomplete = TRUE;
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access,
> - d->path, pool));
> - SVN_ERR(svn_wc__entry_modify(adm_access, NULL /* THIS_DIR */,
> - &tmp_entry, flags,
> - TRUE /* immediate write */,
> - pool));
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Helper for delete_entry() and do_entry_deletion().
> -
> - If the error chain ERR contains evidence that a local mod was left
> - (an SVN_ERR_WC_LEFT_LOCAL_MOD error), clear ERR. Otherwise, return ERR.
> -*/
> -static svn_error_t *
> -leftmod_error_chain(svn_error_t *err,
> - const char *logfile,
> - const char *path,
> - apr_pool_t *pool)
> -{
> - svn_error_t *tmp_err;
> -
> - if (! err)
> - return SVN_NO_ERROR;
> -
> - /* Advance TMP_ERR to the part of the error chain that reveals that
> - a local mod was left, or to the NULL end of the chain. */
> - for (tmp_err = err; tmp_err; tmp_err = tmp_err->child)
> - if (tmp_err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
> - break;
> -
> - /* If we found a "left a local mod" error, tolerate it
> - and clear the whole error. In that case we continue with
> - modified files left on the disk. */
> - if (tmp_err)
> - {
> - svn_error_clear(err);
> - return SVN_NO_ERROR;
> - }
> -
> - /* Otherwise, we just return our top-most error. */
> - return err;
> -}
> -
> -
> -/* ===================================================================== */
> -/* Checking for local modifications. */
> -
> -/* Set *MODIFIED to true iff the item described by (ADM_ACCESS, FULL_PATH,
> - * KIND) has local modifications.
> - * For a file, this means text mods or property mods.
> - * For a directory, this means property mods.
> - */
> -static svn_error_t *
> -entry_has_local_mods(svn_boolean_t *modified,
> - svn_wc_adm_access_t *adm_access,
> - svn_node_kind_t kind,
> - const char *full_path,
> - apr_pool_t *pool)
> -{
> - svn_boolean_t text_modified;
> - svn_boolean_t props_modified;
> -
> - /* Check for text modifications */
> - if (kind == svn_node_file)
> - SVN_ERR(svn_wc_text_modified_p(&text_modified, full_path, FALSE,
> - adm_access, pool));
> - else
> - text_modified = FALSE;
> -
> - /* Check for property modifications */
> - SVN_ERR(svn_wc_props_modified_p(&props_modified, full_path,
> - adm_access, pool));
> -
> - *modified = (text_modified || props_modified);
> - return SVN_NO_ERROR;
> -}
> -
> -/* A baton for use with modcheck_found_entry(). */
> -typedef struct modcheck_baton_t {
> - svn_wc_adm_access_t *adm_access; /* access for the root of the sub-tree */
> - svn_boolean_t found_mod; /* whether a modification has been found */
> -} modcheck_baton_t;
> -
> -static svn_error_t *
> -modcheck_found_entry(const char *path,
> - const svn_wc_entry_t *entry,
> - void *walk_baton,
> - apr_pool_t *pool)
> -{
> - modcheck_baton_t *baton = walk_baton;
> - svn_error_t *err;
> - svn_wc_adm_access_t *adm_access;
> - svn_boolean_t modified;
> -
> - /* Try to get a WC access baton for this node. */
> - err = svn_wc_adm_probe_retrieve(&adm_access, baton->adm_access, path, pool);
> -
> - /* If this node is not locked, e.g. because of a shallow update command
> - * such as "svn update --depth=immediates", then acquire a deep lock on
> - * this node. */
> - if (err)
> - {
> - if (err->apr_err == SVN_ERR_WC_NOT_LOCKED)
> - {
> - svn_error_clear(err);
> - SVN_ERR(svn_wc_adm_open3(&adm_access, baton->adm_access, path,
> - FALSE /* read-only */, -1 /* infinite */,
> - NULL, NULL /* no cancellation */, pool));
> - }
> - else
> - return err;
> - }
> -
> - SVN_ERR(entry_has_local_mods(&modified, adm_access, entry->kind, path, pool));
> - if (modified)
> - baton->found_mod = TRUE;
> -
> - return SVN_NO_ERROR;
> -}
> -
> -static svn_error_t *
> -modcheck_handle_error(const char *path,
> - svn_error_t *err,
> - void *walk_baton,
> - apr_pool_t *pool)
> -{
> - return err;
> -}
> -
> -/* Set *MODIFIED to true iff there are any local modifications within the
> - * tree rooted at PATH whose admin access baton is ADM_ACCESS. PATH may be a
> - * file or a directory. */
> -static svn_error_t *
> -tree_has_local_mods(svn_boolean_t *modified,
> - const char *path,
> - svn_wc_adm_access_t *adm_access,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - apr_pool_t *pool)
> -{
> - static const svn_wc_entry_callbacks2_t modcheck_callbacks =
> - { modcheck_found_entry, modcheck_handle_error };
> - modcheck_baton_t modcheck_baton = { NULL, FALSE };
> -
> - modcheck_baton.adm_access = adm_access;
> -
> - /* Walk the WC tree to its full depth, looking for any local modifications.
> - * If it's a "sparse" directory, that's OK: there can be no local mods in
> - * the pieces that aren't present in the WC. */
> -
> - SVN_ERR(svn_wc_walk_entries3(path, adm_access,
> - &modcheck_callbacks, &modcheck_baton,
> - svn_depth_infinity, FALSE /* show_hidden */,
> - cancel_func, cancel_baton,
> - pool));
> -
> - *modified = modcheck_baton.found_mod;
> - return SVN_NO_ERROR;
> -}
> -
> -/* Check whether the incoming change ACTION on FULL_PATH would conflict with
> - * FULL_PATH's scheduled change. If so, then raise a tree conflict with
> - * FULL_PATH as the victim, by appending log actions to LOG_ACCUM.
> - *
> - * The edit baton EB gives information including whether the operation is
> - * an update or a switch.
> - *
> - * ENTRY is the wc-entry for FULL_PATH, if there is one (even if
> - * schedule-delete etc.), or NULL if FULL_PATH is unversioned or does
> - * not exist. PARENT_ADM_ACCESS is the admin access baton of
> - * FULL_PATH's parent directory.
> - *
> - * If PCONFLICT is not null, set *PCONFLICT to the conflict description if
> - * there is one or else to null.
> - *
> - * Tree conflict use cases are described in issue #2282 and in
> - * notest/tree-conflicts/detection.txt.
> - */
> -static svn_error_t *
> -check_tree_conflict(svn_wc_conflict_description_t **pconflict,
> - struct edit_baton *eb,
> - svn_stringbuf_t *log_accum,
> - const char *full_path,
> - const svn_wc_entry_t *entry,
> - svn_wc_adm_access_t *parent_adm_access,
> - svn_wc_conflict_action_t action,
> - svn_node_kind_t their_node_kind,
> - const char *their_url,
> - apr_pool_t *pool)
> -{
> - svn_wc_conflict_reason_t reason = (svn_wc_conflict_reason_t)(-1);
> -
> - switch (action)
> - {
> - case svn_wc_conflict_action_edit:
> - /* Use case 1: Modifying a locally-deleted item. */
> - if (entry->schedule == svn_wc_schedule_delete
> - || entry->schedule == svn_wc_schedule_replace)
> - reason = svn_wc_conflict_reason_deleted;
> - break;
> -
> - case svn_wc_conflict_action_add:
> - /* Use case "3.5": Adding a locally-added item.
> - *
> - * When checking out a file-external, add_file() is called twice:
> - * 1.) In the main update, a minimal entry is created.
> - * 2.) In the external update, the file is added properly.
> - * Don't raise a tree conflict the second time! */
> - if (entry && !entry->file_external_path)
> - reason = svn_wc_conflict_reason_added;
> - break;
> -
> - case svn_wc_conflict_action_delete:
> - /* Use case 3: Deleting a locally-deleted item. */
> - their_node_kind = svn_node_none;
> - if (entry->schedule == svn_wc_schedule_delete
> - || entry->schedule == svn_wc_schedule_replace)
> - reason = svn_wc_conflict_reason_deleted;
> - else
> - {
> - svn_boolean_t modified = FALSE;
> - svn_wc_adm_access_t *adm_access;
> -
> - /* Use case 2: Deleting a locally-modified item. */
> - if (entry->kind == svn_node_file)
> - SVN_ERR(entry_has_local_mods(&modified, parent_adm_access,
> - entry->kind, full_path, pool));
> -
> - else if (entry->kind == svn_node_dir)
> - {
> - /* We must detect deep modifications in a directory tree,
> - * but the update editor will not visit the subdirectories
> - * of a directory that it wants to delete. Therefore, we
> - * need to start a separate crawl here. */
> - SVN_ERR(svn_wc_adm_probe_retrieve(&adm_access, parent_adm_access,
> - full_path, pool));
> -
> - /* Ensure that the access baton is specific to FULL_PATH,
> - * otherwise the crawl will start at the parent. */
> - if (strcmp(svn_wc_adm_access_path(adm_access), full_path) == 0)
> - SVN_ERR(tree_has_local_mods(&modified, full_path, adm_access,
> - eb->cancel_func, eb->cancel_baton,
> - pool));
> - }
> -
> - if (modified)
> - reason = svn_wc_conflict_reason_edited;
> -
> - }
> - break;
> - }
> -
> - if (pconflict)
> - *pconflict = NULL;
> -
> - /* If a conflict was detected, append log commands to the log accumulator
> - * to record it. */
> - if (reason != (svn_wc_conflict_reason_t)(-1))
> - {
> - svn_wc_conflict_description_t *conflict;
> - svn_wc_conflict_version_t *older_version;
> - svn_wc_conflict_version_t *their_version;
> - const char *repos_url = NULL;
> - const char *path_in_repos = NULL;
> -
> - repos_url = entry->repos;
> - path_in_repos = svn_path_is_child(repos_url, entry->url, pool);
> - if (path_in_repos == NULL)
> - path_in_repos = ".";
> - /* "Their" repos_url (repository root URL) will be the same. */
> -
> - if (eb->switch_url != NULL)
> - {
> - if (their_url != NULL)
> - path_in_repos = svn_path_is_child(repos_url, their_url, pool);
> - else
> - {
> - /* ### TODO do_entry_deletion() still passes NULL for their_url
> - * */
> - path_in_repos = svn_path_is_child(repos_url, eb->switch_url,
> - pool);
> - path_in_repos = apr_pstrcat(
> - pool, path_in_repos,
> - "<### TODO incomplete for incoming delete>",
> - NULL);
> - }
> - }
> -
> - older_version = apr_pcalloc(pool, sizeof(*older_version));
> - older_version->repos_url = repos_url;
> - older_version->peg_rev = entry->revision;
> - older_version->path_in_repos = path_in_repos;
> - older_version->node_kind =
> - (entry->schedule == svn_wc_schedule_delete) ? svn_node_none
> - : entry->kind;
> - /* entry->kind is both base kind and working kind, because schedule
> - * replace-by-different-kind is not supported. */
> - /* ### TODO: but in case the entry is locally removed, entry->kind
> - * is svn_node_none and doesn't reflect the older kind. */
> -
> - their_version = apr_pcalloc(pool, sizeof(*older_version));
> - their_version->repos_url = repos_url;
> - their_version->peg_rev = *eb->target_revision;
> - their_version->path_in_repos = path_in_repos;
> - their_version->node_kind = their_node_kind;
> -
> - conflict = svn_wc_conflict_description_create_tree(
> - full_path, parent_adm_access, entry->kind,
> - eb->switch_url ? svn_wc_operation_switch : svn_wc_operation_update,
> - older_version, their_version, pool);
> - conflict->action = action;
> - conflict->reason = reason;
> -
> - /* Ensure 'log_accum' is non-null. svn_wc__loggy_add_tree_conflict()
> - * would otherwise quietly set it to point to a newly allocated buffer
> - * but we have no way to propagate that back to our caller. */
> - SVN_ERR_ASSERT(log_accum != NULL);
> -
> - SVN_ERR(svn_wc__loggy_add_tree_conflict(&log_accum, conflict,
> - parent_adm_access, pool));
> -
> - if (pconflict)
> - *pconflict = conflict;
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -/* If PATH is inside a conflicted tree, return the tree conflict's victim
> - * in VICTIM_PATH. Otherwise set VICTIM_PATH to NULL.
> - *
> - * The search begins at the working copy root, returning the first
> - * ("highest") tree conflict victim, which may be PATH itself.
> - *
> - * CANCEL_FUNC and CANCEL_BATON are used when trying to get admin
> - * access batons.
> - *
> - * ### We don't have an ADM_ACCESS parameter to serve as associated
> - * access baton. If we did, then ADM_ACCESS would end up associated
> - * with ancestor dirs, which causes an error when ADM_ACCESS is
> - * eventually closed at the end of svn_client__update_internal().
> - *
> - * ### This function uses svn_wc_adm_probe_open3() liberally, which is
> - * probably not very efficent. And it doesn't memoize, so it looks up
> - * ancestors all the way to the root for every single PATH.
> - */
> -static svn_error_t *
> -already_in_a_tree_conflict(char **victim_path,
> - const char *path,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - apr_pool_t *pool)
> -{
> - svn_boolean_t is_wc_root, tree_conflicted;
> - char *ancestor;
> - apr_array_header_t *ancestors;
> - svn_wc_adm_access_t *ancestor_access;
> - const svn_wc_entry_t *entry;
> - int i;
> -
> - *victim_path = NULL;
> - ancestor = apr_pstrdup(pool, path);
> - ancestors = apr_array_make(pool, 0, sizeof(char *));
> -
> - /* If PATH is under version control, put it on the ancestor list. */
> - SVN_ERR(svn_wc_adm_probe_open3(&ancestor_access, NULL, ancestor, FALSE, 0,
> - cancel_func, cancel_baton, pool));
> -
> - SVN_ERR(svn_wc_entry(&entry, ancestor, ancestor_access, TRUE, pool));
> -
> - if (entry != NULL)
> - APR_ARRAY_PUSH(ancestors, char *) = ancestor;
> -
> - ancestor = svn_path_dirname(ancestor, pool);
> -
> - /* Append to the list all ancestor-dirs in the working copy. Ignore
> - the root because it can't be tree-conflicted. */
> - while (! svn_path_is_empty(ancestor))
> - {
> - SVN_ERR(svn_wc_adm_probe_open3(&ancestor_access, NULL, ancestor,
> - FALSE, 0, cancel_func, cancel_baton,
> - pool));
> -
> - if (ancestor_access == NULL)
> - break;
> -
> - SVN_ERR(svn_wc_is_wc_root(&is_wc_root, ancestor, ancestor_access, pool));
> -
> - if (is_wc_root)
> - break;
> - else
> - APR_ARRAY_PUSH(ancestors, char *) = ancestor;
> -
> - ancestor = svn_path_dirname(ancestor, pool);
> - }
> -
> - /* From the root end, check the conflict status of each ancestor. */
> - for (i = ancestors->nelts - 1; i >= 0; i--)
> - {
> - ancestor = APR_ARRAY_IDX(ancestors, i, char *);
> - SVN_ERR(svn_wc_conflicted_p2(NULL, NULL, &tree_conflicted, ancestor,
> - ancestor_access, pool));
> - if (tree_conflicted)
> - {
> - *victim_path = ancestor;
> -
> - return SVN_NO_ERROR;
> - }
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -/* Delete PATH from its immediate parent PARENT_PATH, in the edit
> - * represented by EB. Name temporary transactional logs based on
> - * *LOG_NUMBER, but set *LOG_NUMBER to 0 after running the final log.
> - * Perform all allocations in POOL.
> - */
> -static svn_error_t *
> -do_entry_deletion(struct edit_baton *eb,
> - const char *parent_path,
> - const char *path,
> - const char *their_path,
> - int *log_number,
> - apr_pool_t *pool)
> -{
> - svn_wc_adm_access_t *adm_access;
> - const svn_wc_entry_t *entry;
> - const char *full_path = svn_path_join(eb->anchor, path, pool);
> - char *victim_path;
> - svn_stringbuf_t *log_item = svn_stringbuf_create("", pool);
> - svn_wc_conflict_description_t *tree_conflict;
> - svn_wc_notify_t *notify;
> -
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access,
> - parent_path, pool));
> -
> - SVN_ERR(svn_wc__entry_versioned(&entry, full_path, adm_access, TRUE, pool));
> -
> - /* Receive the remote removal of excluded entry. Do not notify. */
> - if (entry->depth == svn_depth_exclude)
> - {
> - apr_hash_t *entries;
> - const char *base_name = svn_path_basename(full_path, pool);
> - SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
> - svn_wc__entry_remove(entries, base_name);
> - SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
> - if (strcmp(path, eb->target) == 0)
> - eb->target_deleted = TRUE;
> - return SVN_NO_ERROR;
> - }
> -
> - /* Is an ancestor-dir (already visited by this edit) a tree conflict
> - victim? If so, skip without notification. */
> - if (eb->current_conflict)
> - {
> - SVN_ERR_ASSERT(svn_path_is_ancestor(eb->current_conflict,
> - full_path));
> -
> - remember_skipped_path(eb, full_path);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - /* Is this path, or an ancestor-dir NOT visited by this edit, already
> - marked as a tree conflict victim? */
> - SVN_ERR(already_in_a_tree_conflict(&victim_path, full_path, eb->cancel_func,
> - eb->cancel_baton, pool));
> -
> - /* Is this path the victim of a newly-discovered tree conflict? */
> - tree_conflict = NULL;
> - if (victim_path == NULL)
> - SVN_ERR(check_tree_conflict(&tree_conflict, eb, log_item, full_path,
> - entry, adm_access,
> - svn_wc_conflict_action_delete,
> - svn_node_none, their_path, pool));
> -
> - if (tree_conflict != NULL)
> - {
> - /* Run the log immediately, so that the tree conflict is recorded. */
> - SVN_ERR(svn_wc__write_log(adm_access, *log_number, log_item, pool));
> - SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
> - *log_number = 0;
> - }
> -
> - if (victim_path != NULL || tree_conflict != NULL)
> - {
> - remember_skipped_path(eb, full_path);
> -
> - /* ### TODO: Also print victim_path in the skip msg. */
> - if (eb->notify_func)
> - (*eb->notify_func)(eb->notify_baton,
> - svn_wc_create_notify(full_path,
> - (tree_conflict != NULL)
> - ? svn_wc_notify_tree_conflict
> - : svn_wc_notify_skip,
> - pool),
> - pool);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - SVN_ERR(svn_wc__write_log(adm_access, *log_number, log_item, pool));
> -
> - /* If the thing being deleted is the *target* of this update, then
> - we may need to recreate an entry, so that the parent can give
> - accurate reports about itself in future. */
> - if (strcmp(path, eb->target) == 0)
> - {
> - svn_wc_entry_t tmp_entry;
> -
> - tmp_entry.revision = *(eb->target_revision);
> - tmp_entry.kind =
> - (entry->kind == svn_node_file) ? svn_node_file : svn_node_dir;
> -
> - SVN_ERR(svn_wc__loggy_entry_modify(&log_item, adm_access,
> - full_path, &tmp_entry,
> - SVN_WC__ENTRY_MODIFY_REVISION
> - | SVN_WC__ENTRY_MODIFY_KIND,
> - pool));
> - }
> -
> - SVN_ERR(svn_wc__loggy_delete_entry(&log_item, adm_access, full_path,
> - pool));
> -
> - /* If the thing being deleted is the *target* of this update, then
> - we need to recreate a 'deleted' entry, so that parent can give
> - accurate reports about itself in the future. */
> - if (strcmp(path, eb->target) == 0)
> - {
> - svn_wc_entry_t tmp_entry;
> -
> - tmp_entry.revision = *(eb->target_revision);
> - tmp_entry.kind =
> - (entry->kind == svn_node_file) ? svn_node_file : svn_node_dir; /* ### redundant? */
> - tmp_entry.deleted = TRUE;
> -
> - SVN_ERR(svn_wc__loggy_entry_modify(&log_item, adm_access,
> - full_path, &tmp_entry,
> - SVN_WC__ENTRY_MODIFY_REVISION
> - | SVN_WC__ENTRY_MODIFY_KIND /* ### redundant change? */
> - | SVN_WC__ENTRY_MODIFY_DELETED,
> - pool));
> -
> - eb->target_deleted = TRUE;
> - }
> -
> - SVN_ERR(svn_wc__write_log(adm_access, *log_number, log_item, pool));
> -
> - if (eb->switch_url)
> - {
> - /* The SVN_WC__LOG_DELETE_ENTRY log item will cause
> - * svn_wc_remove_from_revision_control() to be run. But that
> - * function checks whether the deletion target's URL is child of
> - * its parent directory's URL, and if it's not, then the entry
> - * in parent won't be deleted (because presumably the child
> - * represents a disjoint working copy, i.e., it is a wc_root).
> - *
> - * However, during a switch this works against us, because by
> - * the time we get here, the parent's URL has already been
> - * changed. So we manually remove the child from revision
> - * control after the delete-entry item has been written in the
> - * parent's log, but before it is run, so the only work left for
> - * the log item is to remove the entry in the parent directory.
> - */
> -
> - if (entry->kind == svn_node_dir)
> - {
> - svn_wc_adm_access_t *child_access;
> - const char *logfile_path
> - = svn_wc__adm_child(parent_path,
> - svn_wc__logfile_path(*log_number, pool),
> - pool);
> -
> - SVN_ERR(svn_wc_adm_retrieve
> - (&child_access, eb->adm_access,
> - full_path, pool));
> -
> - SVN_ERR(leftmod_error_chain
> - (svn_wc_remove_from_revision_control
> - (child_access,
> - SVN_WC_ENTRY_THIS_DIR,
> - TRUE, /* destroy */
> - FALSE, /* instant error */
> - eb->cancel_func,
> - eb->cancel_baton,
> - pool),
> - logfile_path, parent_path, pool));
> - }
> - }
> -
> - /* Note: these two lines are duplicated in the tree-conflicts bail out
> - * above. */
> - SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
> - *log_number = 0;
> -
> - if (eb->notify_func)
> - {
> - notify = svn_wc_create_notify(full_path,
> - (tree_conflict != NULL)
> - ? svn_wc_notify_tree_conflict
> - : svn_wc_notify_update_delete,
> - pool);
> -
> - (*eb->notify_func)(eb->notify_baton, notify, pool);
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -delete_entry(const char *path,
> - svn_revnum_t revision,
> - void *parent_baton,
> - apr_pool_t *pool)
> -{
> - struct dir_baton *pb = parent_baton;
> - const char *path_basename = svn_path_basename(path, pool);
> - const char *their_path = svn_path_url_add_component(pb->new_URL,
> - path_basename, pool);
> -
> - SVN_ERR(check_path_under_root(pb->path, path_basename, pool));
> - return do_entry_deletion(pb->edit_baton, pb->path, path, their_path,
> - &pb->log_number, pool);
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -add_directory(const char *path,
> - void *parent_baton,
> - const char *copyfrom_path,
> - svn_revnum_t copyfrom_revision,
> - apr_pool_t *pool,
> - void **child_baton)
> -{
> - struct dir_baton *pb = parent_baton;
> - struct edit_baton *eb = pb->edit_baton;
> - struct dir_baton *db;
> - svn_node_kind_t kind;
> - const char *full_path = svn_path_join(eb->anchor, path, pool);
> - char *victim_path;
> -
> - SVN_ERR(make_dir_baton(&db, path, eb, pb, TRUE, pool));
> - *child_baton = db;
> -
> - if (strcmp(eb->target, path) == 0)
> - {
> - /* The target of the edit is being added, give it the requested
> - depth of the edit (but convert svn_depth_unknown to
> - svn_depth_infinity). */
> - db->ambient_depth = (eb->requested_depth == svn_depth_unknown)
> - ? svn_depth_infinity : eb->requested_depth;
> - }
> - else if (eb->requested_depth == svn_depth_immediates
> - || (eb->requested_depth == svn_depth_unknown
> - && pb->ambient_depth == svn_depth_immediates))
> - {
> - db->ambient_depth = svn_depth_empty;
> - }
> - else
> - {
> - db->ambient_depth = svn_depth_infinity;
> - }
> -
> -
> - /* Flush the log for the parent directory before going into this subtree. */
> - SVN_ERR(flush_log(pb, pool));
> -
> - /* Semantic check. Either both "copyfrom" args are valid, or they're
> - NULL and SVN_INVALID_REVNUM. A mixture is illegal semantics. */
> - SVN_ERR_ASSERT((copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
> - || (!copyfrom_path &&
> - !SVN_IS_VALID_REVNUM(copyfrom_revision)));
> -
> - SVN_ERR(check_path_under_root(pb->path, db->name, pool));
> - SVN_ERR(svn_io_check_path(db->path, &kind, db->pool));
> -
> - /* The path can exist, but it must be a directory... */
> - if (kind == svn_node_file || kind == svn_node_unknown)
> - {
> - return svn_error_createf
> - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
> - _("Failed to add directory '%s': a non-directory object of the "
> - "same name already exists"),
> - svn_path_local_style(db->path, pool));
> - }
> -
> - /* Is an ancestor-dir (already visited by this edit) a tree conflict
> - victim? If so, skip without notification. */
> - if (eb->current_conflict)
> - {
> - SVN_ERR_ASSERT(svn_path_is_ancestor(eb->current_conflict,
> - full_path));
> -
> - remember_skipped_path(eb, full_path);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - /* Is this path, or an ancestor-dir NOT visited by this edit, already
> - marked as a tree conflict victim? */
> - SVN_ERR(already_in_a_tree_conflict(&victim_path, full_path, eb->cancel_func,
> - eb->cancel_baton, pool));
> -
> - if (victim_path != NULL)
> - {
> - /* Record this conflict so that its descendants are skipped silently. */
> - eb->current_conflict = victim_path;
> - remember_skipped_path(eb, full_path);
> -
> - /* ### TODO: Also print victim_path in the skip msg. */
> - if (eb->notify_func)
> - (*eb->notify_func)(eb->notify_baton,
> - svn_wc_create_notify(full_path,
> - svn_wc_notify_skip,
> - pool),
> - pool);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - if (kind == svn_node_dir)
> - {
> - /* ...Ok, it's a directory but it can't be versioned or
> - scheduled for addition with history. */
> - svn_wc_adm_access_t *adm_access;
> -
> - /* Test the obstructing dir to see if it's versioned. */
> - svn_error_t *err = svn_wc_adm_open3(&adm_access, NULL,
> - db->path, FALSE, 0,
> - NULL, NULL, pool);
> -
> - if (err && err->apr_err != SVN_ERR_WC_NOT_DIRECTORY)
> - {
> - /* Something quite unexpected has happened. */
> - return err;
> - }
> - else if (err) /* Not a versioned dir. */
> - {
> - svn_error_clear(err);
> - if (eb->allow_unver_obstructions)
> - {
> - /* Obstructing dir is not versioned, just need to flag it as
> - existing then we are done here. */
> - db->existed = TRUE;
> - }
> - else
> - {
> - /* ### TODO: raise a tree conflict */
> -
> - return svn_error_createf
> - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
> - _("Failed to add directory '%s': an unversioned "
> - "directory of the same name already exists"),
> - svn_path_local_style(db->path, pool));
> - }
> - }
> - else /* Obstructing dir *is* versioned or scheduled for addition. */
> - {
> - const svn_wc_entry_t *entry;
> -
> - SVN_ERR(svn_wc_entry(&entry, db->path, adm_access, FALSE, pool));
> -
> - /* Anything other than a dir scheduled for addition without
> - history is an error. */
> - /* ### what's this "add_existed" clause for? */
> - if (entry
> - && entry->schedule == svn_wc_schedule_add
> - && ! entry->copied)
> - {
> - db->add_existed = TRUE;
> - }
> - else
> - {
> - svn_wc_adm_access_t *parent_adm_access;
> - const char *repos;
> - svn_wc_conflict_description_t *tree_conflict;
> -
> - /* Use the repository root of the anchor, but only if it
> - actually is an ancestor of the URL of this directory. */
> - if (eb->repos && svn_path_is_ancestor(eb->repos, db->new_URL))
> - repos = eb->repos;
> - else
> - repos = NULL;
> -
> - /* Make sure it's the right working copy. */
> - SVN_ERR(svn_wc_ensure_adm3(db->path,
> - NULL /* TODO check uuid too.*/,
> - db->new_URL, repos,
> - *(eb->target_revision),
> - db->ambient_depth, pool));
> -
> - SVN_ERR(svn_wc_adm_retrieve(&parent_adm_access, eb->adm_access,
> - pb->path, pool));
> -
> - /* Raise a tree conflict if this directory is already present. */
> - SVN_ERR(check_tree_conflict(&tree_conflict, eb,
> - pb->log_accum, db->path, entry,
> - parent_adm_access,
> - svn_wc_conflict_action_add,
> - svn_node_dir, db->new_URL, pool));
> -
> - if (tree_conflict != NULL)
> - {
> - /* Record this conflict so that its descendants are skipped silently. */
> - eb->current_conflict = apr_pstrdup(pool,
> - tree_conflict->path);
> -
> - remember_skipped_path(eb, full_path);
> -
> - /* ### TODO: Also print victim_path in the skip msg. */
> - if (eb->notify_func)
> - (*eb->notify_func)(eb->notify_baton,
> - svn_wc_create_notify
> - (full_path,
> - svn_wc_notify_tree_conflict,
> - pool),
> - pool);
> -
> - return SVN_NO_ERROR;
> - }
> - }
> - }
> - }
> -
> - /* It may not be named the same as the administrative directory. */
> - if (svn_wc_is_adm_dir(svn_path_basename(path, pool), pool))
> - return svn_error_createf
> - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
> - _("Failed to add directory '%s': object of the same name as the "
> - "administrative directory"),
> - svn_path_local_style(db->path, pool));
> -
> -
> - /* Either we got real copyfrom args... */
> - if (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_revision))
> - {
> - /* ### todo: for now, this editor doesn't know how to deal with
> - copyfrom args. Someday it will interpet them as an update
> - optimization, and actually copy one part of the wc to another.
> - Then it will recursively "normalize" all the ancestry in the
> - copied tree. Someday!
> -
> - Note from the future: if someday it does, we'll probably want
> - to tweak libsvn_ra_neon/fetch.c:validate_element() to accept
> - that an add-dir element can contain a delete-entry element
> - (because the dir might be added with history). Currently
> - that combination will not validate. See r30161, and see the
> - thread in which this message appears:
> -
> - http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=136879
> - From: "David Glasser" <glasser_at_davidglasser.net>
> - To: "Karl Fogel" <kfogel_at_red-bean.com>, dev_at_subversion.tigris.org
> - Cc: "Arfrever Frehtes Taifersar Arahesis" <arfrever.fta_at_gmail.com>,
> - glasser_at_tigris.org
> - Subject: Re: svn commit: r30161 - in trunk/subversion: \
> - libsvn_ra_neon tests/cmdline
> - Date: Fri, 4 Apr 2008 14:47:06 -0700
> - Message-ID: <1ea387f60804041447q3aea0bbds10c2db3eacaf73e_at_mail.gmail.com>
> -
> - */
> - return svn_error_createf
> - (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> - _("Failed to add directory '%s': "
> - "copyfrom arguments not yet supported"),
> - svn_path_local_style(db->path, pool));
> - }
> - else /* ...or we got invalid copyfrom args. */
> - {
> - svn_wc_adm_access_t *adm_access;
> - svn_wc_entry_t tmp_entry;
> - apr_uint64_t modify_flags = SVN_WC__ENTRY_MODIFY_KIND |
> - SVN_WC__ENTRY_MODIFY_DELETED | SVN_WC__ENTRY_MODIFY_ABSENT;
> -
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access,
> - pb->path, db->pool));
> -
> - /* Immediately create an entry for the new directory in the parent.
> - Note that the parent must already be either added or opened, and
> - thus it's in an 'incomplete' state just like the new dir.
> - The entry may already exist if the new directory is already
> - scheduled for addition without history, in that case set
> - its schedule to normal. */
> - tmp_entry.kind = svn_node_dir;
> - /* Note that there may already exist a 'ghost' entry in the
> - parent with the same name, in a 'deleted' or 'absent' state.
> - If so, it's fine to overwrite it... but we need to make sure
> - we get rid of the state flag when doing so: */
> - tmp_entry.deleted = FALSE;
> - tmp_entry.absent = FALSE;
> -
> - if (db->add_existed)
> - {
> - tmp_entry.schedule = svn_wc_schedule_normal;
> - modify_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE |
> - SVN_WC__ENTRY_MODIFY_FORCE;
> - }
> -
> - SVN_ERR(svn_wc__entry_modify(adm_access, db->name, &tmp_entry,
> - modify_flags,
> - TRUE /* immediate write */, pool));
> -
> - if (db->add_existed)
> - {
> - /* Immediately tweak the schedule for "this dir" so it too
> - is no longer scheduled for addition. Change rev from 0
> - to the target revision allowing prep_directory() to do
> - its thing without error. */
> - modify_flags = SVN_WC__ENTRY_MODIFY_SCHEDULE
> - | SVN_WC__ENTRY_MODIFY_FORCE | SVN_WC__ENTRY_MODIFY_REVISION;
> -
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access,
> - db->edit_baton->adm_access,
> - db->path, pool));
> - tmp_entry.revision = *(eb->target_revision);
> -
> - if (eb->switch_url)
> - {
> - tmp_entry.url = svn_path_url_add_component(eb->switch_url,
> - db->name, pool);
> - modify_flags |= SVN_WC__ENTRY_MODIFY_URL;
> - }
> -
> - SVN_ERR(svn_wc__entry_modify(adm_access, NULL, &tmp_entry,
> - modify_flags,
> - TRUE /* immediate write */, pool));
> - }
> - }
> -
> - SVN_ERR(prep_directory(db,
> - db->new_URL,
> - *(eb->target_revision),
> - db->pool));
> -
> - /* If this add was obstructed by dir scheduled for addition without
> - history let close_file() handle the notification because there
> - might be properties to deal with. */
> - if (eb->notify_func && !(db->add_existed))
> - {
> - svn_wc_notify_t *notify = svn_wc_create_notify(
> - db->path,
> - db->existed
> - ? svn_wc_notify_exists
> - : svn_wc_notify_update_add,
> - pool);
> - notify->kind = svn_node_dir;
> - (*eb->notify_func)(eb->notify_baton, notify, pool);
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -open_directory(const char *path,
> - void *parent_baton,
> - svn_revnum_t base_revision,
> - apr_pool_t *pool,
> - void **child_baton)
> -{
> - struct dir_baton *db, *pb = parent_baton;
> - struct edit_baton *eb = pb->edit_baton;
> - const svn_wc_entry_t *entry;
> - svn_wc_entry_t tmp_entry;
> - apr_uint64_t flags = SVN_WC__ENTRY_MODIFY_REVISION |
> - SVN_WC__ENTRY_MODIFY_URL | SVN_WC__ENTRY_MODIFY_INCOMPLETE;
> -
> - svn_wc_adm_access_t *adm_access;
> - svn_wc_adm_access_t *parent_adm_access;
> - char *victim_path = NULL;
> - const char *full_path = svn_path_join(eb->anchor, path, pool);
> - svn_wc_conflict_description_t *tree_conflict;
> - svn_boolean_t prop_conflicted;
> -
> - SVN_ERR(make_dir_baton(&db, path, eb, pb, FALSE, pool));
> - *child_baton = db;
> -
> - /* Flush the log for the parent directory before going into this subtree. */
> - SVN_ERR(flush_log(pb, pool));
> -
> - SVN_ERR(check_path_under_root(pb->path, db->name, pool));
> -
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access,
> - db->path, pool));
> - SVN_ERR(svn_wc_adm_retrieve(&parent_adm_access, eb->adm_access,
> - pb->path, pool));
> -
> - SVN_ERR(svn_wc_entry(&entry, db->path, adm_access, FALSE, pool));
> - if (entry)
> - {
> - db->ambient_depth = entry->depth;
> - db->was_incomplete = entry->incomplete;
> - }
> -
> - /* Is an ancestor-dir (already visited by this edit) a tree conflict
> - victim? If so, skip the tree without notification. */
> - if (eb->current_conflict)
> - {
> - SVN_ERR_ASSERT(svn_path_is_ancestor(eb->current_conflict,
> - full_path));
> -
> - db->bump_info->skipped = TRUE;
> - remember_skipped_path(eb, full_path);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - /* Is this path, or an ancestor-dir NOT visited by this edit, already a
> - tree conflict victim? If so, skip the tree with one notification. */
> - SVN_ERR(already_in_a_tree_conflict(&victim_path, full_path, eb->cancel_func,
> - eb->cancel_baton, pool));
> -
> - if (victim_path != NULL)
> - {
> - eb->current_conflict = victim_path;
> - tree_conflict = NULL;
> - }
> - else
> - /* Is this path a fresh tree conflict victim? If so, skip the tree
> - with one notification. */
> - SVN_ERR(check_tree_conflict(&tree_conflict, eb, pb->log_accum,
> - full_path, entry, parent_adm_access,
> - svn_wc_conflict_action_edit,
> - svn_node_dir, db->new_URL, pool));
> -
> - /* If property-conflicted, skip the tree with notification. */
> - SVN_ERR(svn_wc_conflicted_p2(NULL, &prop_conflicted, NULL, full_path,
> - adm_access, pool));
> -
> - if (tree_conflict != NULL || prop_conflicted)
> - eb->current_conflict = full_path;
> -
> - if (victim_path != NULL || tree_conflict != NULL || prop_conflicted)
> - {
> - db->bump_info->skipped = TRUE;
> - remember_skipped_path(eb, full_path);
> -
> - if (eb->notify_func)
> - {
> - svn_wc_notify_t *notify
> - = svn_wc_create_notify(full_path,
> - prop_conflicted
> - ? svn_wc_notify_skip
> - : svn_wc_notify_tree_conflict,
> - pool);
> - notify->kind = svn_node_dir;
> -
> - if (prop_conflicted)
> - notify->prop_state = svn_wc_notify_state_conflicted;
> -
> - (*eb->notify_func)(eb->notify_baton, notify, pool);
> - }
> - return SVN_NO_ERROR;
> - }
> -
> - /* Mark directory as being at target_revision and URL, but incomplete. */
> - tmp_entry.revision = *(eb->target_revision);
> - tmp_entry.url = db->new_URL;
> - /* In some situations, the URL of this directory does not have the same
> - repository root as the anchor of the update; we can't just blindly
> - use the that repository root here, so make sure it is really an
> - ancestor. */
> - if (eb->repos && svn_path_is_ancestor(eb->repos, db->new_URL))
> - {
> - tmp_entry.repos = eb->repos;
> - flags |= SVN_WC__ENTRY_MODIFY_REPOS;
> - }
> - tmp_entry.incomplete = TRUE;
> -
> - return svn_wc__entry_modify(adm_access, NULL /* THIS_DIR */,
> - &tmp_entry, flags,
> - TRUE /* immediate write */,
> - pool);
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -change_dir_prop(void *dir_baton,
> - const char *name,
> - const svn_string_t *value,
> - apr_pool_t *pool)
> -{
> - svn_prop_t *propchange;
> - struct dir_baton *db = dir_baton;
> -
> - if (db->bump_info->skipped)
> - return SVN_NO_ERROR;
> -
> - 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;
> -}
> -
> -
> -
> -/* If any of the svn_prop_t objects in PROPCHANGES represents a change
> - to the SVN_PROP_EXTERNALS property, return that change, else return
> - null. If PROPCHANGES contains more than one such change, return
> - the first. */
> -static const svn_prop_t *
> -externals_prop_changed(apr_array_header_t *propchanges)
> -{
> - int i;
> -
> - for (i = 0; i < propchanges->nelts; i++)
> - {
> - const svn_prop_t *p = &(APR_ARRAY_IDX(propchanges, i, svn_prop_t));
> - if (strcmp(p->name, SVN_PROP_EXTERNALS) == 0)
> - return p;
> - }
> -
> - return NULL;
> -}
> -
> -/* This implements the svn_iter_apr_hash_cb_t callback interface.
> - *
> - * Add a property named KEY ('const char *') to a list of properties
> - * to be deleted. BATON is the list: an 'apr_array_header_t *'
> - * representing propchanges (the same type as found in struct dir_baton
> - * and struct file_baton).
> - *
> - * Ignore KLEN, VAL, and POOL.
> - */
> -static svn_error_t *
> -add_prop_deletion(void *baton, const void *key,
> - apr_ssize_t klen, void *val,
> - apr_pool_t *pool)
> -{
> - apr_array_header_t *propchanges = baton;
> - const char *name = key;
> - svn_prop_t *prop = apr_array_push(propchanges);
> -
> - /* Add the deletion of NAME to PROPCHANGES. */
> - prop->name = name;
> - prop->value = NULL;
> -
> - return SVN_NO_ERROR;
> -}
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -close_directory(void *dir_baton,
> - apr_pool_t *pool)
> -{
> - struct dir_baton *db = dir_baton;
> - svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
> - apr_array_header_t *entry_props, *wc_props, *regular_props;
> - apr_hash_t *base_props = NULL, *working_props = NULL;
> - svn_wc_adm_access_t *adm_access;
> -
> - /* Skip if we're in a conflicted tree. Remove the tree-conflict flag if
> - we're closing the victim directory. */
> - if (db->edit_baton->current_conflict)
> - {
> - /* Allow the parent to complete its update. */
> - SVN_ERR(maybe_bump_dir_info(db->edit_baton, db->bump_info, db->pool));
> -
> - if (strcmp(db->edit_baton->current_conflict, db->path) == 0)
> - db->edit_baton->current_conflict = NULL;
> -
> - return SVN_NO_ERROR;
> - }
> -
> - SVN_ERR(svn_categorize_props(db->propchanges, &entry_props, &wc_props,
> - ®ular_props, pool));
> -
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, db->edit_baton->adm_access,
> - db->path, db->pool));
> -
> - /* An incomplete directory might have props which were supposed to be
> - deleted but weren't. Because the server sent us all the props we're
> - supposed to have, any previous base props not in this list must be
> - deleted (issue #1672). */
> - if (db->was_incomplete)
> - {
> - int i;
> - apr_hash_t *props_to_delete;
> -
> - SVN_ERR(svn_wc__load_props(&base_props, &working_props, NULL,
> - adm_access, db->path, pool));
> -
> - /* Calculate which base props weren't also in the incoming
> - propchanges. */
> - props_to_delete = apr_hash_copy(pool, base_props);
> - for (i = 0; i < regular_props->nelts; i++)
> - {
> - const svn_prop_t *prop;
> - prop = &APR_ARRAY_IDX(regular_props, i, svn_prop_t);
> - apr_hash_set(props_to_delete, prop->name,
> - APR_HASH_KEY_STRING, NULL);
> - }
> -
> - /* Add these props to the incoming propchanges. */
> - SVN_ERR(svn_iter_apr_hash(NULL, props_to_delete, add_prop_deletion,
> - regular_props, pool));
> - }
> -
> - /* If this directory has property changes stored up, now is the time
> - to deal with them. */
> - if (regular_props->nelts || entry_props->nelts || wc_props->nelts)
> - {
> - /* Make a temporary log accumulator for dirprop changes.*/
> - svn_stringbuf_t *dirprop_log = svn_stringbuf_create("", pool);
> -
> - if (regular_props->nelts)
> - {
> - /* If recording traversal info, then see if the
> - SVN_PROP_EXTERNALS property on this directory changed,
> - and record before and after for the change. */
> - if (db->edit_baton->traversal_info)
> - {
> - svn_wc_traversal_info_t *ti = db->edit_baton->traversal_info;
> - const svn_prop_t *change = externals_prop_changed(regular_props);
> -
> - if (change)
> - {
> - const svn_string_t *new_val_s = change->value;
> - const svn_string_t *old_val_s;
> -
> - SVN_ERR(svn_wc_prop_get
> - (&old_val_s, SVN_PROP_EXTERNALS,
> - db->path, adm_access, db->pool));
> -
> - if ((new_val_s == NULL) && (old_val_s == NULL))
> - ; /* No value before, no value after... so do nothing. */
> - else if (new_val_s && old_val_s
> - && (svn_string_compare(old_val_s, new_val_s)))
> - ; /* Value did not change... so do nothing. */
> - else if (old_val_s || new_val_s)
> - /* something changed, record the change */
> - {
> - const char *d_path = apr_pstrdup(ti->pool, db->path);
> -
> - apr_hash_set(ti->depths, d_path, APR_HASH_KEY_STRING,
> - svn_depth_to_word(db->ambient_depth));
> -
> - /* We can't assume that ti came pre-loaded with
> - the old values of the svn:externals property.
> - Yes, most callers will have already
> - initialized ti by sending it through
> - svn_wc_crawl_revisions, but we shouldn't
> - count on that here -- so we set both the old
> - and new values again. */
> - if (old_val_s)
> - apr_hash_set(ti->externals_old, d_path,
> - APR_HASH_KEY_STRING,
> - apr_pstrmemdup(ti->pool, old_val_s->data,
> - old_val_s->len));
> - if (new_val_s)
> - apr_hash_set(ti->externals_new, d_path,
> - APR_HASH_KEY_STRING,
> - apr_pstrmemdup(ti->pool, new_val_s->data,
> - new_val_s->len));
> - }
> - }
> - }
> -
> - /* Merge pending properties into temporary files (ignoring
> - conflicts). */
> - SVN_ERR_W(svn_wc__merge_props(&prop_state,
> - adm_access, db->path,
> - NULL /* use baseprops */,
> - base_props, working_props,
> - regular_props, TRUE, FALSE,
> - db->edit_baton->conflict_func,
> - db->edit_baton->conflict_baton,
> - db->pool, &dirprop_log),
> - _("Couldn't do property merge"));
> - }
> -
> - SVN_ERR(accumulate_entry_props(dirprop_log, NULL,
> - adm_access, db->path,
> - entry_props, pool));
> -
> - SVN_ERR(accumulate_wcprops(dirprop_log, adm_access,
> - db->path, wc_props, pool));
> -
> - /* Add the dirprop loggy entries to the baton's log
> - accumulator. */
> - svn_stringbuf_appendstr(db->log_accum, dirprop_log);
> - }
> -
> - /* Flush and run the log. */
> - SVN_ERR(flush_log(db, pool));
> - SVN_ERR(svn_wc__run_log(adm_access, db->edit_baton->diff3_cmd, db->pool));
> - db->log_number = 0;
> -
> - /* We're done with this directory, so remove one reference from the
> - bump information. This may trigger a number of actions. See
> - maybe_bump_dir_info() for more information. */
> - SVN_ERR(maybe_bump_dir_info(db->edit_baton, db->bump_info, db->pool));
> -
> - /* Notify of any prop changes on this directory -- but do nothing if
> - it's an added or skipped directory, because notification has already
> - happened in that case - unless the add was obstructed by a dir
> - scheduled for addition without history, in which case we handle
> - notification here). */
> - if (! db->bump_info->skipped && (db->add_existed || (! db->added))
> - && (db->edit_baton->notify_func))
> - {
> - svn_wc_notify_t *notify
> - = svn_wc_create_notify(db->path,
> - (db->existed || db->add_existed
> - ? svn_wc_notify_exists
> - : svn_wc_notify_update_update),
> - pool);
> - notify->kind = svn_node_dir;
> - notify->prop_state = prop_state;
> - (*db->edit_baton->notify_func)(db->edit_baton->notify_baton,
> - notify, pool);
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Common code for 'absent_file' and 'absent_directory'. */
> -static svn_error_t *
> -absent_file_or_dir(const char *path,
> - svn_node_kind_t kind,
> - void *parent_baton,
> - apr_pool_t *pool)
> -{
> - const char *name = svn_path_basename(path, pool);
> - struct dir_baton *pb = parent_baton;
> - struct edit_baton *eb = pb->edit_baton;
> - svn_wc_adm_access_t *adm_access;
> - apr_hash_t *entries;
> - const svn_wc_entry_t *ent;
> - svn_wc_entry_t tmp_entry;
> -
> - /* Extra check: an item by this name may not exist, but there may
> - still be one scheduled for addition. That's a genuine
> - tree-conflict. */
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access, pb->path, pool));
> - SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
> - ent = apr_hash_get(entries, name, APR_HASH_KEY_STRING);
> - if (ent && (ent->schedule == svn_wc_schedule_add))
> - return svn_error_createf
> - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
> - _("Failed to mark '%s' absent: item of the same name is already "
> - "scheduled for addition"),
> - svn_path_local_style(path, pool));
> -
> - /* Immediately create an entry for the new item in the parent. Note
> - that the parent must already be either added or opened, and thus
> - it's in an 'incomplete' state just like the new item. */
> - tmp_entry.kind = kind;
> -
> - /* Note that there may already exist a 'ghost' entry in the parent
> - with the same name, in a 'deleted' state. If so, it's fine to
> - overwrite it... but we need to make sure we get rid of the
> - 'deleted' flag when doing so: */
> - tmp_entry.deleted = FALSE;
> -
> - /* Post-update processing knows to leave this entry if its revision
> - is equal to the target revision of the overall update. */
> - tmp_entry.revision = *(eb->target_revision);
> -
> - /* And, of course, marking as absent is the whole point. */
> - tmp_entry.absent = TRUE;
> -
> - return svn_wc__entry_modify(adm_access, name, &tmp_entry,
> - (SVN_WC__ENTRY_MODIFY_KIND |
> - SVN_WC__ENTRY_MODIFY_REVISION |
> - SVN_WC__ENTRY_MODIFY_DELETED |
> - SVN_WC__ENTRY_MODIFY_ABSENT),
> - TRUE /* immediate write */, pool);
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -absent_file(const char *path,
> - void *parent_baton,
> - apr_pool_t *pool)
> -{
> - return absent_file_or_dir(path, svn_node_file, parent_baton, pool);
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -absent_directory(const char *path,
> - void *parent_baton,
> - apr_pool_t *pool)
> -{
> - return absent_file_or_dir(path, svn_node_dir, parent_baton, pool);
> -}
> -
> -
> -/* Beginning at DEST_DIR (and its associated entry DEST_ENTRY) within
> - a working copy, search the working copy for an pre-existing
> - versioned file which is exactly equal to COPYFROM_PATH_at_COPYFROM_REV.
> -
> - If the file isn't found, set *RETURN_PATH to NULL.
> -
> - If the file is found, return the absolute path to it in
> - *RETURN_PATH, its entry in *RETURN_ENTRY, and a (read-only)
> - access_t for its parent in *RETURN_ACCESS.
> -*/
> -static svn_error_t *
> -locate_copyfrom(const char *copyfrom_path,
> - svn_revnum_t copyfrom_rev,
> - const char *dest_dir,
> - const svn_wc_entry_t *dest_entry,
> - const char **return_path,
> - const svn_wc_entry_t **return_entry,
> - svn_wc_adm_access_t **return_access,
> - apr_pool_t *pool)
> -{
> - const char *dest_fs_path, *ancestor_fs_path, *ancestor_url, *file_url;
> - const char *copyfrom_parent, *copyfrom_file;
> - const char *abs_dest_dir, *extra_components;
> - const svn_wc_entry_t *ancestor_entry, *file_entry;
> - svn_wc_adm_access_t *ancestor_access;
> - apr_size_t levels_up;
> - svn_stringbuf_t *cwd, *cwd_parent;
> - svn_node_kind_t kind;
> - svn_error_t *err;
> - apr_pool_t *subpool = svn_pool_create(pool);
> -
> - /* Be pessimistic. This function is basically a series of tests
> - that gives dozens of ways to fail our search, returning
> - SVN_NO_ERROR in each case. If we make it all the way to the
> - bottom, we have a real discovery to return. */
> - *return_path = NULL;
> -
> - if ((! dest_entry->repos) || (! dest_entry->url))
> - return svn_error_create(SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND, NULL,
> - _("Destination directory of add-with-history "
> - "is missing a URL"));
> -
> - svn_path_split(copyfrom_path, ©from_parent, ©from_file, pool);
> - SVN_ERR(svn_path_get_absolute(&abs_dest_dir, dest_dir, pool));
> -
> - /* Subtract the dest_dir's URL from the repository "root" URL to get
> - the absolute FS path represented by dest_dir. */
> - dest_fs_path = svn_path_is_child(dest_entry->repos, dest_entry->url, pool);
> - if (! dest_fs_path)
> - {
> - if (strcmp(dest_entry->repos, dest_entry->url) == 0)
> - dest_fs_path = ""; /* the urls are identical; that's ok. */
> - else
> - return svn_error_create(SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND, NULL,
> - _("Destination URLs are broken"));
> - }
> - dest_fs_path = apr_pstrcat(pool, "/", dest_fs_path, NULL);
> - dest_fs_path = svn_path_canonicalize(dest_fs_path, pool);
> -
> - /* Find nearest FS ancestor dir of current FS path and copyfrom_parent */
> - ancestor_fs_path = svn_path_get_longest_ancestor(dest_fs_path,
> - copyfrom_parent, pool);
> - if (strlen(ancestor_fs_path) == 0)
> - return SVN_NO_ERROR;
> -
> - /* Move 'up' the working copy to what ought to be the common ancestor dir. */
> - levels_up = svn_path_component_count(dest_fs_path)
> - - svn_path_component_count(ancestor_fs_path);
> - cwd = svn_stringbuf_create(dest_dir, pool);
> - svn_path_remove_components(cwd, levels_up);
> -
> - /* Open up this hypothetical common ancestor directory. */
> - SVN_ERR(svn_io_check_path(cwd->data, &kind, subpool));
> - if (kind != svn_node_dir)
> - return SVN_NO_ERROR;
> - err = svn_wc_adm_open3(&ancestor_access, NULL, cwd->data,
> - FALSE, /* open read-only, please */
> - 0, /* open only this directory */
> - NULL, NULL, subpool);
> - if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
> - {
> - /* The common ancestor directory isn't version-controlled. */
> - svn_error_clear(err);
> - return SVN_NO_ERROR;
> - }
> - else if (err)
> - return err;
> -
> - SVN_ERR(svn_wc_entry(&ancestor_entry, cwd->data, ancestor_access,
> - FALSE, subpool));
> -
> - /* If we got this far, we know that the ancestor dir exists, and
> - that it's a working copy too. But is it from the same
> - repository? And does it represent the URL we expect it to? */
> - if (dest_entry->uuid && ancestor_entry->uuid
> - && (strcmp(dest_entry->uuid, ancestor_entry->uuid) != 0))
> - return SVN_NO_ERROR;
> -
> - ancestor_url = apr_pstrcat(subpool,
> - dest_entry->repos, ancestor_fs_path, NULL);
> - if (strcmp(ancestor_url, ancestor_entry->url) != 0)
> - return SVN_NO_ERROR;
> -
> - svn_pool_clear(subpool); /* clean up adm_access junk. */
> -
> - /* Add the remaining components to cwd, then 'drill down' to where
> - we hope the copyfrom_path file exists. */
> - extra_components = svn_path_is_child(ancestor_fs_path,
> - copyfrom_path, pool);
> - svn_path_add_component(cwd, extra_components);
> - cwd_parent = svn_stringbuf_create(cwd->data, pool);
> - svn_path_remove_component(cwd_parent);
> -
> - /* First: does the proposed file path even exist? */
> - SVN_ERR(svn_io_check_path(cwd->data, &kind, subpool));
> - if (kind != svn_node_file)
> - return SVN_NO_ERROR;
> -
> - /* Next: is the file's parent-dir under version control? */
> - err = svn_wc_adm_open3(&ancestor_access, NULL, cwd_parent->data,
> - FALSE, /* open read-only, please */
> - 0, /* open only the parent dir */
> - NULL, NULL, pool);
> - if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
> - {
> - svn_error_clear(err);
> -
> - /* There's an unversioned directory (and file) in the exact
> - correct place in the working copy. Chances are high that
> - this file (or some parent) was deleted by 'svn update' --
> - perhaps as part of a move operation -- and this file was left
> - behind becouse it had local edits. If that's true, we may
> - want this thing copied over to the new place.
> -
> - Unfortunately, we have no way of knowing if this file is the
> - one we're looking for. Guessing incorrectly can be really
> - hazardous, breaking the entire update.: we might find out
> - when the server fails to apply a subsequent txdelta against
> - it. Or, if the server doesn't try to do that now, what if a
> - future update fails to apply? For now, the only safe thing
> - to do is return no results. :-/
> - */
> - return SVN_NO_ERROR;
> - }
> - else if (err)
> - return err;
> -
> - /* The candidate file is under version control; but is it
> - really the file we're looking for? <wave hand in circle> */
> - SVN_ERR(svn_wc_entry(&file_entry, cwd->data, ancestor_access,
> - FALSE, pool));
> - if (! file_entry)
> - /* Parent dir is versioned, but file is not. Be safe and
> - return no results (see large discourse above.) */
> - return SVN_NO_ERROR;
> -
> - /* Is the repos UUID and file's URL what we expect it to be? */
> - if (file_entry->uuid && dest_entry->uuid
> - && (strcmp(file_entry->uuid, dest_entry->uuid) != 0))
> - return SVN_NO_ERROR;
> -
> - file_url = apr_pstrcat(subpool, file_entry->repos, copyfrom_path, NULL);
> - if (strcmp(file_url, file_entry->url) != 0)
> - return SVN_NO_ERROR;
> -
> - /* Do we actually have valid revisions for the file? (See Issue
> - #2977.) */
> - if (! (SVN_IS_VALID_REVNUM(file_entry->cmt_rev)
> - && SVN_IS_VALID_REVNUM(file_entry->revision)))
> - return SVN_NO_ERROR;
> -
> - /* Do we have the the right *version* of the file? */
> - if (! ((file_entry->cmt_rev <= copyfrom_rev)
> - && (copyfrom_rev <= file_entry->revision)))
> - return SVN_NO_ERROR;
> -
> - /* Success! We found the exact file we wanted! */
> - *return_path = apr_pstrdup(pool, cwd->data);
> - *return_entry = file_entry;
> - *return_access = ancestor_access;
> -
> - svn_pool_clear(subpool);
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Given a set of properties PROPS_IN, find all regular properties
> - and shallowly copy them into a new set (allocate the new set in
> - POOL, but the set's members retain their original allocations). */
> -static apr_hash_t *
> -copy_regular_props(apr_hash_t *props_in,
> - apr_pool_t *pool)
> -{
> - apr_hash_t *props_out = apr_hash_make(pool);
> - apr_hash_index_t *hi;
> -
> - for (hi = apr_hash_first(pool, props_in); hi; hi = apr_hash_next(hi))
> - {
> - const void *key;
> - void *val;
> - const char *propname;
> - svn_string_t *propval;
> - apr_hash_this(hi, &key, NULL, &val);
> - propname = key;
> - propval = val;
> -
> - if (svn_property_kind(NULL, propname) == svn_prop_regular_kind)
> - apr_hash_set(props_out, propname, APR_HASH_KEY_STRING, propval);
> - }
> - return props_out;
> -}
> -
> -
> -/* Do the "with history" part of add_file().
> -
> - Attempt to locate COPYFROM_PATH_at_COPYFROM_REV within the existing
> - working copy. If found, copy it to PATH, and install it as a
> - normal versioned file. (Local edits are copied as well.) If not
> - found, then resort to fetching the file in a special RA request.
> -
> - After the file is fully installed, call the editor's open_file() on
> - it, so that any subsequent apply_textdelta() commands coming from
> - the server can further alter the file.
> -*/
> -static svn_error_t *
> -add_file_with_history(const char *path,
> - struct dir_baton *pb,
> - const char *copyfrom_path,
> - svn_revnum_t copyfrom_rev,
> - struct file_baton *tfb,
> - apr_pool_t *pool)
> -{
> - struct edit_baton *eb = pb->edit_baton;
> - svn_wc_adm_access_t *adm_access, *src_access;
> - const char *src_path;
> - const svn_wc_entry_t *src_entry;
> - apr_hash_t *base_props, *working_props;
> - const svn_wc_entry_t *path_entry;
> - svn_error_t *err;
> - svn_stream_t *copied_stream;
> - const char *temp_dir_path;
> -
> - /* The file_pool can stick around for a *long* time, so we want to
> - use a subpool for any temporary allocations. */
> - apr_pool_t *subpool = svn_pool_create(pool);
> -
> - tfb->added_with_history = TRUE;
> -
> - /* Attempt to locate the copyfrom_path in the working copy first. */
> - SVN_ERR(svn_wc_entry(&path_entry, pb->path, eb->adm_access, FALSE, subpool));
> - err = locate_copyfrom(copyfrom_path, copyfrom_rev,
> - pb->path, path_entry,
> - &src_path, &src_entry, &src_access, subpool);
> - if (err && err->apr_err == SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND)
> - svn_error_clear(err);
> - else if (err)
> - return err;
> -
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, pb->edit_baton->adm_access,
> - pb->path, subpool));
> -
> - temp_dir_path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access),
> - SVN_WC__ADM_TMP, pool);
> - SVN_ERR(svn_stream_open_unique(&copied_stream,
> - &tfb->copied_text_base,
> - temp_dir_path,
> - svn_io_file_del_none,
> - pool, pool));
> -
> - /* Compute a checksum for the stream as we write stuff into it.
> - ### this is temporary. in many cases, we already *know* the checksum
> - ### since it is a copy. */
> - copied_stream = svn_stream_checksummed2(copied_stream,
> - NULL, 0,
> - &tfb->copied_base_checksum,
> - svn_checksum_md5,
> - FALSE, pool);
> -
> - if (src_path != NULL) /* Found a file to copy */
> - {
> - /* Copy the existing file's text-base over to the (temporary)
> - new text-base, where the file baton expects it to be. Get
> - the text base and props from the usual place or from the
> - revert place, depending on scheduling. */
> -
> - svn_stream_t *source_text_base;
> -
> - if (src_entry->schedule == svn_wc_schedule_replace
> - && src_entry->copyfrom_url)
> - {
> - SVN_ERR(svn_wc__get_revert_contents(&source_text_base, src_path,
> - subpool, subpool));
> - SVN_ERR(svn_wc__load_props(NULL, NULL, &base_props,
> - src_access, src_path, pool));
> - /* The old working props are lost, just like the old
> - working file text is. Just use the base props. */
> - working_props = base_props;
> - }
> - else
> - {
> - SVN_ERR(svn_wc_get_pristine_contents(&source_text_base, src_path,
> - subpool, subpool));
> - SVN_ERR(svn_wc__load_props(&base_props, &working_props, NULL,
> - src_access, src_path, pool));
> - }
> -
> - SVN_ERR(svn_stream_copy3(source_text_base, copied_stream,
> - eb->cancel_func, eb->cancel_baton, pool));
> - }
> - else /* Couldn't find a file to copy */
> - {
> - /* Fall back to fetching it from the repository instead. */
> -
> - if (! eb->fetch_func)
> - return svn_error_create(SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
> - _("No fetch_func supplied to update_editor"));
> -
> - /* Fetch the repository file's text-base and base-props;
> - svn_stream_close() automatically closes the text-base file for us. */
> -
> - /* copyfrom_path is a absolute path, fetch_func requires a path relative
> - to the root of the repository so skip the first '/'. */
> - SVN_ERR(eb->fetch_func(eb->fetch_baton, copyfrom_path + 1, copyfrom_rev,
> - copied_stream,
> - NULL, &base_props, pool));
> - SVN_ERR(svn_stream_close(copied_stream));
> - working_props = base_props;
> - }
> -
> - /* Loop over whatever props we have in memory, and add all
> - regular props to hashes in the baton. Skip entry and wc
> - properties, these are only valid for the original file. */
> - tfb->copied_base_props = copy_regular_props(base_props, pool);
> - tfb->copied_working_props = copy_regular_props(working_props, pool);
> -
> - if (src_path != NULL)
> - {
> - /* If we copied an existing file over, we need to copy its
> - working text too, to preserve any local mods. (We already
> - read its working *props* into tfb->copied_working_props.) */
> - svn_boolean_t text_changed;
> -
> - SVN_ERR(svn_wc_text_modified_p(&text_changed, src_path, FALSE,
> - src_access, subpool));
> -
> - if (text_changed)
> - {
> - /* Make a unique file name for the copied_working_text. */
> - SVN_ERR(svn_wc_create_tmp_file2(NULL, &tfb->copied_working_text,
> - svn_wc_adm_access_path(adm_access),
> - svn_io_file_del_none,
> - pool));
> -
> - SVN_ERR(svn_io_copy_file(src_path, tfb->copied_working_text, TRUE,
> - subpool));
> - }
> - }
> -
> - svn_pool_destroy(subpool);
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -add_file(const char *path,
> - void *parent_baton,
> - const char *copyfrom_path,
> - svn_revnum_t copyfrom_rev,
> - apr_pool_t *pool,
> - void **file_baton)
> -{
> - struct dir_baton *pb = parent_baton;
> - struct edit_baton *eb = pb->edit_baton;
> - struct file_baton *fb;
> - const svn_wc_entry_t *entry;
> - svn_node_kind_t kind;
> - svn_wc_adm_access_t *adm_access;
> - apr_pool_t *subpool;
> - const char *full_path = svn_path_join(eb->anchor, path, pool);
> - char *victim_path;
> - svn_wc_conflict_description_t *tree_conflict;
> -
> - if (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev))
> - {
> - /* Sanity checks */
> - if (! (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev)))
> - return svn_error_create(SVN_ERR_WC_INVALID_OP_ON_CWD, NULL,
> - _("Bad copyfrom arguments received"));
> - }
> -
> - SVN_ERR(make_file_baton(&fb, pb, path, TRUE, pool));
> - *file_baton = fb;
> -
> - /* Is an ancestor-dir (already visited by this edit) a tree conflict
> - victim? If so, skip without notification. */
> - if (eb->current_conflict)
> - {
> - SVN_ERR_ASSERT(svn_path_is_ancestor(eb->current_conflict,
> - full_path));
> -
> - fb->skipped = TRUE;
> - remember_skipped_path(eb, full_path);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - /* The file_pool can stick around for a *long* time, so we want to
> - use a subpool for any temporary allocations. */
> - subpool = svn_pool_create(pool);
> -
> - SVN_ERR(check_path_under_root(fb->dir_baton->path, fb->name, subpool));
> -
> - SVN_ERR(svn_io_check_path(fb->path, &kind, subpool));
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access,
> - pb->path, subpool));
> - SVN_ERR(svn_wc_entry(&entry, fb->path, adm_access, FALSE, subpool));
> -
> - /* Is this path, or an ancestor-dir NOT visited by this edit, already
> - marked as a tree conflict victim? */
> - SVN_ERR(already_in_a_tree_conflict(&victim_path, full_path, eb->cancel_func,
> - eb->cancel_baton, pool));
> -
> - /* Is this path the victim of a newly-discovered tree conflict? */
> - tree_conflict = NULL;
> - if (victim_path == NULL)
> - SVN_ERR(check_tree_conflict(&tree_conflict, eb, pb->log_accum, full_path,
> - entry, adm_access,
> - svn_wc_conflict_action_add,
> - svn_node_file, fb->new_URL, subpool));
> -
> - if (victim_path != NULL || tree_conflict != NULL)
> - {
> - fb->skipped = TRUE;
> - remember_skipped_path(eb, full_path);
> -
> - /* ### TODO: Also print victim_path in the skip msg. */
> - if (eb->notify_func)
> - (*eb->notify_func)(eb->notify_baton,
> - svn_wc_create_notify(full_path,
> - (tree_conflict != NULL)
> - ? svn_wc_notify_tree_conflict
> - : svn_wc_notify_skip,
> - subpool),
> - subpool);
> -
> - svn_pool_destroy(subpool);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - /* Sanity checks. */
> -
> - /* When adding, there should be nothing with this name unless unversioned
> - obstructions are permitted or the obstruction is scheduled for addition
> - (or replacement) without history. */
> - /* ### " or the obstruction is scheduled for addition
> - without history." ??? */
> - if (kind != svn_node_none)
> - {
> - if (eb->allow_unver_obstructions
> - || (entry && ((entry->schedule == svn_wc_schedule_add
> - || entry->schedule == svn_wc_schedule_replace))))
> - {
> - /* ### now detected as a tree conflict instead.
> - if (entry && entry->copied)
> - {
> - return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE,
> - NULL,
> - _("Failed to add file '%s': a "
> - "file of the same name is "
> - "already scheduled for addition "
> - "with history"),
> - svn_path_local_style(fb->path,
> - pool));
> - }
> - */
> -
> - /* The name can exist, but it better *really* be a file. */
> - if (kind != svn_node_file)
> - return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE,
> - NULL,
> - _("Failed to add file '%s': "
> - "a non-file object of the same "
> - "name already exists"),
> - svn_path_local_style(fb->path,
> - pool));
> -
> - if (entry)
> - fb->add_existed = TRUE; /* Flag as addition without history. */
> - else
> - fb->existed = TRUE; /* Flag as unversioned obstruction. */
> - }
> - else
> - {
> - return svn_error_createf
> - (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
> - _("Failed to add file '%s': object of the same name "
> - "already exists"), svn_path_local_style(fb->path, pool));
> - }
> - }
> - /* sussman sez: If we're trying to add a file that's already in
> - `entries' (but not on disk), that's okay. It's probably because
> - the user deleted the working version and ran 'svn up' as a means
> - of getting the file back.
> -
> - It certainly doesn't hurt to re-add the file. We can't possibly
> - get the entry showing up twice in `entries', since it's a hash;
> - and we know that we won't lose any local mods. Let the existing
> - entry be overwritten. */
> -
> - svn_pool_destroy(subpool);
> -
> - /* Now, if this is an add with history, do the history part. */
> - if (copyfrom_path && !fb->skipped)
> - {
> - SVN_ERR(add_file_with_history(path, pb, copyfrom_path, copyfrom_rev,
> - fb, pool));
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -open_file(const char *path,
> - void *parent_baton,
> - svn_revnum_t base_revision,
> - apr_pool_t *pool,
> - void **file_baton)
> -{
> - struct dir_baton *pb = parent_baton;
> - struct edit_baton *eb = pb->edit_baton;
> - struct file_baton *fb;
> - const svn_wc_entry_t *entry;
> - svn_node_kind_t kind;
> - svn_wc_adm_access_t *adm_access;
> - svn_boolean_t text_conflicted;
> - svn_boolean_t prop_conflicted;
> - const char *full_path = svn_path_join(eb->anchor, path, pool);
> - char *victim_path;
> - svn_wc_conflict_description_t *tree_conflict;
> -
> - /* the file_pool can stick around for a *long* time, so we want to use
> - a subpool for any temporary allocations. */
> - apr_pool_t *subpool = svn_pool_create(pool);
> -
> - SVN_ERR(make_file_baton(&fb, pb, path, FALSE, pool));
> - *file_baton = fb;
> -
> - SVN_ERR(check_path_under_root(fb->dir_baton->path, fb->name, subpool));
> -
> -
> - SVN_ERR(svn_io_check_path(fb->path, &kind, subpool));
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access,
> - pb->path, subpool));
> - SVN_ERR(svn_wc_entry(&entry, fb->path, adm_access, FALSE, subpool));
> -
> - /* Sanity check. */
> -
> - /* If replacing, make sure the .svn entry already exists. */
> - if (! entry)
> - return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
> - _("File '%s' in directory '%s' "
> - "is not a versioned resource"),
> - fb->name,
> - svn_path_local_style(pb->path, pool));
> -
> - /* Is an ancestor-dir (already visited by this edit) a tree conflict
> - victim? If so, skip without notification. */
> - if (eb->current_conflict)
> - {
> - SVN_ERR_ASSERT(svn_path_is_ancestor(eb->current_conflict,
> - full_path));
> -
> - fb->skipped = TRUE;
> - remember_skipped_path(eb, full_path);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - /* Is this path, or an ancestor-dir NOT visited by this edit, already
> - marked as a tree conflict victim? */
> - SVN_ERR(already_in_a_tree_conflict(&victim_path, full_path, eb->cancel_func,
> - eb->cancel_baton, pool));
> -
> -
> - /* Is this path the victim of a newly-discovered tree conflict? */
> - tree_conflict = NULL;
> - if (victim_path == NULL)
> - SVN_ERR(check_tree_conflict(&tree_conflict, eb, pb->log_accum, full_path,
> - entry, adm_access,
> - svn_wc_conflict_action_edit,
> - svn_node_file, fb->new_URL, pool));
> -
> - /* Does the file already have text or property conflicts? */
> - SVN_ERR(svn_wc_conflicted_p2(&text_conflicted, &prop_conflicted, NULL,
> - full_path, adm_access, pool));
> -
> - if (victim_path != NULL || tree_conflict != NULL || text_conflicted
> - || prop_conflicted)
> - {
> - fb->skipped = TRUE;
> - remember_skipped_path(eb, full_path);
> -
> - /* ### TODO: Also print victim_path in the t-c skip msg. */
> - if (eb->notify_func)
> - (*eb->notify_func)(eb->notify_baton,
> - svn_wc_create_notify(full_path,
> - (tree_conflict != NULL)
> - ? svn_wc_notify_tree_conflict
> - : svn_wc_notify_skip,
> - pool),
> - pool);
> -
> - return SVN_NO_ERROR;
> - }
> -
> - svn_pool_destroy(subpool);
> -
> - return SVN_NO_ERROR;
> -}
> -
> -/* For the given PATH, fill out OLD_TEXT_BASE with the permanent text-base
> - path, or (if the entry is replaced with history) to the permanent
> - revert-base path.
> -
> - If REPLACED_P is non-NULL, set *REPLACED_P to whether or not the
> - entry is replaced (which also implies whether or not it needs to
> - use the revert base). If CHECKSUM_P is non-NULL and the path
> - already has an entry, set *CHECKSUM_P to the entry's checksum.
> -
> - ROOT_ACCESS is an access baton which can be used to find associated
> - batons for the directory that PATH resides within.
> -
> - Use SCRATCH_POOL for temporary allocation and for *CHECKSUM_P (if
> - applicable), but allocate OLD_TEXT_BASE in RESULT_POOL. */
> -static svn_error_t *
> -choose_base_paths(const char **old_text_base,
> - const char **checksum_p,
> - svn_boolean_t *replaced_p,
> - svn_wc_adm_access_t *root_access,
> - const char *path,
> - apr_pool_t *result_pool,
> - apr_pool_t *scratch_pool)
> -{
> - svn_wc_adm_access_t *adm_access;
> - const svn_wc_entry_t *entry;
> - svn_boolean_t replaced;
> -
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, root_access,
> - svn_path_dirname(path, scratch_pool),
> - scratch_pool));
> - SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, scratch_pool));
> -
> - replaced = entry && entry->schedule == svn_wc_schedule_replace;
> - if (replaced)
> - *old_text_base = svn_wc__text_revert_path(path, FALSE, result_pool);
> - else
> - *old_text_base = svn_wc__text_base_path(path, FALSE, result_pool);
> -
> - if (checksum_p)
> - {
> - *checksum_p = NULL;
> - if (entry)
> - *checksum_p = entry->checksum;
> - }
> - if (replaced_p)
> - *replaced_p = replaced;
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t 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 *fb = file_baton;
> - apr_pool_t *handler_pool = svn_pool_create(fb->pool);
> - struct handler_baton *hb = apr_palloc(handler_pool, sizeof(*hb));
> - svn_error_t *err;
> - const char *checksum;
> - svn_boolean_t replaced;
> - svn_stream_t *source;
> - svn_stream_t *target;
> -
> - if (fb->skipped)
> - {
> - *handler = svn_delta_noop_window_handler;
> - *handler_baton = NULL;
> - return SVN_NO_ERROR;
> - }
> -
> - fb->received_textdelta = TRUE;
> -
> - /* Before applying incoming svndiff data to text base, make sure
> - text base hasn't been corrupted, and that its checksum
> - matches the expected base checksum. */
> -
> - SVN_ERR(choose_base_paths(&fb->text_base_path,
> - &checksum, &replaced,
> - fb->edit_baton->adm_access, fb->path,
> - fb->pool, pool));
> -
> - /* Only compare checksums if this file has an entry, and the entry has
> - a checksum. If there's no entry, it just means the file is
> - created in this update, so there won't be any previously recorded
> - checksum to compare against. If no checksum, well, for backwards
> - compatibility we assume that no checksum always matches. */
> - if (checksum)
> - {
> - svn_checksum_t *digest;
> - const char *hex_digest;
> -
> - SVN_ERR(svn_io_file_checksum2(&digest, fb->text_base_path,
> - svn_checksum_md5, pool));
> -
> - /* ### screw hex_digest. use svn_checksum_match() */
> -
> - hex_digest = svn_checksum_to_cstring_display(digest, pool);
> -
> - /* Compare the base_checksum here, rather than in the window
> - handler, because there's no guarantee that the handler will
> - see every byte of the base file. */
> - if (base_checksum)
> - {
> - if (strcmp(hex_digest, base_checksum) != 0)
> - return svn_error_createf
> - (SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
> - _("Checksum mismatch for '%s'; expected: '%s', actual: '%s'"),
> - svn_path_local_style(fb->text_base_path, pool), base_checksum,
> - hex_digest);
> - }
> -
> - if (! replaced && strcmp(hex_digest, checksum) != 0)
> - {
> - return svn_error_createf
> - (SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
> - _("Checksum mismatch for '%s'; recorded: '%s', actual: '%s'"),
> - svn_path_local_style(fb->text_base_path, pool), checksum,
> - hex_digest);
> - }
> - }
> -
> - /* Open the text base for reading, unless this is an added file. */
> -
> - /*
> - kff todo: what we really need to do here is:
> -
> - 1. See if there's a file or dir by this name already here.
> - 2. See if it's under revision control.
> - 3. If both are true, open text-base.
> - 4. If only 1 is true, bail, because we can't go destroying user's
> - files (or as an alternative to bailing, move it to some tmp
> - name and somehow tell the user, but communicating with the
> - user without erroring is a whole callback system we haven't
> - finished inventing yet.)
> - */
> -
> - if (! fb->added)
> - {
> - if (replaced)
> - SVN_ERR(svn_wc__get_revert_contents(&source, fb->path,
> - handler_pool, handler_pool));
> - else
> - SVN_ERR(svn_wc_get_pristine_contents(&source, fb->path,
> - handler_pool, handler_pool));
> - }
> - else
> - {
> - if (fb->copied_text_base)
> - SVN_ERR(svn_stream_open_readonly(&source, fb->copied_text_base,
> - handler_pool, handler_pool));
> - else
> - source = svn_stream_empty(handler_pool);
> - }
> -
> - /* Open the text base for writing (this will get us a temporary file). */
> -
> - {
> - err = svn_wc__open_writable_base(&target, &hb->work_path, fb->path,
> - replaced /* need_revert_base */,
> - handler_pool, pool);
> - if (err)
> - {
> - svn_pool_destroy(handler_pool);
> - return err;
> - }
> - }
> -
> - /* Prepare to apply the delta. */
> - svn_txdelta_apply(source, target,
> - hb->digest, hb->work_path /* error_info */,
> - handler_pool,
> - &hb->apply_handler, &hb->apply_baton);
> -
> - hb->pool = handler_pool;
> - hb->fb = fb;
> -
> - /* We're all set. */
> - *handler_baton = hb;
> - *handler = window_handler;
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -change_file_prop(void *file_baton,
> - const char *name,
> - const svn_string_t *value,
> - apr_pool_t *pool)
> -{
> - struct file_baton *fb = file_baton;
> - struct edit_baton *eb = fb->edit_baton;
> - svn_prop_t *propchange;
> -
> - if (fb->skipped)
> - return SVN_NO_ERROR;
> -
> - /* Push a new propchange to the file baton's array of propchanges */
> - propchange = apr_array_push(fb->propchanges);
> - propchange->name = apr_pstrdup(fb->pool, name);
> - propchange->value = value ? svn_string_dup(value, fb->pool) : NULL;
> -
> - /* Special case: If use-commit-times config variable is set we
> - cache the last-changed-date propval so we can use it to set
> - the working file's timestamp. */
> - if (eb->use_commit_times
> - && (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
> - && value)
> - fb->last_changed_date = apr_pstrdup(fb->pool, value->data);
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* Write log commands to merge PROP_CHANGES into the existing
> - properties of FILE_PATH. PROP_CHANGES can contain regular
> - properties as well as entryprops and wcprops. Update *PROP_STATE
> - to reflect the result of the regular prop merge. Make *LOCK_STATE
> - reflect the possible removal of a lock token from FILE_PATH's
> - entryprops. BASE_PROPS and WORKING_PROPS are hashes of the base and
> - working props of the file; if NULL they are read from the wc.
> -
> - CONFICT_FUNC/BATON is a callback which allows the client to
> - possibly resolve a property conflict interactively.
> -
> - ADM_ACCESS is the access baton for FILE_PATH. Append log commands to
> - LOG_ACCUM. Use POOL for temporary allocations. */
> -static svn_error_t *
> -merge_props(svn_stringbuf_t *log_accum,
> - svn_wc_notify_state_t *prop_state,
> - svn_wc_notify_lock_state_t *lock_state,
> - svn_wc_adm_access_t *adm_access,
> - const char *file_path,
> - const apr_array_header_t *prop_changes,
> - apr_hash_t *base_props,
> - apr_hash_t *working_props,
> - svn_wc_conflict_resolver_func_t conflict_func,
> - void *conflict_baton,
> - apr_pool_t *pool)
> -{
> - apr_array_header_t *regular_props = NULL, *wc_props = NULL,
> - *entry_props = NULL;
> + /* If we add more fields to svn_wc_conflict_result_t, add them here. */
>
> - /* Sort the property list into three arrays, based on kind. */
> - SVN_ERR(svn_categorize_props(prop_changes,
> - &entry_props, &wc_props, ®ular_props,
> - pool));
> -
> - /* Always initialize to unknown state. */
> - *prop_state = svn_wc_notify_state_unknown;
> -
> - /* Merge the 'regular' props into the existing working proplist. */
> - if (regular_props)
> - {
> - /* This will merge the old and new props into a new prop db, and
> - write <cp> commands to the logfile to install the merged
> - props. */
> - SVN_ERR(svn_wc__merge_props(prop_state,
> - adm_access, file_path,
> - NULL /* update, not merge */,
> - base_props,
> - working_props,
> - regular_props, TRUE, FALSE,
> - conflict_func, conflict_baton,
> - pool, &log_accum));
> - }
> -
> - /* If there are any ENTRY PROPS, make sure those get appended to the
> - growing log as fields for the file's entry.
> -
> - Note that no merging needs to happen; these kinds of props aren't
> - versioned, so if the property is present, we overwrite the value. */
> - if (entry_props)
> - SVN_ERR(accumulate_entry_props(log_accum, lock_state,
> - adm_access, file_path,
> - entry_props, pool));
> - else
> - *lock_state = svn_wc_notify_lock_state_unchanged;
> -
> - /* This writes a whole bunch of log commands to install wcprops. */
> - if (wc_props)
> - SVN_ERR(accumulate_wcprops(log_accum, adm_access,
> - file_path, wc_props, pool));
> -
> - return SVN_NO_ERROR;
> -}
> -
> -/* Append, to LOG_ACCUM, log commands to update the entry for NAME in
> - ADM_ACCESS with a NEW_REVISION and a NEW_URL (if non-NULL), making sure
> - the entry refers to a file and has no absent or deleted state.
> - Use POOL for temporary allocations. */
> -static svn_error_t *
> -loggy_tweak_entry(svn_stringbuf_t *log_accum,
> - svn_wc_adm_access_t *adm_access,
> - const char *path,
> - svn_revnum_t new_revision,
> - const char *new_URL,
> - apr_pool_t *pool)
> -{
> - /* Write log entry which will bump the revision number. Also, just
> - in case we're overwriting an existing phantom 'deleted' or
> - 'absent' entry, be sure to remove the hiddenness. */
> - svn_wc_entry_t tmp_entry;
> - apr_uint64_t modify_flags = SVN_WC__ENTRY_MODIFY_KIND
> - | SVN_WC__ENTRY_MODIFY_REVISION
> - | SVN_WC__ENTRY_MODIFY_DELETED
> - | SVN_WC__ENTRY_MODIFY_ABSENT
> - | SVN_WC__ENTRY_MODIFY_TEXT_TIME
> - | SVN_WC__ENTRY_MODIFY_WORKING_SIZE;
> -
> -
> - tmp_entry.revision = new_revision;
> - tmp_entry.kind = svn_node_file;
> - tmp_entry.deleted = FALSE;
> - tmp_entry.absent = FALSE;
> - /* Indicate the file was locally modified and we didn't get to
> - calculate the true value, but we can't set it to UNKNOWN (-1),
> - because that would indicate absense of this value.
> - If it isn't locally modified,
> - we'll overwrite with the actual value later. */
> - tmp_entry.working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
> - /* The same is true for the TEXT_TIME field, except that that doesn't
> - have an explicid 'changed' value, so we set the value to 'undefined'. */
> - tmp_entry.text_time = 0;
> -
> - /* Possibly install a *non*-inherited URL in the entry. */
> - if (new_URL)
> - {
> - tmp_entry.url = new_URL;
> - modify_flags |= SVN_WC__ENTRY_MODIFY_URL;
> - }
> -
> - return svn_wc__loggy_entry_modify(&log_accum, adm_access,
> - path, &tmp_entry, modify_flags,
> - pool);
> -}
> -
> -
> -/* This is the small planet. It has the complex responsibility of
> - * "integrating" a new revision of a file into a working copy.
> - *
> - * Given a file_baton FB for a file either already under version control, or
> - * prepared (see below) to join version control, fully install a
> - * new revision of the file.
> - *
> - * By "install", we mean: create a new text-base and prop-base, merge
> - * any textual and property changes into the working file, and finally
> - * update all metadata so that the working copy believes it has a new
> - * working revision of the file. All of this work includes being
> - * sensitive to eol translation, keyword substitution, and performing
> - * all actions accumulated to FB->DIR_BATON->LOG_ACCUM.
> - *
> - * If there's a new text base, NEW_TEXT_BASE_PATH must be the full
> - * pathname of the new text base, somewhere in the administrative area
> - * of the working file. It will be installed as the new text base for
> - * this file, and removed after a successful run of the generated log
> - * commands.
> - *
> - * Set *CONTENT_STATE, *PROP_STATE and *LOCK_STATE to the state of the
> - * contents, properties and repository lock, respectively, after the
> - * installation. If an error is returned, the value of these three
> - * variables is undefined.
> - *
> - * ACTUAL_CHECKSUM is the checksum that was computed as we constructed
> - * the (new) text base. That was performed during a txdelta apply, or
> - * during a copy of an add-with-history.
> - *
> - * POOL is used for all bookkeeping work during the installation.
> - */
> -static svn_error_t *
> -merge_file(svn_wc_notify_state_t *content_state,
> - svn_wc_notify_state_t *prop_state,
> - svn_wc_notify_lock_state_t *lock_state,
> - struct file_baton *fb,
> - const char *new_text_base_path,
> - const svn_checksum_t *actual_checksum,
> - apr_pool_t *pool)
> -{
> - const char *parent_dir;
> - struct edit_baton *eb = fb->edit_baton;
> - svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool);
> - svn_wc_adm_access_t *adm_access;
> - svn_boolean_t is_locally_modified;
> - svn_boolean_t is_replaced = FALSE;
> - svn_boolean_t magic_props_changed;
> - enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged;
> - const svn_wc_entry_t *entry;
> -
> - /* Accumulated entry modifications. */
> - svn_wc_entry_t tmp_entry;
> - apr_uint64_t flags = 0;
> -
> - /*
> - When this function is called on file F, we assume the following
> - things are true:
> -
> - - The new pristine text of F, if any, is present at
> - NEW_TEXT_BASE_PATH
> -
> - - The .svn/entries file still reflects the old version of F.
> -
> - - fb->old_text_base_path is the old pristine F.
> - (This is only set if there's a new text base).
> -
> - The goal is to update the local working copy of F to reflect
> - the changes received from the repository, preserving any local
> - modifications.
> - */
> -
> - /* Start by splitting the file path, getting an access baton for the parent,
> - and an entry for the file if any. */
> - svn_path_split(fb->path, &parent_dir, NULL, pool);
> - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access,
> - parent_dir, pool));
> -
> - SVN_ERR(svn_wc_entry(&entry, fb->path, adm_access, FALSE, pool));
> - if (! entry && ! fb->added)
> - return svn_error_createf(
> - SVN_ERR_UNVERSIONED_RESOURCE, NULL,
> - _("'%s' is not under version control"),
> - svn_path_local_style(fb->path, pool));
> -
> - /* Determine if any of the propchanges are the "magic" ones that
> - might require changing the working file. */
> - magic_props_changed = svn_wc__has_magic_property(fb->propchanges);
> -
> - /* Install all kinds of properties. It is important to do this before
> - any file content merging, since that process might expand keywords, in
> - which case we want the new entryprops to be in place. */
> - SVN_ERR(merge_props(log_accum, prop_state, lock_state, adm_access,
> - fb->path, fb->propchanges,
> - fb->copied_base_props, fb->copied_working_props,
> - eb->conflict_func, eb->conflict_baton, pool));
> -
> - /* Has the user made local mods to the working file?
> - Note that this compares to the current pristine file, which is
> - different from fb->old_text_base_path if we have a replaced-with-history
> - file. However, in the case we had an obstruction, we check against the
> - new text base. (And if we're doing an add-with-history and we've already
> - saved a copy of a locally-modified file, then there certainly are mods.) */
> - if (fb->copied_working_text)
> - is_locally_modified = TRUE;
> - else if (! fb->existed)
> - SVN_ERR(svn_wc__text_modified_internal_p(&is_locally_modified, fb->path,
> - FALSE, adm_access, FALSE, pool));
> - else if (new_text_base_path)
> - SVN_ERR(svn_wc__versioned_file_modcheck(&is_locally_modified, fb->path,
> - adm_access,
> - new_text_base_path,
> - FALSE, pool));
> - else
> - is_locally_modified = FALSE;
> -
> - if (entry && entry->schedule == svn_wc_schedule_replace)
> - is_replaced = TRUE;
> -
> - if (fb->add_existed)
> - {
> - /* Tweak schedule for the file's entry so it is no longer
> - scheduled for addition. */
> - tmp_entry.schedule = svn_wc_schedule_normal;
> - flags |= (SVN_WC__ENTRY_MODIFY_SCHEDULE |
> - SVN_WC__ENTRY_MODIFY_FORCE);
> - }
> -
> - /* Set the new revision and URL in the entry and clean up some other
> - fields. */
> - SVN_ERR(loggy_tweak_entry(log_accum, adm_access, fb->path,
> - *eb->target_revision, fb->new_URL, pool));
> -
> - /* For 'textual' merging, we implement this matrix.
> -
> - Text file Binary File
> - -----------------------------------------------
> - "Local Mods" && | svn_wc_merge uses diff3, | svn_wc_merge |
> - (!fb->existed || | possibly makes backups & | makes backups, |
> - fb->add_existed) | marks file as conflicted.| marks conflicted |
> - -----------------------------------------------
> - "Local Mods" && | Just leave obstructing file as-is. |
> - fb->existed | |
> - -----------------------------------------------
> - No Mods | Just overwrite working file. |
> - | |
> - -----------------------------------------------
> -
> - So the first thing we do is figure out where we are in the
> - matrix. */
> - if (new_text_base_path)
> - {
> - if (! is_locally_modified && ! is_replaced)
> - {
> - /* If there are no local mods, who cares whether it's a text
> - or binary file! Just write a log command to overwrite
> - any working file with the new text-base. If newline
> - conversion or keyword substitution is activated, this
> - will happen as well during the copy.
> - For replaced files, though, we want to merge in the changes
> - even if the file is not modified compared to the (non-revert)
> - text-base. */
> - SVN_ERR(svn_wc__loggy_copy(&log_accum, adm_access,
> - new_text_base_path,
> - fb->path, pool));
> - }
> - else /* working file or obstruction is locally modified... */
> - {
> - svn_node_kind_t wfile_kind = svn_node_unknown;
> -
> - SVN_ERR(svn_io_check_path(fb->path, &wfile_kind, pool));
> - if (wfile_kind == svn_node_none && ! fb->added_with_history)
> - {
> - /* working file is missing?!
> - Just copy the new text-base to the file. */
> - SVN_ERR(svn_wc__loggy_copy(&log_accum, adm_access,
> - new_text_base_path,
> - fb->path, pool));
> - }
> - else if (! fb->existed)
> - /* Working file exists and has local mods
> - or is scheduled for addition but is not an obstruction. */
> - {
> - /* Now we need to let loose svn_wc__merge_internal() to merge
> - the textual changes into the working file. */
> - const char *oldrev_str, *newrev_str, *mine_str;
> - const char *merge_left;
> - svn_boolean_t delete_left = FALSE;
> - const char *path_ext = "";
> -
> - /* If we have any file extensions we're supposed to
> - preserve in generated conflict file names, then find
> - this path's extension. But then, if it isn't one of
> - the ones we want to keep in conflict filenames,
> - pretend it doesn't have an extension at all. */
> - if (eb->ext_patterns && eb->ext_patterns->nelts)
> - {
> - svn_path_splitext(NULL, &path_ext, fb->path, pool);
> - if (! (*path_ext
> - && svn_cstring_match_glob_list(path_ext,
> - eb->ext_patterns)))
> - path_ext = "";
> - }
> -
> - /* Create strings representing the revisions of the
> - old and new text-bases. */
> - /* Either an old version, or an add-with-history */
> - if (fb->added_with_history)
> - oldrev_str = apr_psprintf(pool, ".copied%s%s",
> - *path_ext ? "." : "",
> - *path_ext ? path_ext : "");
> - else
> - oldrev_str = apr_psprintf(pool, ".r%ld%s%s",
> - entry->revision,
> - *path_ext ? "." : "",
> - *path_ext ? path_ext : "");
> -
> - newrev_str = apr_psprintf(pool, ".r%ld%s%s",
> - *eb->target_revision,
> - *path_ext ? "." : "",
> - *path_ext ? path_ext : "");
> - mine_str = apr_psprintf(pool, ".mine%s%s",
> - *path_ext ? "." : "",
> - *path_ext ? path_ext : "");
> -
> - if (fb->add_existed && ! is_replaced)
> - {
> - SVN_ERR(svn_wc_create_tmp_file2(NULL, &merge_left,
> - svn_wc_adm_access_path(
> - adm_access),
> - svn_io_file_del_none,
> - pool));
> - delete_left = TRUE;
> - }
> - else if (fb->copied_text_base)
> - merge_left = fb->copied_text_base;
> - else
> - merge_left = fb->text_base_path;
> -
> - /* Merge the changes from the old textbase to the new
> - textbase into the file we're updating.
> - Remember that this function wants full paths! */
> - SVN_ERR(svn_wc__merge_internal
> - (&log_accum, &merge_outcome,
> - merge_left, NULL,
> - new_text_base_path, NULL,
> - fb->path,
> - fb->copied_working_text,
> - adm_access,
> - oldrev_str, newrev_str, mine_str,
> - FALSE, eb->diff3_cmd, NULL, fb->propchanges,
> - eb->conflict_func, eb->conflict_baton, pool));
> -
> - /* If we created a temporary left merge file, get rid of it. */
> - if (delete_left)
> - SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access,
> - merge_left, pool));
> -
> - /* And clean up add-with-history-related temp file too. */
> - if (fb->copied_working_text)
> - SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access,
> - fb->copied_working_text, pool));
> -
> - } /* end: working file exists and has mods */
> - } /* end: working file has mods */
> - } /* end: "textual" merging process */
> - else
> - {
> - apr_hash_t *keywords;
> -
> - SVN_ERR(svn_wc__get_keywords(&keywords, fb->path,
> - adm_access, NULL, pool));
> - if (magic_props_changed || keywords)
> - /* no new text base, but... */
> - {
> - /* Special edge-case: it's possible that this file installation
> - only involves propchanges, but that some of those props still
> - require a retranslation of the working file.
> -
> - OR that the file doesn't involve propchanges which by themselves
> - require retranslation, but receiving a change bumps the revision
> - number which requires re-expansion of keywords... */
> -
> - const char *tmptext;
> -
> - /* Copy and DEtranslate the working file to a temp text-base.
> - Note that detranslation is done according to the old props. */
> - SVN_ERR(svn_wc_translated_file2(&tmptext, fb->path, fb->path,
> - adm_access,
> - SVN_WC_TRANSLATE_TO_NF
> - | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP,
> - pool));
> -
> - /* A log command that copies the tmp-text-base and REtranslates
> - it back to the working file.
> - Now, since this is done during the execution of the log file, this
> - retranslation is actually done according to the new props. */
> - SVN_ERR(svn_wc__loggy_copy(&log_accum, adm_access,
> - tmptext, fb->path, pool));
> - }
> -
> - if (*lock_state == svn_wc_notify_lock_state_unlocked)
> - /* If a lock was removed and we didn't update the text contents, we
> - might need to set the file read-only. */
> - SVN_ERR(svn_wc__loggy_maybe_set_readonly(&log_accum, adm_access,
> - fb->path, pool));
> - }
> -
> - /* Deal with installation of the new textbase, if appropriate. */
> - if (new_text_base_path)
> - {
> - SVN_ERR(svn_wc__loggy_move(&log_accum,
> - adm_access, new_text_base_path,
> - fb->text_base_path, pool));
> - SVN_ERR(svn_wc__loggy_set_readonly(&log_accum, adm_access,
> - fb->text_base_path, pool));
> - tmp_entry.checksum = svn_checksum_to_cstring(actual_checksum, pool);
> - flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM;
> - }
> -
> - /* Do the entry modifications we've accumulated. */
> - SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access,
> - fb->path, &tmp_entry, flags, pool));
> -
> - /* Log commands to handle text-timestamp and working-size,
> - if the file is - or will be - unmodified and schedule-normal */
> - if (!is_locally_modified &&
> - (fb->added || entry->schedule == svn_wc_schedule_normal))
> - {
> - /* Adjust working copy file unless this file is an allowed
> - obstruction. */
> - if (fb->last_changed_date && !fb->existed)
> - SVN_ERR(svn_wc__loggy_set_timestamp(&log_accum, adm_access,
> - fb->path, fb->last_changed_date,
> - pool));
> -
> - if (new_text_base_path || magic_props_changed)
> - {
> - /* Adjust entries file to match working file */
> - SVN_ERR(svn_wc__loggy_set_entry_timestamp_from_wc
> - (&log_accum, adm_access,
> - fb->path, SVN_WC__ENTRY_ATTR_TEXT_TIME, pool));
> - }
> - SVN_ERR(svn_wc__loggy_set_entry_working_size_from_wc
> - (&log_accum, adm_access, fb->path, pool));
> - }
> -
> - /* Clean up add-with-history temp file. */
> - if (fb->copied_text_base)
> - SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access,
> - fb->copied_text_base,
> - pool));
> -
> -
> - /* Set the returned content state. */
> -
> - /* This is kind of interesting. Even if no new text was
> - installed (i.e., new_text_path was null), we could still
> - report a pre-existing conflict state. Say a file, already
> - in a state of textual conflict, receives prop mods during an
> - update. Then we'll notify that it has text conflicts. This
> - seems okay to me. I guess. I dunno. You? */
> -
> - if (merge_outcome == svn_wc_merge_conflict)
> - *content_state = svn_wc_notify_state_conflicted;
> - else if (new_text_base_path)
> - {
> - if (is_locally_modified)
> - *content_state = svn_wc_notify_state_merged;
> - else
> - *content_state = svn_wc_notify_state_changed;
> - }
> - else
> - *content_state = svn_wc_notify_state_unchanged;
> -
> - /* Now that we've built up *all* of the loggy commands for this
> - file, add them to the directory's log accumulator in one fell
> - swoop. */
> - svn_stringbuf_appendstr(fb->dir_baton->log_accum, log_accum);
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -/* Mostly a wrapper around merge_file. */
> -static svn_error_t *
> -close_file(void *file_baton,
> - const char *expected_hex_digest,
> - apr_pool_t *pool)
> -{
> - struct file_baton *fb = file_baton;
> - struct edit_baton *eb = fb->edit_baton;
> - svn_wc_notify_state_t content_state, prop_state;
> - svn_wc_notify_lock_state_t lock_state;
> - svn_checksum_t *expected_checksum = NULL;
> - svn_checksum_t *actual_checksum;
> - const char *new_base_path;
> -
> - if (fb->skipped)
> - return maybe_bump_dir_info(eb, fb->bump_info, pool);
> -
> - if (expected_hex_digest)
> - SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
> - expected_hex_digest, pool));
> -
> - /* Was this an add-with-history, with no apply_textdelta? */
> - if (fb->added_with_history && ! fb->received_textdelta)
> - {
> - SVN_ERR_ASSERT(! fb->text_base_path && ! fb->new_text_base_path
> - && fb->copied_text_base);
> -
> - /* Set up the base paths like apply_textdelta does. */
> - SVN_ERR(choose_base_paths(&fb->text_base_path,
> - NULL, NULL,
> - eb->adm_access, fb->path,
> - fb->pool, pool));
> -
> - actual_checksum = fb->copied_base_checksum;
> - new_base_path = fb->copied_text_base;
> - }
> - else
> - {
> - /* Pull the actual checksum from the file_baton, computed during
> - the application of a text delta. */
> - actual_checksum = fb->actual_checksum;
> - new_base_path = fb->new_text_base_path;
> - }
> -
> - /* window-handler assembles new pristine text in .svn/tmp/text-base/ */
> - if (new_base_path && expected_checksum
> - && !svn_checksum_match(expected_checksum, actual_checksum))
> - return svn_error_createf
> - (SVN_ERR_CHECKSUM_MISMATCH, NULL,
> - _("Checksum mismatch for '%s'; expected: '%s', actual: '%s'"),
> - svn_path_local_style(fb->path, pool), expected_hex_digest,
> - svn_checksum_to_cstring_display(actual_checksum, pool));
> -
> - SVN_ERR(merge_file(&content_state, &prop_state, &lock_state, fb,
> - new_base_path,
> - actual_checksum, pool));
> -
> - /* We have one less referrer to the directory's bump information. */
> - SVN_ERR(maybe_bump_dir_info(eb, fb->bump_info, pool));
> -
> - if (((content_state != svn_wc_notify_state_unchanged) ||
> - (prop_state != svn_wc_notify_state_unchanged) ||
> - (lock_state != svn_wc_notify_lock_state_unchanged) ||
> - (fb->tree_conflicted))
> - && eb->notify_func)
> - {
> - svn_wc_notify_t *notify;
> - svn_wc_notify_action_t action = svn_wc_notify_update_update;
> -
> - if (fb->tree_conflicted)
> - action = svn_wc_notify_tree_conflict;
> - else if (fb->existed || fb->add_existed)
> - {
> - if (content_state != svn_wc_notify_state_conflicted)
> - action = svn_wc_notify_exists;
> - }
> - else if (fb->added)
> - {
> - action = svn_wc_notify_update_add;
> - }
> -
> - notify = svn_wc_create_notify(fb->path, action, pool);
> - notify->kind = svn_node_file;
> - notify->content_state = content_state;
> - notify->prop_state = prop_state;
> - notify->lock_state = lock_state;
> - /* ### use merge_file() mimetype here */
> - (*eb->notify_func)(eb->notify_baton, notify, pool);
> - }
> - return SVN_NO_ERROR;
> -}
> -
> -
> -/* An svn_delta_editor_t function. */
> -static svn_error_t *
> -close_edit(void *edit_baton,
> - apr_pool_t *pool)
> -{
> - struct edit_baton *eb = edit_baton;
> - const char *target_path = svn_path_join(eb->anchor, eb->target, pool);
> - int log_number = 0;
> -
> - /* If there is a target and that target is missing, then it
> - apparently wasn't re-added by the update process, so we'll
> - pretend that the editor deleted the entry. The helper function
> - do_entry_deletion() will take care of the necessary steps. */
> - if ((*eb->target) && (svn_wc__adm_missing(eb->adm_access, target_path)))
> - SVN_ERR(do_entry_deletion(eb, eb->anchor, eb->target, NULL,
> - &log_number, pool));
> -
> - /* The editor didn't even open the root; we have to take care of
> - some cleanup stuffs. */
> - if (! eb->root_opened)
> - {
> - /* We need to "un-incomplete" the root directory. */
> - SVN_ERR(complete_directory(eb, eb->anchor, TRUE, pool));
> - }
> -
> -
> - /* By definition, anybody "driving" this editor for update or switch
> - purposes at a *minimum* must have called set_target_revision() at
> - the outset, and close_edit() at the end -- even if it turned out
> - that no changes ever had to be made, and open_root() was never
> - called. That's fine. But regardless, when the edit is over,
> - this editor needs to make sure that *all* paths have had their
> - revisions bumped to the new target revision. */
> -
> - /* Make sure our update target now has the new working revision.
> - Also, if this was an 'svn switch', then rewrite the target's
> - url. All of this tweaking might happen recursively! Note
> - that if eb->target is NULL, that's okay (albeit "sneaky",
> - some might say). */
> -
> - /* Extra check: if the update did nothing but make its target
> - 'deleted', then do *not* run cleanup on the target, as it
> - will only remove the deleted entry! */
> - if (! eb->target_deleted)
> - SVN_ERR(svn_wc__do_update_cleanup(target_path,
> - eb->adm_access,
> - eb->requested_depth,
> - eb->switch_url,
> - eb->repos,
> - *(eb->target_revision),
> - eb->notify_func,
> - eb->notify_baton,
> - TRUE, eb->skipped_paths,
> - eb->pool));
> -
> - /* The edit is over, free its pool.
> - ### No, this is wrong. Who says this editor/baton won't be used
> - again? But the change is not merely to remove this call. We
> - should also make eb->pool not be a subpool (see make_editor),
> - and change callers of svn_client_{checkout,update,switch} to do
> - better pool management. ### */
> - svn_pool_destroy(eb->pool);
> -
> - return SVN_NO_ERROR;
> -}
> -
> -
> -
> -/*** Returning editors. ***/
> -
> -/* Helper for the three public editor-supplying functions. */
> -static svn_error_t *
> -make_editor(svn_revnum_t *target_revision,
> - svn_wc_adm_access_t *adm_access,
> - const char *anchor,
> - const char *target,
> - svn_boolean_t use_commit_times,
> - const char *switch_url,
> - svn_depth_t depth,
> - svn_boolean_t depth_is_sticky,
> - svn_boolean_t allow_unver_obstructions,
> - svn_wc_notify_func2_t notify_func,
> - void *notify_baton,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - svn_wc_conflict_resolver_func_t conflict_func,
> - void *conflict_baton,
> - svn_wc_get_file_t fetch_func,
> - void *fetch_baton,
> - const char *diff3_cmd,
> - apr_array_header_t *preserved_exts,
> - const svn_delta_editor_t **editor,
> - void **edit_baton,
> - svn_wc_traversal_info_t *traversal_info,
> - apr_pool_t *pool)
> -{
> - struct edit_baton *eb;
> - void *inner_baton;
> - apr_pool_t *subpool = svn_pool_create(pool);
> - svn_delta_editor_t *tree_editor = svn_delta_default_editor(subpool);
> - const svn_delta_editor_t *inner_editor;
> - const svn_wc_entry_t *entry;
> -
> - /* An unknown depth can't be sticky. */
> - if (depth == svn_depth_unknown)
> - depth_is_sticky = FALSE;
> -
> - /* Get the anchor entry, so we can fetch the repository root. */
> - SVN_ERR(svn_wc_entry(&entry, anchor, adm_access, FALSE, pool));
> -
> - /* Disallow a switch operation to change the repository root of the target,
> - if that is known. */
> - if (switch_url && entry && entry->repos &&
> - ! svn_path_is_ancestor(entry->repos, switch_url))
> - return svn_error_createf
> - (SVN_ERR_WC_INVALID_SWITCH, NULL,
> - _("'%s'\n"
> - "is not the same repository as\n"
> - "'%s'"), switch_url, entry->repos);
> -
> - /* Construct an edit baton. */
> - eb = apr_pcalloc(subpool, sizeof(*eb));
> - eb->pool = subpool;
> - eb->use_commit_times = use_commit_times;
> - eb->target_revision = target_revision;
> - eb->switch_url = switch_url;
> - eb->repos = entry ? entry->repos : NULL;
> - eb->adm_access = adm_access;
> - eb->anchor = anchor;
> - eb->target = target;
> - eb->requested_depth = depth;
> - eb->depth_is_sticky = depth_is_sticky;
> - eb->notify_func = notify_func;
> - eb->notify_baton = notify_baton;
> - eb->traversal_info = traversal_info;
> - eb->diff3_cmd = diff3_cmd;
> - eb->cancel_func = cancel_func;
> - eb->cancel_baton = cancel_baton;
> - eb->conflict_func = conflict_func;
> - eb->conflict_baton = conflict_baton;
> - eb->fetch_func = fetch_func;
> - eb->fetch_baton = fetch_baton;
> - eb->allow_unver_obstructions = allow_unver_obstructions;
> - eb->skipped_paths = apr_hash_make(subpool);
> - eb->ext_patterns = preserved_exts;
> - eb->current_conflict = NULL;
> -
> - /* Construct an editor. */
> - tree_editor->set_target_revision = set_target_revision;
> - tree_editor->open_root = open_root;
> - tree_editor->delete_entry = delete_entry;
> - tree_editor->add_directory = add_directory;
> - tree_editor->open_directory = open_directory;
> - tree_editor->change_dir_prop = change_dir_prop;
> - tree_editor->close_directory = close_directory;
> - tree_editor->absent_directory = absent_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->absent_file = absent_file;
> - tree_editor->close_edit = close_edit;
> -
> - /* Fiddle with the type system. */
> - inner_editor = tree_editor;
> - inner_baton = eb;
> -
> - /* We need to limit the scope of our operation to the ambient depths
> - present in the working copy already, but only if the requested
> - depth is not sticky. If a depth was explicitly requested,
> - libsvn_delta/depth_filter_editor.c will ensure that we never see
> - editor calls that extend beyond the scope of the requested depth.
> - But even what we do so might extend beyond the scope of our
> - ambient depth. So we use another filtering editor to avoid
> - modifying the ambient working copy depth when not asked to do so.
> - (This can also be skipped if the server understands depth; consider
> - letting the depth RA capability percolate down to this level.) */
> - if (!depth_is_sticky)
> - SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
> - &inner_baton,
> - inner_editor,
> - inner_baton,
> - anchor,
> - target,
> - adm_access,
> - pool));
> -
> - return svn_delta_get_cancellation_editor(cancel_func,
> - cancel_baton,
> - inner_editor,
> - inner_baton,
> - editor,
> - edit_baton,
> - pool);
> -}
> -
> -
> -svn_error_t *
> -svn_wc_get_update_editor3(svn_revnum_t *target_revision,
> - svn_wc_adm_access_t *anchor,
> - const char *target,
> - svn_boolean_t use_commit_times,
> - svn_depth_t depth,
> - svn_boolean_t depth_is_sticky,
> - svn_boolean_t allow_unver_obstructions,
> - svn_wc_notify_func2_t notify_func,
> - void *notify_baton,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - svn_wc_conflict_resolver_func_t conflict_func,
> - void *conflict_baton,
> - svn_wc_get_file_t fetch_func,
> - void *fetch_baton,
> - const char *diff3_cmd,
> - apr_array_header_t *preserved_exts,
> - const svn_delta_editor_t **editor,
> - void **edit_baton,
> - svn_wc_traversal_info_t *traversal_info,
> - apr_pool_t *pool)
> -{
> - return make_editor(target_revision, anchor, svn_wc_adm_access_path(anchor),
> - target, use_commit_times, NULL, depth, depth_is_sticky,
> - allow_unver_obstructions, notify_func, notify_baton,
> - cancel_func, cancel_baton, conflict_func, conflict_baton,
> - fetch_func, fetch_baton,
> - diff3_cmd, preserved_exts, editor, edit_baton,
> - traversal_info, pool);
> -}
> -
> -
> -svn_error_t *
> -svn_wc_get_update_editor2(svn_revnum_t *target_revision,
> - svn_wc_adm_access_t *anchor,
> - const char *target,
> - svn_boolean_t use_commit_times,
> - svn_boolean_t recurse,
> - svn_wc_notify_func2_t notify_func,
> - void *notify_baton,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - const char *diff3_cmd,
> - const svn_delta_editor_t **editor,
> - void **edit_baton,
> - svn_wc_traversal_info_t *traversal_info,
> - apr_pool_t *pool)
> -{
> - return svn_wc_get_update_editor3(target_revision, anchor, target,
> - use_commit_times,
> - SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
> - FALSE, notify_func, notify_baton,
> - cancel_func, cancel_baton, NULL, NULL,
> - NULL, NULL,
> - diff3_cmd, NULL, editor, edit_baton,
> - traversal_info, pool);
> -}
> -
> -svn_error_t *
> -svn_wc_get_update_editor(svn_revnum_t *target_revision,
> - svn_wc_adm_access_t *anchor,
> - const char *target,
> - svn_boolean_t use_commit_times,
> - svn_boolean_t recurse,
> - svn_wc_notify_func_t notify_func,
> - void *notify_baton,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - const char *diff3_cmd,
> - const svn_delta_editor_t **editor,
> - void **edit_baton,
> - svn_wc_traversal_info_t *traversal_info,
> - apr_pool_t *pool)
> -{
> - svn_wc__compat_notify_baton_t *nb = apr_palloc(pool, sizeof(*nb));
> - nb->func = notify_func;
> - nb->baton = notify_baton;
> -
> - return svn_wc_get_update_editor3(target_revision, anchor, target,
> - use_commit_times,
> - SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
> - FALSE, svn_wc__compat_call_notify_func, nb,
> - cancel_func, cancel_baton, NULL, NULL,
> - NULL, NULL,
> - diff3_cmd, NULL, editor, edit_baton,
> - traversal_info, pool);
> -}
> -
> -svn_error_t *
> -svn_wc_get_switch_editor3(svn_revnum_t *target_revision,
> - svn_wc_adm_access_t *anchor,
> - const char *target,
> - const char *switch_url,
> - svn_boolean_t use_commit_times,
> - svn_depth_t depth,
> - svn_boolean_t depth_is_sticky,
> - svn_boolean_t allow_unver_obstructions,
> - svn_wc_notify_func2_t notify_func,
> - void *notify_baton,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - svn_wc_conflict_resolver_func_t conflict_func,
> - void *conflict_baton,
> - const char *diff3_cmd,
> - apr_array_header_t *preserved_exts,
> - const svn_delta_editor_t **editor,
> - void **edit_baton,
> - svn_wc_traversal_info_t *traversal_info,
> - apr_pool_t *pool)
> -{
> - SVN_ERR_ASSERT(switch_url);
> -
> - return make_editor(target_revision, anchor, svn_wc_adm_access_path(anchor),
> - target, use_commit_times, switch_url,
> - depth, depth_is_sticky, allow_unver_obstructions,
> - notify_func, notify_baton, cancel_func, cancel_baton,
> - conflict_func, conflict_baton,
> - NULL, NULL, /* TODO(sussman): add fetch callback here */
> - diff3_cmd, preserved_exts,
> - editor, edit_baton, traversal_info, pool);
> -}
> -
> -svn_error_t *
> -svn_wc_get_switch_editor2(svn_revnum_t *target_revision,
> - svn_wc_adm_access_t *anchor,
> - const char *target,
> - const char *switch_url,
> - svn_boolean_t use_commit_times,
> - svn_boolean_t recurse,
> - svn_wc_notify_func2_t notify_func,
> - void *notify_baton,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - const char *diff3_cmd,
> - const svn_delta_editor_t **editor,
> - void **edit_baton,
> - svn_wc_traversal_info_t *traversal_info,
> - apr_pool_t *pool)
> -{
> - SVN_ERR_ASSERT(switch_url);
> -
> - return svn_wc_get_switch_editor3(target_revision, anchor, target,
> - switch_url, use_commit_times,
> - SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
> - FALSE, notify_func, notify_baton,
> - cancel_func, cancel_baton,
> - NULL, NULL, diff3_cmd,
> - NULL, editor, edit_baton, traversal_info,
> - pool);
> -}
> -
> -svn_error_t *
> -svn_wc_get_switch_editor(svn_revnum_t *target_revision,
> - svn_wc_adm_access_t *anchor,
> - const char *target,
> - const char *switch_url,
> - svn_boolean_t use_commit_times,
> - svn_boolean_t recurse,
> - svn_wc_notify_func_t notify_func,
> - void *notify_baton,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - const char *diff3_cmd,
> - const svn_delta_editor_t **editor,
> - void **edit_baton,
> - svn_wc_traversal_info_t *traversal_info,
> - apr_pool_t *pool)
> -{
> - svn_wc__compat_notify_baton_t *nb = apr_palloc(pool, sizeof(*nb));
> - nb->func = notify_func;
> - nb->baton = notify_baton;
> -
> - return svn_wc_get_switch_editor3(target_revision, anchor, target,
> - switch_url, use_commit_times,
> - SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE,
> - FALSE, svn_wc__compat_call_notify_func, nb,
> - cancel_func, cancel_baton,
> - NULL, NULL, diff3_cmd,
> - NULL, editor, edit_baton, traversal_info,
> - pool);
> -}
> -
> -
> -svn_wc_traversal_info_t *
> -svn_wc_init_traversal_info(apr_pool_t *pool)
> -{
> - svn_wc_traversal_info_t *ti = apr_palloc(pool, sizeof(*ti));
> -
> - ti->pool = pool;
> - ti->externals_old = apr_hash_make(pool);
> - ti->externals_new = apr_hash_make(pool);
> - ti->depths = apr_hash_make(pool);
> -
> - return ti;
> -}
> -
> -
> -void
> -svn_wc_edited_externals(apr_hash_t **externals_old,
> - apr_hash_t **externals_new,
> - svn_wc_traversal_info_t *traversal_info)
> -{
> - *externals_old = traversal_info->externals_old;
> - *externals_new = traversal_info->externals_new;
> -}
> -
> -
> -void
> -svn_wc_traversed_depths(apr_hash_t **depths,
> - svn_wc_traversal_info_t *traversal_info)
> -{
> - *depths = traversal_info->depths;
> -}
> -
> -
> -/* ABOUT ANCHOR AND TARGET, AND svn_wc_get_actual_target()
> -
> - THE GOAL
> -
> - Note the following actions, where X is the thing we wish to update,
> - P is a directory whose repository URL is the parent of
> - X's repository URL, N is directory whose repository URL is *not*
> - the parent directory of X (including the case where N is not a
> - versioned resource at all):
> -
> - 1. `svn up .' from inside X.
> - 2. `svn up ...P/X' from anywhere.
> - 3. `svn up ...N/X' from anywhere.
> -
> - For the purposes of the discussion, in the '...N/X' situation, X is
> - said to be a "working copy (WC) root" directory.
> -
> - Now consider the four cases for X's type (file/dir) in the working
> - copy vs. the repository:
> -
> - A. dir in working copy, dir in repos.
> - B. dir in working copy, file in repos.
> - C. file in working copy, dir in repos.
> - D. file in working copy, file in repos.
> -
> - Here are the results we expect for each combination of the above:
> -
> - 1A. Successfully update X.
> - 1B. Error (you don't want to remove your current working
> - directory out from underneath the application).
> - 1C. N/A (you can't be "inside X" if X is a file).
> - 1D. N/A (you can't be "inside X" if X is a file).
> -
> - 2A. Successfully update X.
> - 2B. Successfully update X.
> - 2C. Successfully update X.
> - 2D. Successfully update X.
> -
> - 3A. Successfully update X.
> - 3B. Error (you can't create a versioned file X inside a
> - non-versioned directory).
> - 3C. N/A (you can't have a versioned file X in directory that is
> - not its repository parent).
> - 3D. N/A (you can't have a versioned file X in directory that is
> - not its repository parent).
> -
> - To summarize, case 2 always succeeds, and cases 1 and 3 always fail
> - (or can't occur) *except* when the target is a dir that remains a
> - dir after the update.
> -
> - ACCOMPLISHING THE GOAL
> -
> - Updates are accomplished by driving an editor, and an editor is
> - "rooted" on a directory. So, in order to update a file, we need to
> - break off the basename of the file, rooting the editor in that
> - file's parent directory, and then updating only that file, not the
> - other stuff in its parent directory.
> -
> - Secondly, we look at the case where we wish to update a directory.
> - This is typically trivial. However, one problematic case, exists
> - when we wish to update a directory that has been removed from the
> - repository and replaced with a file of the same name. If we root
> - our edit at the initial directory, there is no editor mechanism for
> - deleting that directory and replacing it with a file (this would be
> - like having an editor now anchored on a file, which is disallowed).
> -
> - All that remains is to have a function with the knowledge required
> - to properly decide where to root our editor, and what to act upon
> - with that now-rooted editor. Given a path to be updated, this
> - function should conditionally split that path into an "anchor" and
> - a "target", where the "anchor" is the directory at which the update
> - editor is rooted (meaning, editor->open_root() is called with
> - this directory in mind), and the "target" is the actual intended
> - subject of the update.
> -
> - svn_wc_get_actual_target() is that function.
> -
> - So, what are the conditions?
> -
> - Case I: Any time X is '.' (implying it is a directory), we won't
> - lop off a basename. So we'll root our editor at X, and update all
> - of X.
> -
> - Cases II & III: Any time we are trying to update some path ...N/X,
> - we again will not lop off a basename. We can't root an editor at
> - ...N with X as a target, either because ...N isn't a versioned
> - resource at all (Case II) or because X is X is not a child of ...N
> - in the repository (Case III). We root at X, and update X.
> -
> - Cases IV-???: We lop off a basename when we are updating a
> - path ...P/X, rooting our editor at ...P and updating X, or when X
> - is missing from disk.
> -
> - These conditions apply whether X is a file or directory.
> -
> - ---
> -
> - As it turns out, commits need to have a similar check in place,
> - too, specifically for the case where a single directory is being
> - committed (we have to anchor at that directory's parent in case the
> - directory itself needs to be modified).
> -*/
> -
> -
> -/* Like svn_wc_is_wc_root(), but also, if KIND is not null, set *KIND to
> - * the versioned node kind of PATH, or to svn_node_file if PATH is
> - * unversioned.
> - */
> -static svn_error_t *
> -check_wc_root(svn_boolean_t *wc_root,
> - svn_node_kind_t *kind,
> - const char *path,
> - svn_wc_adm_access_t *adm_access,
> - apr_pool_t *pool)
> -{
> - const char *parent, *base_name;
> - const svn_wc_entry_t *p_entry, *entry;
> - svn_error_t *err;
> - svn_wc_adm_access_t *p_access;
> -
> - /* Go ahead and initialize our return value to the most common
> - (code-wise) values. */
> - *wc_root = TRUE;
> -
> - /* Get our ancestry. In the event that the path is unversioned,
> - treat it as if it were a file so that the anchor will be the
> - parent directory. */
> - SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
> - if (kind)
> - *kind = entry ? entry->kind : svn_node_file;
> -
> - /* If PATH is the current working directory, we have no choice but
> - to consider it a WC root (we can't examine its parent at all) */
> - if (svn_path_is_empty(path))
> - return SVN_NO_ERROR;
> -
> - /* If this is the root folder (of a drive), it should be the WC
> - root too. */
> - if (svn_dirent_is_root(path, strlen(path)))
> - return SVN_NO_ERROR;
> -
> - /* If we cannot get an entry for PATH's parent, PATH is a WC root. */
> - p_entry = NULL;
> - svn_path_split(path, &parent, &base_name, pool);
> - SVN_ERR(svn_wc__adm_retrieve_internal(&p_access, adm_access, parent,
> - pool));
> - err = SVN_NO_ERROR;
> - if (! p_access)
> - /* For historical reasons we cannot rely on the caller having opened
> - the parent, so try it here. I'd like this bit to go away. */
> - err = svn_wc_adm_probe_open3(&p_access, NULL, parent, FALSE, 0,
> - NULL, NULL, pool);
> -
> - if (! err)
> - err = svn_wc_entry(&p_entry, parent, p_access, FALSE, pool);
> -
> - if (err || (! p_entry))
> - {
> - svn_error_clear(err);
> - return SVN_NO_ERROR;
> - }
> -
> - /* If the parent directory has no url information, something is
> - messed up. Bail with an error. */
> - if (! p_entry->url)
> - return svn_error_createf
> - (SVN_ERR_ENTRY_MISSING_URL, NULL,
> - _("'%s' has no ancestry information"),
> - svn_path_local_style(parent, pool));
> -
> - /* If PATH's parent in the WC is not its parent in the repository,
> - PATH is a WC root. */
> - if (entry && entry->url
> - && (strcmp(svn_path_url_add_component(p_entry->url, base_name, pool),
> - entry->url) != 0))
> - return SVN_NO_ERROR;
> -
> - /* If PATH's parent in the repository is not its parent in the WC,
> - PATH is a WC root. */
> - SVN_ERR(svn_wc_entry(&p_entry, path, p_access, FALSE, pool));
> - if (! p_entry)
> - return SVN_NO_ERROR;
> -
> - /* If we have not determined that PATH is a WC root by now, it must
> - not be! */
> - *wc_root = FALSE;
> - return SVN_NO_ERROR;
> -}
> -
> -
> -svn_error_t *
> -svn_wc_is_wc_root(svn_boolean_t *wc_root,
> - const char *path,
> - svn_wc_adm_access_t *adm_access,
> - apr_pool_t *pool)
> -{
> - return check_wc_root(wc_root, NULL, path, adm_access, pool);
> -}
> -
> -
> -svn_error_t *
> -svn_wc_get_actual_target(const char *path,
> - const char **anchor,
> - const char **target,
> - apr_pool_t *pool)
> -{
> - svn_wc_adm_access_t *adm_access;
> - svn_boolean_t is_wc_root;
> - svn_node_kind_t kind;
> -
> - SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path, FALSE, 0,
> - NULL, NULL, pool));
> - SVN_ERR(check_wc_root(&is_wc_root, &kind, path, adm_access, pool));
> - SVN_ERR(svn_wc_adm_close2(adm_access, pool));
> -
> - /* If PATH is not a WC root, or if it is a file, lop off a basename. */
> - if ((! is_wc_root) || (kind == svn_node_file))
> - {
> - svn_path_split(path, anchor, target, pool);
> - }
> - else
> - {
> - *anchor = apr_pstrdup(pool, path);
> - *target = "";
> - }
> -
> - return SVN_NO_ERROR;
> -}
> -
> -/* Write, to LOG_ACCUM, commands to install properties for an added DST_PATH.
> - NEW_BASE_PROPS and NEW_PROPS are base and working properties, respectively.
> - BASE_PROPS can contain entryprops and wcprops as well. ADM_ACCESS must
> - be an access baton for DST_PATH.
> - Use @a POOL for temporary allocations. */
> -static svn_error_t *
> -install_added_props(svn_stringbuf_t *log_accum,
> - svn_wc_adm_access_t *adm_access,
> - const char *dst_path,
> - apr_hash_t *new_base_props,
> - apr_hash_t *new_props,
> - apr_pool_t *pool)
> -{
> - apr_array_header_t *regular_props = NULL, *wc_props = NULL,
> - *entry_props = NULL;
> -
> - /* Categorize the base properties. */
> - {
> - apr_array_header_t *prop_array;
> - int i;
> -
> - /* Diff an empty prop has against the new base props gives us an array
> - of all props. */
> - SVN_ERR(svn_prop_diffs(&prop_array, new_base_props,
> - apr_hash_make(pool), pool));
> - SVN_ERR(svn_categorize_props(prop_array,
> - &entry_props, &wc_props, ®ular_props,
> - pool));
> -
> - /* Put regular props back into a hash table. */
> - new_base_props = apr_hash_make(pool);
> - for (i = 0; i < regular_props->nelts; ++i)
> - {
> - const svn_prop_t *prop = &APR_ARRAY_IDX(regular_props, i, svn_prop_t);
> -
> - apr_hash_set(new_base_props, prop->name, APR_HASH_KEY_STRING,
> - prop->value);
> - }
> - }
> -
> - /* Install base and working props. */
> - SVN_ERR(svn_wc__install_props(&log_accum, adm_access, dst_path,
> - new_base_props,
> - new_props ? new_props : new_base_props,
> - TRUE, pool));
> -
> - /* Install the entry props. */
> - SVN_ERR(accumulate_entry_props(log_accum, NULL,
> - adm_access, dst_path,
> - entry_props, pool));
> -
> - /* This writes a whole bunch of log commands to install wcprops. */
> - return accumulate_wcprops(log_accum, adm_access,
> - dst_path, wc_props, pool);
> -}
> -
> -svn_error_t *
> -svn_wc_add_repos_file3(const char *dst_path,
> - svn_wc_adm_access_t *adm_access,
> - svn_stream_t *new_base_contents,
> - svn_stream_t *new_contents,
> - apr_hash_t *new_base_props,
> - apr_hash_t *new_props,
> - const char *copyfrom_url,
> - svn_revnum_t copyfrom_rev,
> - svn_cancel_func_t cancel_func,
> - void *cancel_baton,
> - svn_wc_notify_func2_t notify_func,
> - void *notify_baton,
> - apr_pool_t *pool)
> -{
> - const char *new_URL;
> - const char *adm_path = svn_wc_adm_access_path(adm_access);
> - apr_file_t *base_file;
> - const char *tmp_text_base_path;
> - svn_checksum_t *base_checksum;
> - svn_stream_t *tmp_base_contents;
> - const char *text_base_path = svn_wc__text_base_path(dst_path, FALSE, pool);
> - const svn_wc_entry_t *ent;
> - const svn_wc_entry_t *dst_entry;
> - svn_stringbuf_t *log_accum;
> - const char *dir_name, *base_name;
> -
> - svn_path_split(dst_path, &dir_name, &base_name, pool);
> -
> - /* Fabricate the anticipated new URL of the target and check the
> - copyfrom URL to be in the same repository. */
> - {
> - SVN_ERR(svn_wc__entry_versioned(&ent, dir_name, adm_access, FALSE, pool));
> -
> - new_URL = svn_path_url_add_component(ent->url, base_name, pool);
> -
> - if (copyfrom_url && ent->repos &&
> - ! svn_path_is_ancestor(ent->repos, copyfrom_url))
> - return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
> - _("Copyfrom-url '%s' has different repository"
> - " root than '%s'"),
> - copyfrom_url, ent->repos);
> - }
> -
> - /* Accumulate log commands in this buffer until we're ready to close
> - and run the log. */
> - log_accum = svn_stringbuf_create("", pool);
> -
> - /* If we're replacing the file then we need to save the destination files
> - text base and prop base before replacing it. This allows us to revert
> - the entire change. */
> - SVN_ERR(svn_wc_entry(&dst_entry, dst_path, adm_access, FALSE, pool));
> - if (dst_entry && dst_entry->schedule == svn_wc_schedule_delete)
> - {
> - const char *dst_rtext = svn_wc__text_revert_path(dst_path, FALSE, pool);
> - const char *dst_txtb = svn_wc__text_base_path(dst_path, FALSE, pool);
> -
> - SVN_ERR(svn_wc__loggy_move(&log_accum,
> - adm_access, dst_txtb, dst_rtext,
> - pool));
> - SVN_ERR(svn_wc__loggy_revert_props_create(&log_accum,
> - dst_path, adm_access,
> - TRUE, pool));
> - }
> -
> - /* Schedule this for addition first, before the entry exists.
> - * Otherwise we'll get bounced out with an error about scheduling
> - * an already-versioned item for addition.
> - */
> - {
> - svn_wc_entry_t tmp_entry;
> - apr_uint64_t modify_flags = SVN_WC__ENTRY_MODIFY_SCHEDULE;
> -
> - tmp_entry.schedule = svn_wc_schedule_add;
> -
> - if (copyfrom_url)
> - {
> - SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(copyfrom_rev));
> -
> - tmp_entry.copyfrom_url = copyfrom_url;
> - tmp_entry.copyfrom_rev = copyfrom_rev;
> - tmp_entry.copied = TRUE;
> -
> - modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_URL
> - | SVN_WC__ENTRY_MODIFY_COPYFROM_REV
> - | SVN_WC__ENTRY_MODIFY_COPIED;
> - }
> -
> - SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access,
> - dst_path, &tmp_entry,
> - modify_flags, pool));
> - }
> -
> - /* Set the new revision number and URL in the entry and clean up some other
> - fields. */
> - SVN_ERR(loggy_tweak_entry(log_accum, adm_access, dst_path,
> - dst_entry ? dst_entry->revision : ent->revision,
> - new_URL, pool));
> -
> - /* Install the props first, so that the loggy translation has access to
> - the properties for this file. */
> - SVN_ERR(install_added_props(log_accum, adm_access, dst_path,
> - new_base_props, new_props, pool));
> -
> - /* Copy the text base contents into a temporary file so our log
> - can refer to it. Compute its checksum as we copy. */
> - SVN_ERR(svn_wc_create_tmp_file2(&base_file, &tmp_text_base_path, adm_path,
> - svn_io_file_del_none, pool));
> - new_base_contents = svn_stream_checksummed2(new_base_contents,
> - &base_checksum, svn_checksum_md5,
> - NULL, 0,
> - TRUE, pool);
> - tmp_base_contents = svn_stream_from_aprfile2(base_file, FALSE, pool);
> - SVN_ERR(svn_stream_copy3(new_base_contents, tmp_base_contents,
> - cancel_func, cancel_baton,
> - pool));
> -
> - /* Install working file. */
> - if (new_contents)
> - {
> - /* If the caller gave us a new working file, copy it in place. */
> - apr_file_t *contents_file;
> - svn_stream_t *tmp_contents;
> - const char *tmp_text_path;
> -
> - SVN_ERR(svn_wc_create_tmp_file2(&contents_file, &tmp_text_path, adm_path,
> - svn_io_file_del_none, pool));
> - tmp_contents = svn_stream_from_aprfile2(contents_file, FALSE, pool);
> - SVN_ERR(svn_stream_copy3(new_contents,
> - tmp_contents,
> - cancel_func, cancel_baton,
> - pool));
> -
> - /* Translate new temporary text file to working text. */
> - SVN_ERR(svn_wc__loggy_copy(&log_accum, adm_access,
> - tmp_text_path, dst_path,
> - pool));
> -
> - /* After copying to the working directory, lose the temp file. */
> - SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access,
> - tmp_text_path,
> - pool));
> - }
> - else
> - {
> - /* No working file provided by the caller, copy and translate the
> - text base. */
> - SVN_ERR(svn_wc__loggy_copy(&log_accum, adm_access,
> - tmp_text_base_path, dst_path,
> - pool));
> - SVN_ERR(svn_wc__loggy_set_entry_timestamp_from_wc
> - (&log_accum, adm_access,
> - dst_path, SVN_WC__ENTRY_ATTR_TEXT_TIME, pool));
> - SVN_ERR(svn_wc__loggy_set_entry_working_size_from_wc
> - (&log_accum, adm_access, dst_path, pool));
> - }
> -
> - /* Install new text base. */
> - {
> - svn_wc_entry_t tmp_entry;
> -
> - /* Write out log commands to set up the new text base and its
> - checksum. */
> - SVN_ERR(svn_wc__loggy_move(&log_accum,
> - adm_access, tmp_text_base_path,
> - text_base_path, pool));
> - SVN_ERR(svn_wc__loggy_set_readonly(&log_accum, adm_access,
> - text_base_path, pool));
> -
> - tmp_entry.checksum = svn_checksum_to_cstring(base_checksum, pool);
> - SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access,
> - dst_path, &tmp_entry,
> - SVN_WC__ENTRY_MODIFY_CHECKSUM,
> - pool));
> - }
> -
> - /* Write our accumulation of log entries into a log file */
> - SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
> -
> - return svn_wc__run_log(adm_access, NULL, pool);
> + return result;
> }
>
> Modified: branches/tc_url_rev/subversion/libsvn_wc/update_editor.c
> URL: http://svn.collab.net/viewvc/svn/branches/tc_url_rev/subversion/libsvn_wc/update_editor.c?pathrev=34332&r1=34331&r2=34332
> ==============================================================================
> --- branches/tc_url_rev/subversion/libsvn_wc/update_editor.c Fri Nov 21 23:41:51 2008 (r34331)
> +++ branches/tc_url_rev/subversion/libsvn_wc/update_editor.c Sat Nov 22 05:58:46 2008 (r34332)
> @@ -3813,8 +3813,8 @@ merge_file(svn_wc_notify_state_t *conten
> Remember that this function wants full paths! */
> SVN_ERR(svn_wc__merge_internal
> (&log_accum, &merge_outcome,
> - merge_left,
> - new_text_base_path,
> + merge_left, NULL,
> + new_text_base_path, NULL,
> fb->path,
> fb->copied_working_text,
> adm_access,
>
> Modified: branches/tc_url_rev/subversion/libsvn_wc/wc.h
> URL: http://svn.collab.net/viewvc/svn/branches/tc_url_rev/subversion/libsvn_wc/wc.h?pathrev=34332&r1=34331&r2=34332
> ==============================================================================
> --- branches/tc_url_rev/subversion/libsvn_wc/wc.h Fri Nov 21 23:41:51 2008 (r34331)
> +++ branches/tc_url_rev/subversion/libsvn_wc/wc.h Sat Nov 22 05:58:46 2008 (r34332)
> @@ -255,16 +255,24 @@ svn_wc__text_modified_internal_p(svn_boo
> conflict is encountered, giving the callback a chance to resolve
> the conflict (before marking the file 'conflicted').
>
> + When LEFT_VERSION and RIGHT_VERSION are non-NULL, pass them to the
> + conflict resolver as older_version and their_version.
> +
> + ## TODO: We should store the information in LEFT_VERSION and RIGHT_VERSION
> + in the workingcopy for future retrieval via svn info.
> +
> Property changes sent by the update are provided in PROP_DIFF.
>
> - For a complete description, see svn_wc_merge2() for which this is
> + For a complete description, see svn_wc_merge3() for which this is
> the (loggy) implementation.
> */
> svn_error_t *
> svn_wc__merge_internal(svn_stringbuf_t **log_accum,
> enum svn_wc_merge_outcome_t *merge_outcome,
> const char *left,
> + svn_wc_conflict_version_t *left_version,
> const char *right,
> + svn_wc_conflict_version_t *right_version,
> const char *merge_target,
> const char *copyfrom_text,
> svn_wc_adm_access_t *adm_access,
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: svn-unsubscribe_at_subversion.tigris.org
> For additional commands, e-mail: svn-help_at_subversion.tigris.org
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe_at_subversion.tigris.org
For additional commands, e-mail: dev-help_at_subversion.tigris.org
Received on 2008-11-22 15:11:01 CET