blob: 4dca3af7e13f356970a6977aa873febc18b21777 [file] [log] [blame]
/*
* update_editor.c : main editor for checkouts and updates
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <stdlib.h>
#include <string.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include <apr_md5.h>
#include <apr_tables.h>
#include <apr_strings.h>
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_hash.h"
#include "svn_string.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_private_config.h"
#include "svn_time.h"
#include "wc.h"
#include "adm_files.h"
#include "conflicts.h"
#include "translate.h"
#include "workqueue.h"
#include "private/svn_subr_private.h"
#include "private/svn_wc_private.h"
#include "private/svn_editor.h"
/* Checks whether a svn_wc__db_status_t indicates whether a node is
present in a working copy. Used by the editor implementation */
#define IS_NODE_PRESENT(status) \
((status) != svn_wc__db_status_server_excluded &&\
(status) != svn_wc__db_status_excluded && \
(status) != svn_wc__db_status_not_present)
static svn_error_t *
path_join_under_root(const char **result_path,
const char *base_path,
const char *add_path,
apr_pool_t *result_pool);
/*
* 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.
*/
/*** batons ***/
struct edit_baton
{
/* For updates, the "destination" of the edit is ANCHOR_ABSPATH, the
directory containing TARGET_ABSPATH. If ANCHOR_ABSPATH itself is the
target, the values are identical.
TARGET_BASENAME is the name of TARGET_ABSPATH in ANCHOR_ABSPATH, or "" if
ANCHOR_ABSPATH is the target */
const char *target_basename;
/* Absolute variants of ANCHOR and TARGET */
const char *anchor_abspath;
const char *target_abspath;
/* The DB handle for managing the working copy state. */
svn_wc__db_t *db;
/* Array of file extension patterns to preserve as extensions in
generated conflict files. */
const apr_array_header_t *ext_patterns;
/* Hash mapping const char * absolute working copy paths to depth-first
ordered arrays of svn_prop_inherited_item_t * structures representing
the properties inherited by the base node at that working copy path.
May be NULL. */
apr_hash_t *wcroot_iprops;
/* 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;
/* Handle local additions as modifications of new nodes */
svn_boolean_t adds_as_modification;
/* If set, we check out into an empty directory. This allows for a number
of conflict checks to be omitted. */
svn_boolean_t clean_checkout;
/* If this is a 'switch' operation, the new relpath of target_abspath,
else NULL. */
const char *switch_repos_relpath;
/* The URL to the root of the repository. */
const char *repos_root;
/* The UUID of the repos, or NULL. */
const char *repos_uuid;
/* External diff3 to use for merges (can be null, in which case
internal merge code is used). */
const char *diff3_cmd;
/* Externals handler */
svn_wc_external_update_t external_func;
void *external_baton;
/* 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_func2_t conflict_func;
void *conflict_baton;
/* Subtrees 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 paths
relative to the working copy root and the values unspecified. */
apr_hash_t *skipped_trees;
/* A mapping from const char * repos_relpaths to the apr_hash_t * instances
returned from fetch_dirents_func for that repos_relpath. These
are used to avoid issue #3569 in specific update scenarios where a
restricted depth is used. */
apr_hash_t *dir_dirents;
/* Absolute path of the working copy root or NULL if not initialized yet */
const char *wcroot_abspath;
/* After closing the root directory a copy of its edited value */
svn_boolean_t edited;
apr_pool_t *pool;
};
/* Record in the edit baton EB that LOCAL_ABSPATH's base version is not being
* updated.
*
* Add to EB->skipped_trees a copy (allocated in EB->pool) of the string
* LOCAL_ABSPATH.
*/
static svn_error_t *
remember_skipped_tree(struct edit_baton *eb,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
svn_hash_sets(eb->skipped_trees,
apr_pstrdup(eb->pool,
svn_dirent_skip_ancestor(eb->wcroot_abspath,
local_abspath)),
(void *)1);
return SVN_NO_ERROR;
}
/* Per directory baton. Lives in its own subpool of the parent directory
or of the edit baton if there is no parent directory */
struct dir_baton
{
/* Basename of this directory. */
const char *name;
/* Absolute path of this directory */
const char *local_abspath;
/* The repository relative path this directory will correspond to. */
const char *new_repos_relpath;
/* The revision of the directory before updating */
svn_revnum_t old_revision;
/* The repos_relpath before updating/switching */
const char *old_repos_relpath;
/* 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;
/* Set if updates to this directory are skipped */
svn_boolean_t skip_this;
/* Set if there was a previous notification for this directory */
svn_boolean_t already_notified;
/* Set if this directory is being added during this editor drive. */
svn_boolean_t adding_dir;
/* Set on a node and its descendants are not present in the working copy
but should still be updated (not skipped). These nodes should all be
marked as deleted. */
svn_boolean_t shadowed;
/* Set on a node when the existing node is obstructed, and the edit operation
continues as semi-shadowed update */
svn_boolean_t edit_obstructed;
/* The (new) changed_* information, cached to avoid retrieving it later */
svn_revnum_t changed_rev;
apr_time_t changed_date;
const char *changed_author;
/* If not NULL, contains a mapping of const char* basenames of children that
have been deleted to their svn_skel_t* tree conflicts.
We store this hash to allow replacements to continue under a just
installed tree conflict.
The add after the delete will then update the tree conflicts information
and reinstall it. */
apr_hash_t *deletion_conflicts;
/* A hash of file names (only the hash key matters) seen by add_file and
add_directory and not yet added to the database, mapping to a const
char * node kind (via svn_node_kind_to_word(). */
apr_hash_t *not_present_nodes;
/* Set if an unversioned dir of the same name already existed in
this directory. */
svn_boolean_t obstruction_found;
/* 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;
/* A boolean indicating whether this node or one of its children has
received any 'real' changes. Used to avoid tree conflicts for simple
entryprop changes, like lock management */
svn_boolean_t edited;
/* The tree conflict to install once the node is really edited */
svn_skel_t *edit_conflict;
/* The bump information for this directory. */
struct bump_dir_info *bump_info;
/* 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;
/* Was the directory marked as incomplete before the update?
(In other words, are we resuming an interrupted update?)
If WAS_INCOMPLETE is set to TRUE we expect to receive all child nodes
and properties for/of the directory. If WAS_INCOMPLETE is FALSE then
we only receive the changes in/for children and properties.*/
svn_boolean_t was_incomplete;
/* The pool in which this baton itself is allocated. */
apr_pool_t *pool;
/* how many nodes are referring to baton? */
int ref_count;
};
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. */
svn_wc__db_install_data_t *install_data;
/* The expected source checksum of the text source or NULL if no base
checksum is available (MD5 if the server provides a checksum, SHA1 if
the server doesn't) */
svn_checksum_t *expected_source_checksum;
/* Why two checksums?
The editor currently provides an md5 which we use to detect corruption
during transmission. We use the sha1 inside libsvn_wc both for pristine
handling and corruption detection. In the future, the editor will also
provide a sha1, so we may not have to calculate both, but for the time
being, that's the way it is. */
/* The calculated checksum of the text source or NULL if the actual
checksum is not being calculated. The checksum kind is identical to the
kind of expected_source_checksum. */
svn_checksum_t *actual_source_checksum;
/* The stream used to calculate the source checksums */
svn_stream_t *source_checksum_stream;
/* A calculated MD5 digest of NEW_TEXT_BASE_TMP_ABSPATH.
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 new_text_base_md5_digest[APR_MD5_DIGESTSIZE];
/* A calculated SHA-1 of NEW_TEXT_BASE_TMP_ABSPATH, which we'll use for
eventually writing the pristine. */
svn_checksum_t * new_text_base_sha1_checksum;
};
/* Get an empty file in the temporary area for WRI_ABSPATH. The file will
not be set for automatic deletion, and the name will be returned in
TMP_FILENAME.
This implementation creates a new empty file with a unique name.
### This is inefficient for callers that just want an empty file to read
### from. There could be (and there used to be) a permanent, shared
### empty file for this purpose.
### This is inefficient for callers that just want to reserve a unique
### file name to create later. A better way may not be readily available.
*/
static svn_error_t *
get_empty_tmp_file(const char **tmp_filename,
svn_wc__db_t *db,
const char *wri_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *temp_dir_abspath;
SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, wri_abspath,
scratch_pool, scratch_pool));
SVN_ERR(svn_io_open_unique_file3(NULL, tmp_filename, temp_dir_abspath,
svn_io_file_del_none,
scratch_pool, scratch_pool));
return SVN_NO_ERROR;
}
/* An APR pool cleanup handler. This runs the working queue for an
editor baton. */
static apr_status_t
cleanup_edit_baton(void *edit_baton)
{
struct edit_baton *eb = edit_baton;
svn_error_t *err;
apr_pool_t *pool = apr_pool_parent_get(eb->pool);
err = svn_wc__wq_run(eb->db, eb->wcroot_abspath,
NULL /* cancel_func */, NULL /* cancel_baton */,
pool);
if (err)
{
apr_status_t apr_err = err->apr_err;
svn_error_clear(err);
return apr_err;
}
return APR_SUCCESS;
}
/* Calculate the new repos_relpath for a directory or file */
static svn_error_t *
calculate_repos_relpath(const char **new_repos_relpath,
const char *local_abspath,
const char *old_repos_relpath,
struct edit_baton *eb,
struct dir_baton *pb,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *name = svn_dirent_basename(local_abspath, NULL);
/* Figure out the new_repos_relpath for this directory. */
if (eb->switch_repos_relpath)
{
/* Handle switches... */
if (pb == NULL)
{
if (*eb->target_basename == '\0')
{
/* No parent baton and target_basename=="" means that we are
the target of the switch. Thus, our new_repos_relpath will be
the switch_repos_relpath. */
*new_repos_relpath = eb->switch_repos_relpath;
}
else
{
/* This node is NOT the target of the switch (one of our
children is the target); therefore, it must already exist.
Get its old REPOS_RELPATH, as it won't be changing. */
*new_repos_relpath = apr_pstrdup(result_pool, old_repos_relpath);
}
}
else
{
/* This directory is *not* the root (has a parent). If there is
no grandparent, then we may have anchored at the parent,
and self is the target. If we match the target, then set
new_repos_relpath to the switch_repos_relpath.
Otherwise, we simply extend new_repos_relpath from the parent. */
if (pb->parent_baton == NULL
&& strcmp(eb->target_basename, name) == 0)
*new_repos_relpath = eb->switch_repos_relpath;
else
*new_repos_relpath = svn_relpath_join(pb->new_repos_relpath, name,
result_pool);
}
}
else /* must be an update */
{
/* If we are adding the node, then simply extend the parent's
relpath for our own. */
if (old_repos_relpath == NULL)
{
SVN_ERR_ASSERT(pb != NULL);
*new_repos_relpath = svn_relpath_join(pb->new_repos_relpath, name,
result_pool);
}
else
{
*new_repos_relpath = apr_pstrdup(result_pool, old_repos_relpath);
}
}
return SVN_NO_ERROR;
}
/* Make a new dir baton in a subpool of PB->pool. PB is the parent baton.
If PATH and PB are NULL, this is the root directory of the edit; in this
case, make the new dir baton in a subpool of EB->pool.
ADDING should be TRUE if we are adding this directory. */
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 adding,
apr_pool_t *scratch_pool)
{
apr_pool_t *dir_pool;
struct dir_baton *d;
if (pb != NULL)
dir_pool = svn_pool_create(pb->pool);
else
dir_pool = svn_pool_create(eb->pool);
SVN_ERR_ASSERT(path || (! pb));
/* Okay, no easy out, so allocate and initialize a dir baton. */
d = apr_pcalloc(dir_pool, sizeof(*d));
/* Construct the PATH and baseNAME of this directory. */
if (path)
{
d->name = svn_dirent_basename(path, dir_pool);
SVN_ERR(path_join_under_root(&d->local_abspath,
pb->local_abspath, d->name, dir_pool));
}
else
{
/* This is the root baton. */
d->name = NULL;
d->local_abspath = eb->anchor_abspath;
}
d->edit_baton = eb;
d->parent_baton = pb;
d->pool = dir_pool;
d->propchanges = apr_array_make(dir_pool, 1, sizeof(svn_prop_t));
d->obstruction_found = FALSE;
d->add_existed = FALSE;
d->ref_count = 1;
d->old_revision = SVN_INVALID_REVNUM;
d->adding_dir = adding;
d->changed_rev = SVN_INVALID_REVNUM;
d->not_present_nodes = apr_hash_make(dir_pool);
/* Copy some flags from the parent baton */
if (pb)
{
d->skip_this = pb->skip_this;
d->shadowed = pb->shadowed || pb->edit_obstructed;
/* the parent's bump info has one more referer */
pb->ref_count++;
}
/* The caller of this function needs to fill these in. */
d->ambient_depth = svn_depth_unknown;
d->was_incomplete = FALSE;
*d_p = d;
return SVN_NO_ERROR;
}
/* Forward declarations. */
static svn_error_t *
already_in_a_tree_conflict(svn_boolean_t *conflicted,
svn_boolean_t *ignored,
svn_wc__db_t *db,
const char *local_abspath,
apr_pool_t *scratch_pool);
static void
do_notification(const struct edit_baton *eb,
const char *local_abspath,
svn_node_kind_t kind,
svn_wc_notify_action_t action,
apr_pool_t *scratch_pool)
{
svn_wc_notify_t *notify;
if (eb->notify_func == NULL)
return;
notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
notify->kind = kind;
(*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
}
/* Decrement the directory's reference count. If it hits zero,
then this directory is "done". This means it is safe to clear its pool.
In addition, when the directory is "done", we recurse to possible cleanup
the parent directory.
*/
static svn_error_t *
maybe_release_dir_info(struct dir_baton *db)
{
db->ref_count--;
if (!db->ref_count)
{
struct dir_baton *pb = db->parent_baton;
svn_pool_destroy(db->pool);
if (pb)
SVN_ERR(maybe_release_dir_info(pb));
}
return SVN_NO_ERROR;
}
/* Per file baton. Lives in its own subpool below the pool of the parent
directory */
struct file_baton
{
/* Pool specific to this file_baton. */
apr_pool_t *pool;
/* Name of this file (its entry in the directory). */
const char *name;
/* Absolute path to this file */
const char *local_abspath;
/* The repository relative path this file will correspond to. */
const char *new_repos_relpath;
/* The revision of the file before updating */
svn_revnum_t old_revision;
/* The repos_relpath before updating/switching */
const char *old_repos_relpath;
/* The global edit baton. */
struct edit_baton *edit_baton;
/* The parent directory of this file. */
struct dir_baton *dir_baton;
/* Set if updates to this directory are skipped */
svn_boolean_t skip_this;
/* Set if there was a previous notification */
svn_boolean_t already_notified;
/* Set if this file is new. */
svn_boolean_t adding_file;
/* Set if an unversioned file of the same name already existed in
this directory. */
svn_boolean_t obstruction_found;
/* Set if a file of the same name already exists and is
scheduled for addition without history. */
svn_boolean_t add_existed;
/* Set if this file is being added in the BASE layer, but is not-present
in the working copy (replaced, deleted, etc.). */
svn_boolean_t shadowed;
/* Set on a node when the existing node is obstructed, and the edit operation
continues as semi-shadowed update */
svn_boolean_t edit_obstructed;
/* The (new) changed_* information, cached to avoid retrieving it later */
svn_revnum_t changed_rev;
apr_time_t changed_date;
const char *changed_author;
/* If there are file content changes, these are the checksums of the
resulting new text base, which is in the pristine store, else NULL. */
const svn_checksum_t *new_text_base_md5_checksum;
const svn_checksum_t *new_text_base_sha1_checksum;
/* The checksum of the file before the update */
const svn_checksum_t *original_checksum;
/* 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;
/* For existing files, whether there are local modifications. FALSE for added
files */
svn_boolean_t local_prop_mods;
/* Bump information for the directory this file lives in */
struct bump_dir_info *bump_info;
/* A boolean indicating whether this node or one of its children has
received any 'real' changes. Used to avoid tree conflicts for simple
entryprop changes, like lock management */
svn_boolean_t edited;
/* The tree conflict to install once the node is really edited */
svn_skel_t *edit_conflict;
};
/* Make a new file baton in a subpool of PB->pool. PB is the parent baton.
* PATH is relative to the root of the edit. ADDING tells whether this file
* is being added. */
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 *scratch_pool)
{
apr_pool_t *file_pool = svn_pool_create(pb->pool);
struct file_baton *f = apr_pcalloc(file_pool, sizeof(*f));
SVN_ERR_ASSERT(path);
/* Make the file's on-disk name. */
f->name = svn_dirent_basename(path, file_pool);
f->old_revision = SVN_INVALID_REVNUM;
SVN_ERR(path_join_under_root(&f->local_abspath,
pb->local_abspath, f->name, file_pool));
f->pool = file_pool;
f->edit_baton = pb->edit_baton;
f->propchanges = apr_array_make(file_pool, 1, sizeof(svn_prop_t));
f->bump_info = pb->bump_info;
f->adding_file = adding;
f->obstruction_found = FALSE;
f->add_existed = FALSE;
f->skip_this = pb->skip_this;
f->shadowed = pb->shadowed || pb->edit_obstructed;
f->dir_baton = pb;
f->changed_rev = SVN_INVALID_REVNUM;
/* the directory has one more referer now */
pb->ref_count++;
*f_p = f;
return SVN_NO_ERROR;
}
/* Complete a conflict skel by describing the update.
*
* LOCAL_KIND is the node kind of the tree conflict victim in the
* working copy.
*
* All temporary allocations are be made in SCRATCH_POOL, while allocations
* needed for the returned conflict struct are made in RESULT_POOL.
*/
static svn_error_t *
complete_conflict(svn_skel_t *conflict,
const struct edit_baton *eb,
const char *local_abspath,
const char *old_repos_relpath,
svn_revnum_t old_revision,
const char *new_repos_relpath,
svn_node_kind_t local_kind,
svn_node_kind_t target_kind,
const svn_skel_t *delete_conflict,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const svn_wc_conflict_version_t *original_version = NULL;
svn_wc_conflict_version_t *target_version;
svn_boolean_t is_complete;
SVN_ERR_ASSERT(new_repos_relpath);
if (!conflict)
return SVN_NO_ERROR; /* Not conflicted */
SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict));
if (is_complete)
return SVN_NO_ERROR; /* Already completed */
if (old_repos_relpath)
original_version = svn_wc_conflict_version_create2(eb->repos_root,
eb->repos_uuid,
old_repos_relpath,
old_revision,
local_kind,
result_pool);
else if (delete_conflict)
{
const apr_array_header_t *locations;
SVN_ERR(svn_wc__conflict_read_info(NULL, &locations, NULL, NULL, NULL,
eb->db, local_abspath,
delete_conflict,
scratch_pool, scratch_pool));
if (locations)
{
original_version = APR_ARRAY_IDX(locations, 0,
const svn_wc_conflict_version_t *);
}
}
target_version = svn_wc_conflict_version_create2(eb->repos_root,
eb->repos_uuid,
new_repos_relpath,
*eb->target_revision,
target_kind,
result_pool);
if (eb->switch_repos_relpath)
SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict,
original_version,
target_version,
result_pool, scratch_pool));
else
SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict,
original_version,
target_version,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
/* Called when a directory is really edited, to avoid marking a
tree conflict on a node for a no-change edit */
static svn_error_t *
mark_directory_edited(struct dir_baton *db, apr_pool_t *scratch_pool)
{
if (db->edited)
return SVN_NO_ERROR;
if (db->parent_baton)
SVN_ERR(mark_directory_edited(db->parent_baton, scratch_pool));
db->edited = TRUE;
if (db->edit_conflict)
{
/* We have a (delayed) tree conflict to install */
SVN_ERR(complete_conflict(db->edit_conflict, db->edit_baton,
db->local_abspath,
db->old_repos_relpath, db->old_revision,
db->new_repos_relpath,
svn_node_dir, svn_node_dir,
NULL,
db->pool, scratch_pool));
SVN_ERR(svn_wc__db_op_mark_conflict(db->edit_baton->db,
db->local_abspath,
db->edit_conflict, NULL,
scratch_pool));
do_notification(db->edit_baton, db->local_abspath, svn_node_dir,
svn_wc_notify_tree_conflict, scratch_pool);
db->already_notified = TRUE;
}
return SVN_NO_ERROR;
}
/* Called when a file is really edited, to avoid marking a
tree conflict on a node for a no-change edit */
static svn_error_t *
mark_file_edited(struct file_baton *fb, apr_pool_t *scratch_pool)
{
if (fb->edited)
return SVN_NO_ERROR;
SVN_ERR(mark_directory_edited(fb->dir_baton, scratch_pool));
fb->edited = TRUE;
if (fb->edit_conflict)
{
/* We have a (delayed) tree conflict to install */
SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton,
fb->local_abspath, fb->old_repos_relpath,
fb->old_revision, fb->new_repos_relpath,
svn_node_file, svn_node_file,
NULL,
fb->pool, scratch_pool));
SVN_ERR(svn_wc__db_op_mark_conflict(fb->edit_baton->db,
fb->local_abspath,
fb->edit_conflict, NULL,
scratch_pool));
do_notification(fb->edit_baton, fb->local_abspath, svn_node_file,
svn_wc_notify_tree_conflict, scratch_pool);
fb->already_notified = TRUE;
}
return SVN_NO_ERROR;
}
/* Handle the next delta window of the file described by BATON. If it is
* the end (WINDOW == NULL), then check the checksum, store the text in the
* pristine store and write its details into BATON->fb->new_text_base_*. */
static svn_error_t *
window_handler(svn_txdelta_window_t *window, void *baton)
{
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 (hb->expected_source_checksum)
{
/* Close the stream to calculate HB->actual_source_md5_checksum. */
svn_error_t *err2 = svn_stream_close(hb->source_checksum_stream);
if (!err2)
{
SVN_ERR_ASSERT(hb->expected_source_checksum->kind ==
hb->actual_source_checksum->kind);
if (!svn_checksum_match(hb->expected_source_checksum,
hb->actual_source_checksum))
{
err = svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, err,
_("Checksum mismatch while updating '%s':\n"
" expected: %s\n"
" actual: %s\n"),
svn_dirent_local_style(fb->local_abspath, hb->pool),
svn_checksum_to_cstring(hb->expected_source_checksum,
hb->pool),
svn_checksum_to_cstring(hb->actual_source_checksum,
hb->pool));
}
}
err = svn_error_compose_create(err, err2);
}
if (err)
{
/* We failed to apply the delta; clean up the temporary file if it
already created by lazy_open_target(). */
if (hb->install_data)
{
svn_error_clear(svn_wc__db_pristine_install_abort(hb->install_data,
hb->pool));
}
}
else
{
/* Tell the file baton about the new text base's checksums. */
fb->new_text_base_md5_checksum =
svn_checksum__from_digest_md5(hb->new_text_base_md5_digest, fb->pool);
fb->new_text_base_sha1_checksum =
svn_checksum_dup(hb->new_text_base_sha1_checksum, fb->pool);
/* Store the new pristine text in the pristine store now. Later, in a
single transaction we will update the BASE_NODE to include a
reference to this pristine text's checksum. */
SVN_ERR(svn_wc__db_pristine_install(hb->install_data,
fb->new_text_base_sha1_checksum,
fb->new_text_base_md5_checksum,
hb->pool));
}
svn_pool_destroy(hb->pool);
return err;
}
/* Find the last-change info within ENTRY_PROPS, and return then in the
CHANGED_* parameters. Each parameter will be initialized to its "none"
value, and will contain the relavent info if found.
CHANGED_AUTHOR will be allocated in RESULT_POOL. SCRATCH_POOL will be
used for some temporary allocations.
*/
static svn_error_t *
accumulate_last_change(svn_revnum_t *changed_rev,
apr_time_t *changed_date,
const char **changed_author,
const apr_array_header_t *entry_props,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
int i;
*changed_rev = SVN_INVALID_REVNUM;
*changed_date = 0;
*changed_author = NULL;
for (i = 0; i < entry_props->nelts; ++i)
{
const svn_prop_t *prop = &APR_ARRAY_IDX(entry_props, i, svn_prop_t);
/* 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;
if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
*changed_author = apr_pstrdup(result_pool, prop->value->data);
else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
{
apr_int64_t rev;
SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
*changed_rev = (svn_revnum_t)rev;
}
else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
SVN_ERR(svn_time_from_cstring(changed_date, prop->value->data,
scratch_pool));
/* Starting with Subversion 1.7 we ignore the SVN_PROP_ENTRY_UUID
property here. */
}
return SVN_NO_ERROR;
}
/* Join ADD_PATH to BASE_PATH. If ADD_PATH is absolute, or if any ".."
* component of it resolves to a path above BASE_PATH, then return
* SVN_ERR_WC_OBSTRUCTED_UPDATE.
*
* 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 *
path_join_under_root(const char **result_path,
const char *base_path,
const char *add_path,
apr_pool_t *pool)
{
svn_boolean_t under_root;
SVN_ERR(svn_dirent_is_under_root(&under_root,
result_path, base_path, add_path, pool));
if (! under_root)
{
return svn_error_createf(
SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("Path '%s' is not in the working copy"),
svn_dirent_local_style(svn_dirent_join(base_path, add_path, pool),
pool));
}
/* This catches issue #3288 */
if (strcmp(add_path, svn_dirent_basename(*result_path, NULL)) != 0)
{
return svn_error_createf(
SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("'%s' is not valid as filename in directory '%s'"),
svn_dirent_local_style(add_path, pool),
svn_dirent_local_style(base_path, 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;
*(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 *db;
svn_boolean_t already_conflicted, conflict_ignored;
svn_error_t *err;
svn_wc__db_status_t status;
svn_wc__db_status_t base_status;
svn_node_kind_t kind;
svn_boolean_t have_work;
/* Note that something interesting is actually happening in this
edit run. */
eb->root_opened = TRUE;
SVN_ERR(make_dir_baton(&db, NULL, eb, NULL, FALSE, pool));
*dir_baton = db;
err = already_in_a_tree_conflict(&already_conflicted, &conflict_ignored,
eb->db, db->local_abspath, pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
svn_error_clear(err);
already_conflicted = conflict_ignored = FALSE;
}
else if (already_conflicted)
{
/* Record a skip of both the anchor and target in the skipped tree
as the anchor itself might not be updated */
SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
SVN_ERR(remember_skipped_tree(eb, eb->target_abspath, pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
/* Notify that we skipped the target, while we actually skipped
the anchor */
do_notification(eb, eb->target_abspath, svn_node_unknown,
svn_wc_notify_skip_conflicted, pool);
return SVN_NO_ERROR;
}
SVN_ERR(svn_wc__db_read_info(&status, &kind, &db->old_revision,
&db->old_repos_relpath, NULL, NULL,
&db->changed_rev, &db->changed_date,
&db->changed_author, &db->ambient_depth,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, &have_work,
eb->db, db->local_abspath,
db->pool, pool));
if (have_work)
{
SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL,
&db->old_revision,
&db->old_repos_relpath, NULL, NULL,
&db->changed_rev, &db->changed_date,
&db->changed_author,
&db->ambient_depth,
NULL, NULL, NULL, NULL, NULL, NULL,
eb->db, db->local_abspath,
db->pool, pool));
}
else
base_status = status;
SVN_ERR(calculate_repos_relpath(&db->new_repos_relpath, db->local_abspath,
db->old_repos_relpath, eb, NULL,
db->pool, pool));
if (conflict_ignored)
db->shadowed = TRUE;
else if (have_work)
{
const char *move_src_root_abspath;
SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, &move_src_root_abspath,
NULL, eb->db, db->local_abspath,
pool, pool));
if (move_src_root_abspath)
{
/* This is an update anchored inside a move. We need to
raise a move-edit tree-conflict on the move root to
update the move destination. */
svn_skel_t *tree_conflict = svn_wc__conflict_skel_create(pool);
SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
tree_conflict, eb->db, move_src_root_abspath,
svn_wc_conflict_reason_moved_away,
svn_wc_conflict_action_edit,
move_src_root_abspath, pool, pool));
if (strcmp(db->local_abspath, move_src_root_abspath))
{
/* We are raising the tree-conflict on some parent of
the edit root, we won't be handling that path again
so raise the conflict now. */
SVN_ERR(complete_conflict(tree_conflict, eb,
move_src_root_abspath,
db->old_repos_relpath,
db->old_revision,
db->new_repos_relpath,
svn_node_dir, svn_node_dir,
NULL, pool, pool));
SVN_ERR(svn_wc__db_op_mark_conflict(eb->db,
move_src_root_abspath,
tree_conflict,
NULL, pool));
do_notification(eb, move_src_root_abspath, svn_node_dir,
svn_wc_notify_tree_conflict, pool);
}
else
db->edit_conflict = tree_conflict;
}
db->shadowed = TRUE; /* Needed for the close_directory() on the root, to
make sure it doesn't use the ACTUAL tree */
}
if (*eb->target_basename == '\0')
{
/* For an update with a NULL target, this is equivalent to open_dir(): */
db->was_incomplete = (base_status == svn_wc__db_status_incomplete);
/* ### TODO: Add some tree conflict and obstruction detection, etc. like
open_directory() does.
(or find a way to reuse that code here)
### BH 2013: I don't think we need all of the detection here, as the
user explicitly asked to update this node. So we don't
have to tell that it is a local replacement/delete.
*/
SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db,
db->local_abspath,
db->new_repos_relpath,
*eb->target_revision,
pool));
}
return SVN_NO_ERROR;
}
/* ===================================================================== */
/* Checking for local modifications. */
/* Indicates an unset svn_wc_conflict_reason_t. */
#define SVN_WC_CONFLICT_REASON_NONE (svn_wc_conflict_reason_t)(-1)
/* Check whether the incoming change ACTION on FULL_PATH would conflict with
* LOCAL_ABSPATH's scheduled change. If so, then raise a tree conflict with
* LOCAL_ABSPATH as the victim.
*
* The edit baton EB gives information including whether the operation is
* an update or a switch.
*
* WORKING_STATUS is the current node status of LOCAL_ABSPATH
* and EXISTS_IN_REPOS specifies whether a BASE_NODE representation for exists
* for this node. In that case the on disk type is compared to EXPECTED_KIND.
*
* If a tree conflict reason was found for the incoming action, the resulting
* tree conflict info is returned in *PCONFLICT. PCONFLICT must be non-NULL,
* while *PCONFLICT is always overwritten.
*
* The tree conflict is allocated in RESULT_POOL. Temporary allocations use
* SCRATCH_POOL.
*/
static svn_error_t *
check_tree_conflict(svn_skel_t **pconflict,
struct edit_baton *eb,
const char *local_abspath,
svn_wc__db_status_t working_status,
svn_boolean_t exists_in_repos,
svn_node_kind_t expected_kind,
svn_wc_conflict_action_t action,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc_conflict_reason_t reason = SVN_WC_CONFLICT_REASON_NONE;
svn_boolean_t modified = FALSE;
const char *move_src_op_root_abspath = NULL;
*pconflict = NULL;
/* Find out if there are any local changes to this node that may
* be the "reason" of a tree-conflict with the incoming "action". */
switch (working_status)
{
case svn_wc__db_status_added:
case svn_wc__db_status_moved_here:
case svn_wc__db_status_copied:
if (!exists_in_repos)
{
/* The node is locally added, and it did not exist before. This
* is an 'update', so the local add can only conflict with an
* incoming 'add'. In fact, if we receive anything else than an
* svn_wc_conflict_action_add (which includes 'added',
* 'copied-here' and 'moved-here') during update on a node that
* did not exist before, then something is very wrong.
* Note that if there was no action on the node, this code
* would not have been called in the first place. */
SVN_ERR_ASSERT(action == svn_wc_conflict_action_add);
/* Scan the addition in case our caller didn't. */
if (working_status == svn_wc__db_status_added)
SVN_ERR(svn_wc__db_scan_addition(&working_status, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL,
eb->db, local_abspath,
scratch_pool, scratch_pool));
if (working_status == svn_wc__db_status_moved_here)
reason = svn_wc_conflict_reason_moved_here;
else
reason = svn_wc_conflict_reason_added;
}
else
{
/* The node is locally replaced but could also be moved-away,
but we can't report that it is moved away and replaced.
And we wouldn't be able to store that each of a dozen
descendants was moved to other locations...
Replaced is what actually happened... */
reason = svn_wc_conflict_reason_replaced;
}
break;
case svn_wc__db_status_deleted:
{
SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL,
&move_src_op_root_abspath,
eb->db, local_abspath,
scratch_pool, scratch_pool));
if (move_src_op_root_abspath)
reason = svn_wc_conflict_reason_moved_away;
else
reason = svn_wc_conflict_reason_deleted;
}
break;
case svn_wc__db_status_incomplete:
/* We used svn_wc__db_read_info(), so 'incomplete' means
* - there is no node in the WORKING tree
* - a BASE node is known to exist
* So the node exists and is essentially 'normal'. We still need to
* check prop and text mods, and those checks will retrieve the
* missing information (hopefully). */
case svn_wc__db_status_normal:
if (action == svn_wc_conflict_action_edit)
{
/* An edit onto a local edit or onto *no* local changes is no
* tree-conflict. (It's possibly a text- or prop-conflict,
* but we don't handle those here.)
*
* Except when there is a local obstruction
*/
if (exists_in_repos)
{
svn_node_kind_t disk_kind;
SVN_ERR(svn_io_check_path(local_abspath, &disk_kind,
scratch_pool));
if (disk_kind != expected_kind && disk_kind != svn_node_none)
{
reason = svn_wc_conflict_reason_obstructed;
break;
}
}
return SVN_NO_ERROR;
}
/* Replace is handled as delete and then specifically in
add_directory() and add_file(), so we only expect deletes here */
SVN_ERR_ASSERT(action == svn_wc_conflict_action_delete);
/* Check if the update wants to delete or replace a locally
* modified node. */
/* Do a deep tree detection of local changes. 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__node_has_local_mods(&modified, NULL,
eb->db, local_abspath, FALSE,
eb->cancel_func, eb->cancel_baton,
scratch_pool));
if (modified)
{
if (working_status == svn_wc__db_status_deleted)
reason = svn_wc_conflict_reason_deleted;
else
reason = svn_wc_conflict_reason_edited;
}
break;
case svn_wc__db_status_server_excluded:
/* Not allowed to view the node. Not allowed to report tree
* conflicts. */
case svn_wc__db_status_excluded:
/* Locally marked as excluded. No conflicts wanted. */
case svn_wc__db_status_not_present:
/* A committed delete (but parent not updated). The delete is
committed, so no conflict possible during update. */
return SVN_NO_ERROR;
case svn_wc__db_status_base_deleted:
/* An internal status. Should never show up here. */
SVN_ERR_MALFUNCTION();
break;
}
if (reason == SVN_WC_CONFLICT_REASON_NONE)
/* No conflict with the current action. */
return SVN_NO_ERROR;
/* Sanity checks. Note that if there was no action on the node, this function
* would not have been called in the first place.*/
if (reason == svn_wc_conflict_reason_edited
|| reason == svn_wc_conflict_reason_obstructed
|| reason == svn_wc_conflict_reason_deleted
|| reason == svn_wc_conflict_reason_moved_away
|| reason == svn_wc_conflict_reason_replaced)
{
/* When the node existed before (it was locally deleted, replaced or
* edited), then 'update' cannot add it "again". So it can only send
* _action_edit, _delete or _replace. */
if (action != svn_wc_conflict_action_edit
&& action != svn_wc_conflict_action_delete
&& action != svn_wc_conflict_action_replace)
return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
_("Unexpected attempt to add a node at path '%s'"),
svn_dirent_local_style(local_abspath, scratch_pool));
}
else if (reason == svn_wc_conflict_reason_added ||
reason == svn_wc_conflict_reason_moved_here)
{
/* When the node did not exist before (it was locally added),
* then 'update' cannot want to modify it in any way.
* It can only send _action_add. */
if (action != svn_wc_conflict_action_add)
return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
_("Unexpected attempt to edit, delete, or replace "
"a node at path '%s'"),
svn_dirent_local_style(local_abspath, scratch_pool));
}
/* A conflict was detected. Create a conflict skel to record it. */
*pconflict = svn_wc__conflict_skel_create(result_pool);
SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(*pconflict,
eb->db, local_abspath,
reason,
action,
move_src_op_root_abspath,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
/* If LOCAL_ABSPATH is inside a conflicted tree and the conflict is
* not a moved-away-edit conflict, set *CONFLICTED to TRUE. Otherwise
* set *CONFLICTED to FALSE.
*/
static svn_error_t *
already_in_a_tree_conflict(svn_boolean_t *conflicted,
svn_boolean_t *ignored,
svn_wc__db_t *db,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
const char *ancestor_abspath = local_abspath;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
*conflicted = *ignored = FALSE;
while (TRUE)
{
svn_boolean_t is_wc_root;
svn_pool_clear(iterpool);
SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, ignored, db,
ancestor_abspath, TRUE,
scratch_pool));
if (*conflicted || *ignored)
break;
SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, ancestor_abspath,
iterpool));
if (is_wc_root)
break;
ancestor_abspath = svn_dirent_dirname(ancestor_abspath, scratch_pool);
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
/* Temporary helper until the new conflict handling is in place */
static svn_error_t *
node_already_conflicted(svn_boolean_t *conflicted,
svn_boolean_t *conflict_ignored,
svn_wc__db_t *db,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, conflict_ignored, db,
local_abspath, FALSE,
scratch_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;
struct edit_baton *eb = pb->edit_baton;
const char *base = svn_relpath_basename(path, NULL);
const char *local_abspath;
const char *repos_relpath;
const char *deleted_repos_relpath;
svn_node_kind_t kind;
svn_revnum_t old_revision;
svn_boolean_t conflicted;
svn_boolean_t have_work;
svn_skel_t *tree_conflict = NULL;
svn_wc__db_status_t status;
svn_wc__db_status_t base_status;
apr_pool_t *scratch_pool;
svn_boolean_t deleting_target;
svn_boolean_t deleting_switched;
if (pb->skip_this)
return SVN_NO_ERROR;
scratch_pool = svn_pool_create(pb->pool);
SVN_ERR(mark_directory_edited(pb, scratch_pool));
SVN_ERR(path_join_under_root(&local_abspath, pb->local_abspath, base,
scratch_pool));
deleting_target = (strcmp(local_abspath, eb->target_abspath) == 0);
/* Detect obstructing working copies */
{
svn_boolean_t is_root;
SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, local_abspath,
scratch_pool));
if (is_root)
{
/* Just skip this node; a future update will handle it */
SVN_ERR(remember_skipped_tree(eb, local_abspath, pool));
do_notification(eb, local_abspath, svn_node_unknown,
svn_wc_notify_update_skip_obstruction, scratch_pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
}
SVN_ERR(svn_wc__db_read_info(&status, &kind, &old_revision, &repos_relpath,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
&conflicted, NULL, NULL, NULL,
NULL, NULL, &have_work,
eb->db, local_abspath,
scratch_pool, scratch_pool));
if (!have_work)
{
base_status = status;
}
else
SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &old_revision,
&repos_relpath,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
eb->db, local_abspath,
scratch_pool, scratch_pool));
if (pb->old_repos_relpath && repos_relpath)
{
const char *expected_name;
expected_name = svn_relpath_skip_ancestor(pb->old_repos_relpath,
repos_relpath);
deleting_switched = (!expected_name || strcmp(expected_name, base) != 0);
}
else
deleting_switched = FALSE;
/* Is this path a conflict victim? */
if (pb->shadowed)
conflicted = FALSE; /* Conflict applies to WORKING */
else if (conflicted)
SVN_ERR(node_already_conflicted(&conflicted, NULL,
eb->db, local_abspath, scratch_pool));
if (conflicted)
{
SVN_ERR(remember_skipped_tree(eb, local_abspath, scratch_pool));
do_notification(eb, local_abspath, svn_node_unknown,
svn_wc_notify_skip_conflicted,
scratch_pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
/* Receive the remote removal of excluded/server-excluded/not present node.
Do not notify, but perform the change even when the node is shadowed */
if (base_status == svn_wc__db_status_not_present
|| base_status == svn_wc__db_status_excluded
|| base_status == svn_wc__db_status_server_excluded)
{
SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, TRUE,
deleting_target, FALSE,
*eb->target_revision,
NULL, NULL,
scratch_pool));
if (deleting_target)
eb->target_deleted = TRUE;
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
/* Is this path the victim of a newly-discovered tree conflict? If so,
* remember it and notify the client. Then (if it was existing and
* modified), re-schedule the node to be added back again, as a (modified)
* copy of the previous base version. */
/* Check for conflicts only when we haven't already recorded
* a tree-conflict on a parent node. */
if (!pb->shadowed && !pb->edit_obstructed)
{
SVN_ERR(check_tree_conflict(&tree_conflict, eb, local_abspath,
status, TRUE,
kind,
svn_wc_conflict_action_delete,
pb->pool, scratch_pool));
}
if (tree_conflict != NULL)
{
/* When we raise a tree conflict on a node, we don't want to mark the
* node as skipped, to allow a replacement to continue doing at least
* a bit of its work (possibly adding a not present node, for the
* next update) */
if (!pb->deletion_conflicts)
pb->deletion_conflicts = apr_hash_make(pb->pool);
svn_hash_sets(pb->deletion_conflicts, apr_pstrdup(pb->pool, base),
tree_conflict);
/* Whatever the kind of conflict, we can just clear BASE
by turning whatever is there into a copy */
}
/* Calculate the repository-relative path of the entry which was
* deleted. For updates it's the same as REPOS_RELPATH but for
* switches it is within the switch target. */
SVN_ERR(calculate_repos_relpath(&deleted_repos_relpath, local_abspath,
repos_relpath, eb, pb, scratch_pool,
scratch_pool));
SVN_ERR(complete_conflict(tree_conflict, eb, local_abspath, repos_relpath,
old_revision, deleted_repos_relpath,
kind, svn_node_none, NULL,
pb->pool, scratch_pool));
/* Issue a wq operation to delete the BASE_NODE data and to delete actual
nodes based on that from disk, but leave any WORKING_NODEs on disk.
Local modifications are already turned into copies at this point.
If the thing being deleted is the *target* of this update, then
we need to recreate a 'deleted' entry, so that the parent can give
accurate reports about itself in the future. */
if (! deleting_target && ! deleting_switched)
{
/* Delete, and do not leave a not-present node. */
SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
(tree_conflict != NULL),
FALSE, FALSE,
SVN_INVALID_REVNUM /* not_present_rev */,
tree_conflict, NULL,
scratch_pool));
}
else
{
/* Delete, leaving a not-present node. */
SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath,
(tree_conflict != NULL),
TRUE, FALSE,
*eb->target_revision,
tree_conflict, NULL,
scratch_pool));
if (deleting_target)
eb->target_deleted = TRUE;
else
{
/* Don't remove the not-present marker at the final bump */
SVN_ERR(remember_skipped_tree(eb, local_abspath, pool));
}
}
SVN_ERR(svn_wc__wq_run(eb->db, pb->local_abspath,
eb->cancel_func, eb->cancel_baton,
scratch_pool));
/* Notify. */
if (tree_conflict)
{
if (eb->conflict_func)
SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, local_abspath,
kind,
tree_conflict,
NULL /* merge_options */,
eb->conflict_func,
eb->conflict_baton,
eb->cancel_func,
eb->cancel_baton,
scratch_pool));
do_notification(eb, local_abspath, kind,
svn_wc_notify_tree_conflict, scratch_pool);
}
else
{
svn_wc_notify_action_t action = svn_wc_notify_update_delete;
if (pb->shadowed || pb->edit_obstructed)
action = svn_wc_notify_update_shadowed_delete;
do_notification(eb, local_abspath, kind, action, scratch_pool);
}
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
add_directory(const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_rev,
apr_pool_t *pool,
void **child_baton)
{
struct dir_baton *pb = parent_baton;
struct edit_baton *eb = pb->edit_baton;
struct dir_baton *db;
apr_pool_t *scratch_pool = svn_pool_create(pool);
svn_node_kind_t kind;
svn_wc__db_status_t status;
svn_node_kind_t wc_kind;
svn_boolean_t conflicted;
svn_boolean_t conflict_ignored = FALSE;
svn_boolean_t versioned_locally_and_present;
svn_skel_t *tree_conflict = NULL;
svn_error_t *err;
SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev)));
SVN_ERR(make_dir_baton(&db, path, eb, pb, TRUE, pool));
*child_baton = db;
if (db->skip_this)
return SVN_NO_ERROR;
SVN_ERR(calculate_repos_relpath(&db->new_repos_relpath, db->local_abspath,
NULL, eb, pb, db->pool, scratch_pool));
SVN_ERR(mark_directory_edited(db, pool));
if (strcmp(eb->target_abspath, db->local_abspath) == 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;
}
/* It may not be named the same as the administrative directory. */
if (svn_wc_is_adm_dir(db->name, 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_dirent_local_style(db->local_abspath, pool));
if (!eb->clean_checkout)
{
SVN_ERR(svn_io_check_path(db->local_abspath, &kind, db->pool));
err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
&conflicted, NULL, NULL, NULL, NULL, NULL, NULL,
eb->db, db->local_abspath,
scratch_pool, scratch_pool);
}
else
{
kind = svn_node_none;
status = svn_wc__db_status_not_present;
wc_kind = svn_node_unknown;
conflicted = FALSE;
err = NULL;
}
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
svn_error_clear(err);
wc_kind = svn_node_unknown;
status = svn_wc__db_status_normal;
conflicted = FALSE;
versioned_locally_and_present = FALSE;
}
else if (status == svn_wc__db_status_normal && wc_kind == svn_node_unknown)
{
SVN_ERR_ASSERT(conflicted);
versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */
}
else if (status == svn_wc__db_status_normal
|| status == svn_wc__db_status_incomplete)
{
svn_boolean_t root;
SVN_ERR(svn_wc__db_is_wcroot(&root, eb->db, db->local_abspath,
scratch_pool));
if (root)
{
/* !! We found the root of a working copy obstructing the wc !!
If the directory would be part of our own working copy then
we wouldn't have been called as an add_directory().
The only thing we can do is add a not-present node, to allow
a future update to bring in the new files when the problem is
resolved. Note that svn_wc__db_base_add_not_present_node()
explicitly adds the node into the parent's node database. */
svn_hash_sets(pb->not_present_nodes,
apr_pstrdup(pb->pool, db->name),
svn_node_kind_to_word(svn_node_dir));
}
else if (wc_kind == svn_node_dir)
{
/* We have an editor violation. Github sometimes does this
in its subversion compatibility code, when changing the
depth of a working copy, or on updates from incomplete */
}
else
{
/* We found a file external occupating the place we need in BASE.
We can't add a not-present node in this case as that would overwrite
the file external. Luckily the file external itself stops us from
forgetting a child of this parent directory like an obstructing
working copy would.
The reason we get here is that the adm crawler doesn't report
file externals.
*/
SVN_ERR_ASSERT(wc_kind == svn_node_file
|| wc_kind == svn_node_symlink);
}
SVN_ERR(remember_skipped_tree(eb, db->local_abspath, scratch_pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
do_notification(eb, db->local_abspath, wc_kind,
svn_wc_notify_update_skip_obstruction, scratch_pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
else
versioned_locally_and_present = IS_NODE_PRESENT(status);
/* Is this path a conflict victim? */
if (conflicted)
{
if (pb->deletion_conflicts)
tree_conflict = svn_hash_gets(pb->deletion_conflicts, db->name);
if (tree_conflict)
{
svn_wc_conflict_reason_t reason;
const char *move_src_op_root_abspath;
/* So this deletion wasn't just a deletion, it is actually a
replacement. Let's install a better tree conflict. */
SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL,
&move_src_op_root_abspath,
eb->db,
db->local_abspath,
tree_conflict,
db->pool, scratch_pool));
tree_conflict = svn_wc__conflict_skel_create(db->pool);
SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
tree_conflict,
eb->db, db->local_abspath,
reason, svn_wc_conflict_action_replace,
move_src_op_root_abspath,
db->pool, scratch_pool));
/* And now stop checking for conflicts here and just perform
a shadowed update */
db->edit_conflict = tree_conflict; /* Cache for close_directory */
tree_conflict = NULL; /* No direct notification */
db->shadowed = TRUE; /* Just continue */
conflicted = FALSE; /* No skip */
}
else
SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
eb->db, db->local_abspath,
scratch_pool));
}
/* Now the "usual" behaviour if already conflicted. Skip it. */
if (conflicted)
{
/* Record this conflict so that its descendants are skipped silently. */
SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
/* We skip this node, but once the update completes the parent node will
be updated to the new revision. So a future recursive update of the
parent will not bring in this new node as the revision of the parent
describes to the repository that all children are available.
To resolve this problem, we add a not-present node to allow bringing
the node in once this conflict is resolved.
Note that we can safely assume that no present base node exists,
because then we would not have received an add_directory.
*/
svn_hash_sets(pb->not_present_nodes, apr_pstrdup(pb->pool, db->name),
svn_node_kind_to_word(svn_node_dir));
do_notification(eb, db->local_abspath, svn_node_dir,
svn_wc_notify_skip_conflicted, scratch_pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
else if (conflict_ignored)
{
db->shadowed = TRUE;
}
if (db->shadowed)
{
/* Nothing to check; does not and will not exist in working copy */
}
else if (versioned_locally_and_present)
{
/* What to do with a versioned or schedule-add dir:
A dir already added without history is OK. Set add_existed
so that user notification is delayed until after any prop
conflicts have been found.
An existing versioned dir is an error. In the future we may
relax this restriction and simply update such dirs.
A dir added with history is a tree conflict. */
svn_boolean_t local_is_non_dir;
svn_wc__db_status_t add_status = svn_wc__db_status_normal;
/* Is the local add a copy? */
if (status == svn_wc__db_status_added)
SVN_ERR(svn_wc__db_scan_addition(&add_status, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
eb->db, db->local_abspath,
scratch_pool, scratch_pool));
/* Is there *something* that is not a dir? */
local_is_non_dir = (wc_kind != svn_node_dir
&& status != svn_wc__db_status_deleted);
/* Do tree conflict checking if
* - if there is a local copy.
* - if this is a switch operation
* - the node kinds mismatch
*
* During switch, local adds at the same path as incoming adds get
* "lost" in that switching back to the original will no longer have the
* local add. So switch always alerts the user with a tree conflict. */
if (!eb->adds_as_modification
|| local_is_non_dir
|| add_status != svn_wc__db_status_added)
{
SVN_ERR(check_tree_conflict(&tree_conflict, eb,
db->local_abspath,
status, FALSE, svn_node_none,
svn_wc_conflict_action_add,
db->pool, scratch_pool));
}
if (tree_conflict == NULL)
db->add_existed = TRUE; /* Take over WORKING */
else
db->shadowed = TRUE; /* Only update BASE */
}
else if (kind != svn_node_none)
{
/* There's an unversioned node at this path. */
db->obstruction_found = TRUE;
/* Unversioned, obstructing dirs are handled by prop merge/conflict,
* if unversioned obstructions are allowed. */
if (! (kind == svn_node_dir && eb->allow_unver_obstructions))
{
/* Bring in the node as deleted */ /* ### Obstructed Conflict */
db->shadowed = TRUE;
/* Mark a conflict */
tree_conflict = svn_wc__conflict_skel_create(db->pool);
SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
tree_conflict,
eb->db, db->local_abspath,
svn_wc_conflict_reason_unversioned,
svn_wc_conflict_action_add, NULL,
db->pool, scratch_pool));
db->edit_conflict = tree_conflict;
}
}
if (tree_conflict)
SVN_ERR(complete_conflict(tree_conflict, eb, db->local_abspath,
db->old_repos_relpath, db->old_revision,
db->new_repos_relpath,
wc_kind, svn_node_dir,
pb->deletion_conflicts
? svn_hash_gets(pb->deletion_conflicts,
db->name)
: NULL,
db->pool, scratch_pool));
SVN_ERR(svn_wc__db_base_add_incomplete_directory(
eb->db, db->local_abspath,
db->new_repos_relpath,
eb->repos_root,
eb->repos_uuid,
*eb->target_revision,
db->ambient_depth,
(db->shadowed && db->obstruction_found),
(! db->shadowed
&& status == svn_wc__db_status_added),
tree_conflict, NULL,
scratch_pool));
/* Make sure there is a real directory at LOCAL_ABSPATH, unless we are just
updating the DB */
if (!db->shadowed)
SVN_ERR(svn_wc__ensure_directory(db->local_abspath, scratch_pool));
if (tree_conflict != NULL)
{
db->edit_conflict = tree_conflict;
db->already_notified = TRUE;
do_notification(eb, db->local_abspath, svn_node_dir,
svn_wc_notify_tree_conflict, scratch_pool);
}
/* If this add was obstructed by dir scheduled for addition without
history let close_directory() handle the notification because there
might be properties to deal with. If PATH was added inside a locally
deleted tree, then suppress notification, a tree conflict was already
issued. */
if (eb->notify_func && !db->already_notified && !db->add_existed)
{
svn_wc_notify_action_t action;
if (db->shadowed)
action = svn_wc_notify_update_shadowed_add;
else if (db->obstruction_found || db->add_existed)
action = svn_wc_notify_exists;
else
action = svn_wc_notify_update_add;
db->already_notified = TRUE;
do_notification(eb, db->local_abspath, svn_node_dir, action,
scratch_pool);
}
svn_pool_destroy(scratch_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;
svn_boolean_t have_work;
svn_boolean_t conflicted;
svn_boolean_t conflict_ignored = FALSE;
svn_skel_t *tree_conflict = NULL;
svn_wc__db_status_t status, base_status;
svn_node_kind_t wc_kind;
SVN_ERR(make_dir_baton(&db, path, eb, pb, FALSE, pool));
*child_baton = db;
if (db->skip_this)
return SVN_NO_ERROR;
/* Detect obstructing working copies */
{
svn_boolean_t is_root;
SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, db->local_abspath,
pool));
if (is_root)
{
/* Just skip this node; a future update will handle it */
SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
do_notification(eb, db->local_abspath, svn_node_dir,
svn_wc_notify_update_skip_obstruction, pool);
return SVN_NO_ERROR;
}
}
/* We should have a write lock on every directory touched. */
SVN_ERR(svn_wc__write_check(eb->db, db->local_abspath, pool));
SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &db->old_revision,
&db->old_repos_relpath, NULL, NULL,
&db->changed_rev, &db->changed_date,
&db->changed_author, &db->ambient_depth,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
&conflicted, NULL, NULL, NULL,
NULL, NULL, &have_work,
eb->db, db->local_abspath,
db->pool, pool));
if (!have_work)
base_status = status;
else
SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db->old_revision,
&db->old_repos_relpath, NULL, NULL,
&db->changed_rev, &db->changed_date,
&db->changed_author, &db->ambient_depth,
NULL, NULL, NULL, NULL, NULL, NULL,
eb->db, db->local_abspath,
db->pool, pool));
db->was_incomplete = (base_status == svn_wc__db_status_incomplete);
SVN_ERR(calculate_repos_relpath(&db->new_repos_relpath, db->local_abspath,
db->old_repos_relpath, eb, pb,
db->pool, pool));
/* Is this path a conflict victim? */
if (db->shadowed)
conflicted = FALSE; /* Conflict applies to WORKING */
else if (conflicted)
SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
eb->db, db->local_abspath, pool));
if (conflicted)
{
SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool));
db->skip_this = TRUE;
db->already_notified = TRUE;
do_notification(eb, db->local_abspath, svn_node_unknown,
svn_wc_notify_skip_conflicted, pool);
return SVN_NO_ERROR;
}
else if (conflict_ignored)
{
db->shadowed = TRUE;
}
/* Is this path a fresh tree conflict victim? If so, skip the tree
with one notification. */
/* Check for conflicts only when we haven't already recorded
* a tree-conflict on a parent node. */
if (!db->shadowed)
SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath,
status, TRUE, svn_node_dir,
svn_wc_conflict_action_edit,
db->pool, pool));
/* Remember the roots of any locally deleted trees. */
if (tree_conflict != NULL)
{
svn_wc_conflict_reason_t reason;
db->edit_conflict = tree_conflict;
/* Other modifications wouldn't be a tree conflict */
SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
eb->db, db->local_abspath,
tree_conflict,
db->pool, db->pool));
SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
|| reason == svn_wc_conflict_reason_moved_away
|| reason == svn_wc_conflict_reason_replaced
|| reason == svn_wc_conflict_reason_obstructed);
/* Continue updating BASE */
if (reason == svn_wc_conflict_reason_obstructed)
db->edit_obstructed = TRUE;
else
db->shadowed = TRUE;
}
/* Mark directory as being at target_revision and URL, but incomplete. */
SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db, db->local_abspath,
db->new_repos_relpath,
*eb->target_revision,
pool));
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
change_dir_prop(void *dir_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
svn_prop_t *propchange;
struct dir_baton *db = dir_baton;
if (db->skip_this)
return SVN_NO_ERROR;
propchange = apr_array_push(db->propchanges);
propchange->name = apr_pstrdup(db->pool, name);
propchange->value = svn_string_dup(value, db->pool);
if (!db->edited && svn_property_kind2(name) == svn_prop_regular_kind)
SVN_ERR(mark_directory_edited(db, pool));
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(const 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;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
close_directory(void *dir_baton,
apr_pool_t *pool)
{
struct dir_baton *db = dir_baton;
struct edit_baton *eb = db->edit_baton;
svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
apr_array_header_t *entry_prop_changes;
apr_array_header_t *dav_prop_changes;
apr_array_header_t *regular_prop_changes;
apr_hash_t *base_props;
apr_hash_t *actual_props;
apr_hash_t *new_base_props = NULL;
apr_hash_t *new_actual_props = NULL;
svn_revnum_t new_changed_rev = SVN_INVALID_REVNUM;
apr_time_t new_changed_date = 0;
const char *new_changed_author = NULL;
apr_pool_t *scratch_pool = db->pool;
svn_skel_t *all_work_items = NULL;
svn_skel_t *conflict_skel = NULL;
/* Skip if we're in a conflicted tree. */
if (db->skip_this)
{
/* Allow the parent to complete its update. */
SVN_ERR(maybe_release_dir_info(db));
return SVN_NO_ERROR;
}
if (db->edited)
conflict_skel = db->edit_conflict;
SVN_ERR(svn_categorize_props(db->propchanges, &entry_prop_changes,
&dav_prop_changes, &regular_prop_changes, pool));
/* Fetch the existing properties. */
if ((!db->adding_dir || db->add_existed)
&& !db->shadowed)
{
SVN_ERR(svn_wc__get_actual_props(&actual_props,
eb->db, db->local_abspath,
scratch_pool, scratch_pool));
}
else
actual_props = apr_hash_make(pool);
if (db->add_existed)
{
/* This node already exists. Grab the current pristine properties. */
SVN_ERR(svn_wc__db_read_pristine_props(&base_props,
eb->db, db->local_abspath,
scratch_pool, scratch_pool));
}
else if (!db->adding_dir)
{
/* Get the BASE properties for proper merging. */
SVN_ERR(svn_wc__db_base_get_props(&base_props,
eb->db, db->local_abspath,
scratch_pool, scratch_pool));
}
else
base_props = apr_hash_make(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;
apr_hash_index_t *hi;
/* In a copy of the BASE props, remove every property that we see an
incoming change for. The remaining unmentioned properties are those
which need to be deleted. */
props_to_delete = apr_hash_copy(pool, base_props);
for (i = 0; i < regular_prop_changes->nelts; i++)
{
const svn_prop_t *prop;
prop = &APR_ARRAY_IDX(regular_prop_changes, i, svn_prop_t);
svn_hash_sets(props_to_delete, prop->name, NULL);
}
/* Add these props to the incoming propchanges (in
* regular_prop_changes). */
for (hi = apr_hash_first(pool, props_to_delete);
hi != NULL;
hi = apr_hash_next(hi))
{
const char *propname = apr_hash_this_key(hi);
svn_prop_t *prop = apr_array_push(regular_prop_changes);
/* Record a deletion for PROPNAME. */
prop->name = propname;
prop->value = NULL;
}
}
/* If this directory has property changes stored up, now is the time
to deal with them. */
if (regular_prop_changes->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 (eb->external_func)
{
const svn_prop_t *change
= externals_prop_changed(regular_prop_changes);
if (change)
{
const svn_string_t *new_val_s = change->value;
const svn_string_t *old_val_s;
old_val_s = svn_hash_gets(base_props, SVN_PROP_EXTERNALS);
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 */
{
SVN_ERR((eb->external_func)(
eb->external_baton,
db->local_abspath,
old_val_s,
new_val_s,
db->ambient_depth,
db->pool));
}
}
}
if (db->shadowed)
{
/* We don't have a relevant actual row, but we need actual properties
to allow property merging without conflicts. */
if (db->adding_dir)
actual_props = apr_hash_make(scratch_pool);
else
actual_props = base_props;
}
/* Merge pending properties. */
new_base_props = svn_prop__patch(base_props, regular_prop_changes,
db->pool);
SVN_ERR_W(svn_wc__merge_props(&conflict_skel,
&prop_state,
&new_actual_props,
eb->db,
db->local_abspath,
NULL /* use baseprops */,
base_props,
actual_props,
regular_prop_changes,
db->pool,
scratch_pool),
_("Couldn't do property merge"));
/* After a (not-dry-run) merge, we ALWAYS have props to save. */
SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
}
SVN_ERR(accumulate_last_change(&new_changed_rev, &new_changed_date,
&new_changed_author, entry_prop_changes,
scratch_pool, scratch_pool));
/* Check if we should add some not-present markers before marking the
directory complete (Issue #3569) */
{
apr_hash_t *new_children = svn_hash_gets(eb->dir_dirents,
db->new_repos_relpath);
if (new_children != NULL)
{
apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
for (hi = apr_hash_first(scratch_pool, new_children);
hi;
hi = apr_hash_next(hi))
{
const char *child_name;
const char *child_abspath;
const char *child_relpath;
const svn_dirent_t *dirent;
svn_wc__db_status_t status;
svn_node_kind_t child_kind;
svn_error_t *err;
svn_pool_clear(iterpool);
child_name = apr_hash_this_key(hi);
child_abspath = svn_dirent_join(db->local_abspath, child_name,
iterpool);
dirent = apr_hash_this_val(hi);
child_kind = (dirent->kind == svn_node_dir)
? svn_node_dir
: svn_node_file;
if (db->ambient_depth < svn_depth_immediates
&& child_kind == svn_node_dir)
continue; /* We don't need the subdirs */
/* ### We just check if there is some node in BASE at this path */
err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
eb->db, child_abspath,
iterpool, iterpool);
if (!err)
{
svn_boolean_t is_wcroot;
SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, eb->db, child_abspath,
iterpool));
if (!is_wcroot)
continue; /* Everything ok... Nothing to do here */
/* Fall through to allow recovering later */
}
else if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
svn_error_clear(err);
child_relpath = svn_relpath_join(db->new_repos_relpath, child_name,
iterpool);
SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db,
child_abspath,
child_relpath,
eb->repos_root,
eb->repos_uuid,
*eb->target_revision,
child_kind,
NULL, NULL,
iterpool));
}
svn_pool_destroy(iterpool);
}
}
if (apr_hash_count(db->not_present_nodes))
{
apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
/* This should call some new function (which could also be used
for new_children above) to add all the names in single
transaction, but I can't even trigger it. I've tried
ra_local, ra_svn, ra_neon, ra_serf and they all call
close_file before close_dir. */
for (hi = apr_hash_first(scratch_pool, db->not_present_nodes);
hi;
hi = apr_hash_next(hi))
{
const char *child = apr_hash_this_key(hi);
const char *child_abspath, *child_relpath;
svn_node_kind_t kind = svn_node_kind_from_word(apr_hash_this_val(hi));
svn_pool_clear(iterpool);
child_abspath = svn_dirent_join(db->local_abspath, child, iterpool);
child_relpath = svn_dirent_join(db->new_repos_relpath, child, iterpool);
SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db,
child_abspath,
child_relpath,
eb->repos_root,
eb->repos_uuid,
*eb->target_revision,
kind,
NULL, NULL,
iterpool));
}
svn_pool_destroy(iterpool);
}
/* If this directory is merely an anchor for a targeted child, then we
should not be updating the node at all. */
if (db->parent_baton == NULL
&& *eb->target_basename != '\0')
{
/* And we should not have received any changes! */
SVN_ERR_ASSERT(db->propchanges->nelts == 0);
/* ... which also implies NEW_CHANGED_* are not set,
and NEW_BASE_PROPS == NULL. */
}
else
{
apr_hash_t *props;
apr_array_header_t *iprops = NULL;
/* ### we know a base node already exists. it was created in
### open_directory or add_directory. let's just preserve the
### existing DEPTH value, and possibly CHANGED_*. */
/* If we received any changed_* values, then use them. */
if (SVN_IS_VALID_REVNUM(new_changed_rev))
db->changed_rev = new_changed_rev;
if (new_changed_date != 0)
db->changed_date = new_changed_date;
if (new_changed_author != NULL)
db->changed_author = new_changed_author;
/* If no depth is set yet, set to infinity. */
if (db->ambient_depth == svn_depth_unknown)
db->ambient_depth = svn_depth_infinity;
if (eb->depth_is_sticky
&& db->ambient_depth != eb->requested_depth)
{
/* 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->requested_depth == svn_depth_infinity
|| (strcmp(db->local_abspath, eb->target_abspath) == 0
&& eb->requested_depth > db->ambient_depth))
{
db->ambient_depth = eb->requested_depth;
}
}
/* Do we have new properties to install? Or shall we simply retain
the prior set of properties? If we're installing new properties,
then we also want to write them to an old-style props file. */
props = new_base_props;
if (props == NULL)
props = base_props;
if (conflict_skel)
{
svn_skel_t *work_item;
SVN_ERR(complete_conflict(conflict_skel,
db->edit_baton,
db->local_abspath,
db->old_repos_relpath,
db->old_revision,
db->new_repos_relpath,
svn_node_dir, svn_node_dir,
(db->parent_baton
&& db->parent_baton->deletion_conflicts)
? svn_hash_gets(
db->parent_baton->deletion_conflicts,
db->name)
: NULL,
db->pool, scratch_pool));
SVN_ERR(svn_wc__conflict_create_markers(&work_item,
eb->db, db->local_abspath,
conflict_skel,
scratch_pool, scratch_pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item,
scratch_pool);
}
/* Any inherited props to be set set for this base node? */
if (eb->wcroot_iprops)
{
iprops = svn_hash_gets(eb->wcroot_iprops, db->local_abspath);
/* close_edit may also update iprops for switched nodes, catching
those for which close_directory is never called (e.g. a switch
with no changes). So as a minor optimization we remove any
iprops from the hash so as not to set them again in
close_edit. */
if (iprops)
svn_hash_sets(eb->wcroot_iprops, db->local_abspath, NULL);
}
/* Update the BASE data for the directory and mark the directory
complete */
SVN_ERR(svn_wc__db_base_add_directory(
eb->db, db->local_abspath,
eb->wcroot_abspath,
db->new_repos_relpath,
eb->repos_root, eb->repos_uuid,
*eb->target_revision,
props,
db->changed_rev, db->changed_date, db->changed_author,
NULL /* children */,
db->ambient_depth,
(dav_prop_changes->nelts > 0)
? svn_prop_array_to_hash(dav_prop_changes, pool)
: NULL,
(! db->shadowed) && new_base_props != NULL,
new_actual_props, iprops,
conflict_skel, all_work_items,
scratch_pool));
}
/* Process all of the queued work items for this directory. */
SVN_ERR(svn_wc__wq_run(eb->db, db->local_abspath,
eb->cancel_func, eb->cancel_baton,
scratch_pool));
if (db->parent_baton)
svn_hash_sets(db->parent_baton->not_present_nodes, db->name, NULL);
if (conflict_skel && eb->conflict_func)
SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, db->local_abspath,
svn_node_dir,
conflict_skel,
NULL /* merge_options */,
eb->conflict_func,
eb->conflict_baton,
eb->cancel_func,
eb->cancel_baton,
scratch_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->already_notified && eb->notify_func && db->edited)
{
svn_wc_notify_t *notify;
svn_wc_notify_action_t action;
if (db->shadowed || db->edit_obstructed)
action = svn_wc_notify_update_shadowed_update;
else if (db->obstruction_found || db->add_existed)
action = svn_wc_notify_exists;
else
action = svn_wc_notify_update_update;
notify = svn_wc_create_notify(db->local_abspath, action, pool);
notify->kind = svn_node_dir;
notify->prop_state = prop_state;
notify->revision = *eb->target_revision;
notify->old_revision = db->old_revision;
eb->notify_func(eb->notify_baton, notify, scratch_pool);
}
if (db->edited)
eb->edited = db->edited;
/* We're done with this directory, so remove one reference from the
bump information. */
SVN_ERR(maybe_release_dir_info(db));
return SVN_NO_ERROR;
}
/* Common code for 'absent_file' and 'absent_directory'. */
static svn_error_t *
absent_node(const char *path,
svn_node_kind_t absent_kind,
void *parent_baton,
apr_pool_t *pool)
{
struct dir_baton *pb = parent_baton;
struct edit_baton *eb = pb->edit_baton;
apr_pool_t *scratch_pool = svn_pool_create(pool);
const char *name = svn_dirent_basename(path, NULL);
const char *local_abspath;
svn_error_t *err;
svn_wc__db_status_t status;
svn_node_kind_t kind;
svn_skel_t *tree_conflict = NULL;
if (pb->skip_this)
return SVN_NO_ERROR;
local_abspath = svn_dirent_join(pb->local_abspath, name, scratch_pool);
/* If an item by this name is scheduled for addition that's a
genuine tree-conflict. */
err = svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
eb->db, local_abspath,
scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
svn_error_clear(err);
status = svn_wc__db_status_not_present;
kind = svn_node_unknown;
}
if (status != svn_wc__db_status_server_excluded)
SVN_ERR(mark_directory_edited(pb, scratch_pool));
/* Else fall through as we should update the revision anyway */
if (status == svn_wc__db_status_normal)
{
svn_boolean_t wcroot;
/* We found an obstructing working copy or a file external! */
SVN_ERR(svn_wc__db_is_wcroot(&wcroot, eb->db, local_abspath,
scratch_pool));
if (wcroot)
{
/*
We have an obstructing working copy; possibly a directory external
We can do two things now:
1) notify the user, record a skip, etc.
2) Just record the absent node in BASE in the parent
working copy.
As option 2 happens to be exactly what we do anyway, fall through.
*/
}
else
{
svn_boolean_t file_external;
svn_revnum_t revnum;
SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &revnum, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
&file_external,
eb->db, local_abspath,
scratch_pool, scratch_pool));
if (file_external)
{
/* The server asks us to replace a file external
(Existing BASE node; not reported by the working copy crawler
or there would have been a delete_entry() call.
There is no way we can store this state in the working copy as
the BASE layer is already filled.
We could error out, but that is not helping anybody; the user is not
even seeing with what the file external would be replaced, so let's
report a skip and continue the update.
*/
if (eb->notify_func)
{
svn_wc_notify_t *notify;
notify = svn_wc_create_notify(
local_abspath,
svn_wc_notify_update_skip_obstruction,
scratch_pool);
eb->notify_func(eb->notify_baton, notify, scratch_pool);
}
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
else
{
/* We have a normal local node that will now be hidden for the
user. Let's try to delete what is there. This may introduce
tree conflicts if there are local changes */
SVN_ERR(delete_entry(path, revnum, pb, scratch_pool));
/* delete_entry() promises that BASE is empty after the operation,
so we can just fall through now */
}
}
}
else if (status == svn_wc__db_status_not_present
|| status == svn_wc__db_status_server_excluded
|| status == svn_wc__db_status_excluded)
{
/* The BASE node is not actually there, so we can safely turn it into
an absent node */
}
else
{
/* We have a local addition. If this would be a BASE node it would have
been deleted before we get here. (Which might have turned it into
a copy). */
SVN_ERR_ASSERT(status != svn_wc__db_status_normal);
if (!pb->shadowed && !pb->edit_obstructed)
SVN_ERR(check_tree_conflict(&tree_conflict, eb, local_abspath,
status, FALSE, svn_node_unknown,
svn_wc_conflict_action_add,
scratch_pool, scratch_pool));
}
{
const char *repos_relpath;
repos_relpath = svn_relpath_join(pb->new_repos_relpath, name, scratch_pool);
if (tree_conflict)
SVN_ERR(complete_conflict(tree_conflict, eb, local_abspath,
NULL, SVN_INVALID_REVNUM, repos_relpath,
kind, svn_node_unknown, NULL,
scratch_pool, scratch_pool));
/* Insert an excluded node below the parent node to note that this child
is absent. (This puts it in the parent db if the child is obstructed) */
SVN_ERR(svn_wc__db_base_add_excluded_node(eb->db, local_abspath,
repos_relpath, eb->repos_root,
eb->repos_uuid,
*(eb->target_revision),
absent_kind,
svn_wc__db_status_server_excluded,
tree_conflict, NULL,
scratch_pool));
if (tree_conflict)
{
if (eb->conflict_func)
SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, local_abspath,
kind,
tree_conflict,
NULL /* merge_options */,
eb->conflict_func,
eb->conflict_baton,
eb->cancel_func,
eb->cancel_baton,
scratch_pool));
do_notification(eb, local_abspath, kind, svn_wc_notify_tree_conflict,
scratch_pool);
}
}
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
absent_file(const char *path,
void *parent_baton,
apr_pool_t *pool)
{
return absent_node(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_node(path, svn_node_dir, parent_baton, pool);
}
/* 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;
svn_node_kind_t kind;
svn_node_kind_t wc_kind;
svn_wc__db_status_t status;
apr_pool_t *scratch_pool;
svn_boolean_t conflicted;
svn_boolean_t conflict_ignored = FALSE;
svn_boolean_t versioned_locally_and_present;
svn_skel_t *tree_conflict = NULL;
svn_error_t *err = SVN_NO_ERROR;
SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev)));
SVN_ERR(make_file_baton(&fb, pb, path, TRUE, pool));
*file_baton = fb;
if (fb->skip_this)
return SVN_NO_ERROR;
SVN_ERR(calculate_repos_relpath(&fb->new_repos_relpath, fb->local_abspath,
NULL, eb, pb, fb->pool, pool));
SVN_ERR(mark_file_edited(fb, pool));
/* The file_pool can stick around for a *long* time, so we want to
use a subpool for any temporary allocations. */
scratch_pool = svn_pool_create(pool);
/* It may not be named the same as the administrative directory. */
if (svn_wc_is_adm_dir(fb->name, pool))
return svn_error_createf(
SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("Failed to add file '%s': object of the same name as the "
"administrative directory"),
svn_dirent_local_style(fb->local_abspath, pool));
if (!eb->clean_checkout)
{
SVN_ERR(svn_io_check_path(fb->local_abspath, &kind, scratch_pool));
err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
&conflicted, NULL, NULL, NULL, NULL, NULL, NULL,
eb->db, fb->local_abspath,
scratch_pool, scratch_pool);
}
else
{
kind = svn_node_none;
status = svn_wc__db_status_not_present;
wc_kind = svn_node_unknown;
conflicted = FALSE;
}
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
svn_error_clear(err);
wc_kind = svn_node_unknown;
conflicted = FALSE;
versioned_locally_and_present = FALSE;
}
else if (status == svn_wc__db_status_normal && wc_kind == svn_node_unknown)
{
SVN_ERR_ASSERT(conflicted);
versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */
}
else if (status == svn_wc__db_status_normal
|| status == svn_wc__db_status_incomplete)
{
svn_boolean_t root;
SVN_ERR(svn_wc__db_is_wcroot(&root, eb->db, fb->local_abspath,
scratch_pool));
if (root)
{
/* !! We found the root of a working copy obstructing the wc !!
If the directory would be part of our own working copy then
we wouldn't have been called as an add_directory().
The only thing we can do is add a not-present node, to allow
a future update to bring in the new files when the problem is
resolved. Note that svn_wc__db_base_add_not_present_node()
explicitly adds the node into the parent's node database. */
svn_hash_sets(pb->not_present_nodes,
apr_pstrdup(pb->pool, fb->name),
svn_node_kind_to_word(svn_node_dir));
}
else if (wc_kind == svn_node_dir)
{
/* We have an editor violation. Github sometimes does this
in its subversion compatibility code, when changing the
depth of a working copy, or on updates from incomplete */
}
else
{
/* We found a file external occupating the place we need in BASE.
We can't add a not-present node in this case as that would overwrite
the file external. Luckily the file external itself stops us from
forgetting a child of this parent directory like an obstructing
working copy would.
The reason we get here is that the adm crawler doesn't report
file externals.
*/
SVN_ERR_ASSERT(wc_kind == svn_node_file
|| wc_kind == svn_node_symlink);
}
SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
fb->skip_this = TRUE;
fb->already_notified = TRUE;
do_notification(eb, fb->local_abspath, wc_kind,
svn_wc_notify_update_skip_obstruction, scratch_pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
else
versioned_locally_and_present = IS_NODE_PRESENT(status);
/* Is this path a conflict victim? */
if (fb->shadowed)
conflicted = FALSE; /* Conflict applies to WORKING */
else if (conflicted)
{
if (pb->deletion_conflicts)
tree_conflict = svn_hash_gets(pb->deletion_conflicts, fb->name);
if (tree_conflict)
{
svn_wc_conflict_reason_t reason;
const char *move_src_op_root_abspath;
/* So this deletion wasn't just a deletion, it is actually a
replacement. Let's install a better tree conflict. */
SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL,
&move_src_op_root_abspath,
eb->db,
fb->local_abspath,
tree_conflict,
fb->pool, scratch_pool));
tree_conflict = svn_wc__conflict_skel_create(fb->pool);
SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
tree_conflict,
eb->db, fb->local_abspath,
reason, svn_wc_conflict_action_replace,
move_src_op_root_abspath,
fb->pool, scratch_pool));
/* And now stop checking for conflicts here and just perform
a shadowed update */
fb->edit_conflict = tree_conflict; /* Cache for close_file */
tree_conflict = NULL; /* No direct notification */
fb->shadowed = TRUE; /* Just continue */
conflicted = FALSE; /* No skip */
}
else
SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
eb->db, fb->local_abspath, pool));
}
/* Now the usual conflict handling: skip. */
if (conflicted)
{
SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
fb->skip_this = TRUE;
fb->already_notified = TRUE;
/* We skip this node, but once the update completes the parent node will
be updated to the new revision. So a future recursive update of the
parent will not bring in this new node as the revision of the parent
describes to the repository that all children are available.
To resolve this problem, we add a not-present node to allow bringing
the node in once this conflict is resolved.
Note that we can safely assume that no present base node exists,
because then we would not have received an add_file.
*/
svn_hash_sets(pb->not_present_nodes, apr_pstrdup(pb->pool, fb->name),
svn_node_kind_to_word(svn_node_file));
do_notification(eb, fb->local_abspath, svn_node_file,
svn_wc_notify_skip_conflicted, scratch_pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
else if (conflict_ignored)
{
fb->shadowed = TRUE;
}
if (fb->shadowed)
{
/* Nothing to check; does not and will not exist in working copy */
}
else if (versioned_locally_and_present)
{
/* What to do with a versioned or schedule-add file:
If the UUID doesn't match the parent's, or the URL isn't a child of
the parent dir's URL, it's an error.
Set add_existed so that user notification is delayed until after any
text or prop conflicts have been found.
Whether the incoming add is a symlink or a file will only be known in
close_file(), when the props are known. So with a locally added file
or symlink, let close_file() check for a tree conflict.
We will never see missing files here, because these would be
re-added during the crawler phase. */
svn_boolean_t local_is_file;
/* Is the local node a copy or move */
if (status == svn_wc__db_status_added)
SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL,
eb->db, fb->local_abspath,
scratch_pool, scratch_pool));
/* Is there something that is a file? */
local_is_file = (wc_kind == svn_node_file
|| wc_kind == svn_node_symlink);
/* Do tree conflict checking if
* - if there is a local copy.
* - if this is a switch operation
* - the node kinds mismatch
*
* During switch, local adds at the same path as incoming adds get
* "lost" in that switching back to the original will no longer have the
* local add. So switch always alerts the user with a tree conflict. */
if (!eb->adds_as_modification
|| !local_is_file
|| status != svn_wc__db_status_added)
{
SVN_ERR(check_tree_conflict(&tree_conflict, eb,
fb->local_abspath,
status, FALSE, svn_node_none,
svn_wc_conflict_action_add,
fb->pool, scratch_pool));
}
if (tree_conflict == NULL)
fb->add_existed = TRUE; /* Take over WORKING */
else
fb->shadowed = TRUE; /* Only update BASE */
}
else if (kind != svn_node_none)
{
/* There's an unversioned node at this path. */
fb->obstruction_found = TRUE;
/* Unversioned, obstructing files are handled by text merge/conflict,
* if unversioned obstructions are allowed. */
if (! (kind == svn_node_file && eb->allow_unver_obstructions))
{
/* Bring in the node as deleted */ /* ### Obstructed Conflict */
fb->shadowed = TRUE;
/* Mark a conflict */
tree_conflict = svn_wc__conflict_skel_create(fb->pool);
SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
tree_conflict,
eb->db, fb->local_abspath,
svn_wc_conflict_reason_unversioned,
svn_wc_conflict_action_add,
NULL,
fb->pool, scratch_pool));
}
}
/* When this is not the update target add a not-present BASE node now,
to allow marking the parent directory complete in its close_edit() call.
This resolves issues when that occurs before the close_file(). */
if (pb->parent_baton
|| *eb->target_basename == '\0'
|| (strcmp(fb->local_abspath, eb->target_abspath) != 0))
{
svn_hash_sets(pb->not_present_nodes, apr_pstrdup(pb->pool, fb->name),
svn_node_kind_to_word(svn_node_file));
}
if (tree_conflict != NULL)
{
SVN_ERR(complete_conflict(tree_conflict,
fb->edit_baton,
fb->local_abspath,
fb->old_repos_relpath,
fb->old_revision,
fb->new_repos_relpath,
wc_kind, svn_node_file,
pb->deletion_conflicts
? svn_hash_gets(pb->deletion_conflicts,
fb->name)
: NULL,
fb->pool, scratch_pool));
SVN_ERR(svn_wc__db_op_mark_conflict(eb->db,
fb->local_abspath,
tree_conflict, NULL,
scratch_pool));
fb->edit_conflict = tree_conflict;
fb->already_notified = TRUE;
do_notification(eb, fb->local_abspath, svn_node_file,
svn_wc_notify_tree_conflict, scratch_pool);
}
svn_pool_destroy(scratch_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;
svn_boolean_t conflicted;
svn_boolean_t conflict_ignored = FALSE;
svn_boolean_t have_work;
svn_wc__db_status_t status;
svn_node_kind_t wc_kind;
svn_skel_t *tree_conflict = NULL;
/* the file_pool can stick around for a *long* time, so we want to use
a subpool for any temporary allocations. */
apr_pool_t *scratch_pool = svn_pool_create(pool);
SVN_ERR(make_file_baton(&fb, pb, path, FALSE, pool));
*file_baton = fb;
if (fb->skip_this)
return SVN_NO_ERROR;
/* Detect obstructing working copies */
{
svn_boolean_t is_root;
SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, fb->local_abspath,
pool));
if (is_root)
{
/* Just skip this node; a future update will handle it */
SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
fb->skip_this = TRUE;
fb->already_notified = TRUE;
do_notification(eb, fb->local_abspath, svn_node_file,
svn_wc_notify_update_skip_obstruction, pool);
return SVN_NO_ERROR;
}
}
/* Sanity check. */
SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &fb->old_revision,
&fb->old_repos_relpath, NULL, NULL,
&fb->changed_rev, &fb->changed_date,
&fb->changed_author, NULL,
&fb->original_checksum, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
&conflicted, NULL, NULL, &fb->local_prop_mods,
NULL, NULL, &have_work,
eb->db, fb->local_abspath,
fb->pool, scratch_pool));
if (have_work)
SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &fb->old_revision,
&fb->old_repos_relpath, NULL, NULL,
&fb->changed_rev, &fb->changed_date,
&fb->changed_author, NULL,
&fb->original_checksum, NULL, NULL,
NULL, NULL, NULL,
eb->db, fb->local_abspath,
fb->pool, scratch_pool));
SVN_ERR(calculate_repos_relpath(&fb->new_repos_relpath, fb->local_abspath,
fb->old_repos_relpath, eb, pb,
fb->pool, scratch_pool));
/* Is this path a conflict victim? */
if (fb->shadowed)
conflicted = FALSE; /* Conflict applies to WORKING */
else if (conflicted)
SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored,
eb->db, fb->local_abspath, pool));
if (conflicted)
{
SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool));
fb->skip_this = TRUE;
fb->already_notified = TRUE;
do_notification(eb, fb->local_abspath, svn_node_unknown,
svn_wc_notify_skip_conflicted, scratch_pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
else if (conflict_ignored)
{
fb->shadowed = TRUE;
}
/* Check for conflicts only when we haven't already recorded
* a tree-conflict on a parent node. */
if (!fb->shadowed)
SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath,
status, TRUE, svn_node_file,
svn_wc_conflict_action_edit,
fb->pool, scratch_pool));
/* Is this path the victim of a newly-discovered tree conflict? */
if (tree_conflict != NULL)
{
svn_wc_conflict_reason_t reason;
fb->edit_conflict = tree_conflict;
/* Other modifications wouldn't be a tree conflict */
SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL,
eb->db, fb->local_abspath,
tree_conflict,
scratch_pool, scratch_pool));
SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted
|| reason == svn_wc_conflict_reason_moved_away
|| reason == svn_wc_conflict_reason_replaced
|| reason == svn_wc_conflict_reason_obstructed);
/* Continue updating BASE */
if (reason == svn_wc_conflict_reason_obstructed)
fb->edit_obstructed = TRUE;
else
fb->shadowed = TRUE;
}
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
/* Implements svn_stream_lazyopen_func_t. */
static svn_error_t *
lazy_open_source(svn_stream_t **stream,
void *baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
struct file_baton *fb = baton;
SVN_ERR(svn_wc__db_pristine_read(stream, NULL, fb->edit_baton->db,
fb->local_abspath,
fb->original_checksum,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
/* Implements svn_stream_lazyopen_func_t. */
static svn_error_t *
lazy_open_target(svn_stream_t **stream,
void *baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
struct handler_baton *hb = baton;
svn_wc__db_install_data_t *install_data;
/* By convention return value is undefined on error, but we rely
on HB->INSTALL_DATA value in window_handler() and abort
INSTALL_STREAM if is not NULL on error.
So we store INSTALL_DATA to local variable first, to leave
HB->INSTALL_DATA unchanged on error. */
SVN_ERR(svn_wc__db_pristine_prepare_install(stream,
&install_data,
&hb->new_text_base_sha1_checksum,
NULL,
hb->fb->edit_baton->db,
hb->fb->dir_baton->local_abspath,
result_pool, scratch_pool));
hb->install_data = install_data;
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
apply_textdelta(void *file_baton,
const char *expected_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_pcalloc(handler_pool, sizeof(*hb));
struct edit_baton *eb = fb->edit_baton;
const svn_checksum_t *recorded_base_checksum;
svn_checksum_t *expected_base_checksum;
svn_stream_t *source;
svn_stream_t *target;
if (fb->skip_this)
{
*handler = svn_delta_noop_window_handler;
*handler_baton = NULL;
return SVN_NO_ERROR;
}
SVN_ERR(mark_file_edited(fb, pool));
/* Parse checksum or sets expected_base_checksum to NULL */
SVN_ERR(svn_checksum_parse_hex(&expected_base_checksum, svn_checksum_md5,
expected_checksum, pool));
/* 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. */
/* The incoming delta is targeted against EXPECTED_BASE_CHECKSUM. Find and
check our RECORDED_BASE_CHECKSUM. (In WC-1, we could not do this test
for replaced nodes because we didn't store the checksum of the "revert
base". In WC-NG, we do and we can.) */
recorded_base_checksum = fb->original_checksum;
/* If we have a checksum that we want to compare to a MD5 checksum,
ensure that it is a MD5 checksum */
if (recorded_base_checksum
&& expected_base_checksum
&& recorded_base_checksum->kind != svn_checksum_md5)
SVN_ERR(svn_wc__db_pristine_get_md5(&recorded_base_checksum,
eb->db, eb->wcroot_abspath,
recorded_base_checksum, pool, pool));
if (!svn_checksum_match(expected_base_checksum, recorded_base_checksum))
return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
_("Checksum mismatch for '%s':\n"
" expected: %s\n"
" recorded: %s\n"),
svn_dirent_local_style(fb->local_abspath, pool),
svn_checksum_to_cstring_display(expected_base_checksum,
pool),
svn_checksum_to_cstring_display(recorded_base_checksum,
pool));
/* 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->adding_file)
{
SVN_ERR_ASSERT(!fb->original_checksum
|| fb->original_checksum->kind == svn_checksum_sha1);
source = svn_stream_lazyopen_create(lazy_open_source, fb, FALSE,
handler_pool);
}
else
{
source = svn_stream_empty(handler_pool);
}
/* If we don't have a recorded checksum, use the ra provided checksum */
if (!recorded_base_checksum)
recorded_base_checksum = expected_base_checksum;
/* Checksum the text base while applying deltas */
if (recorded_base_checksum)
{
hb->expected_source_checksum = svn_checksum_dup(recorded_base_checksum,
handler_pool);
/* Wrap stream and store reference to allow calculating the
checksum. */
source = svn_stream_checksummed2(source,
&hb->actual_source_checksum,
NULL, recorded_base_checksum->kind,
TRUE, handler_pool);
hb->source_checksum_stream = source;
}
target = svn_stream_lazyopen_create(lazy_open_target, hb, TRUE, handler_pool);
/* Prepare to apply the delta. */
svn_txdelta_apply(source, target,
hb->new_text_base_md5_digest,
fb->local_abspath /* 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 *scratch_pool)
{
struct file_baton *fb = file_baton;
svn_prop_t *propchange;
if (fb->skip_this)
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 = svn_string_dup(value, fb->pool);
if (!fb->edited && svn_property_kind2(name) == svn_prop_regular_kind)
SVN_ERR(mark_file_edited(fb, scratch_pool));
if (! fb->shadowed
&& strcmp(name, SVN_PROP_SPECIAL) == 0)
{
struct edit_baton *eb = fb->edit_baton;
svn_boolean_t modified = FALSE;
svn_boolean_t becomes_symlink;
svn_boolean_t was_symlink;
/* Let's see if we have a change as in some scenarios servers report
non-changes of properties. */
becomes_symlink = (value != NULL);
if (fb->adding_file)
was_symlink = becomes_symlink; /* No change */
else
{
apr_hash_t *props;
/* We read the server-props, not the ACTUAL props here as we just
want to see if this is really an incoming prop change. */
SVN_ERR(svn_wc__db_base_get_props(&props, eb->db,
fb->local_abspath,
scratch_pool, scratch_pool));
was_symlink = ((props
&& svn_hash_gets(props, SVN_PROP_SPECIAL) != NULL)
? svn_tristate_true
: svn_tristate_false);
}
if (was_symlink != becomes_symlink)
{
/* If the local node was not modified, we continue as usual, if
modified we want a tree conflict just like how we would handle
it when receiving a delete + add (aka "replace") */
if (fb->local_prop_mods)
modified = TRUE;
else
SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db,
fb->local_abspath,
FALSE, scratch_pool));
}
if (modified)
{
if (!fb->edit_conflict)
fb->edit_conflict = svn_wc__conflict_skel_create(fb->pool);
SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(
fb->edit_conflict,
eb->db, fb->local_abspath,
svn_wc_conflict_reason_edited,
svn_wc_conflict_action_replace,
NULL,
fb->pool, scratch_pool));
SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton,
fb->local_abspath, fb->old_repos_relpath,
fb->old_revision, fb->new_repos_relpath,
svn_node_file, svn_node_file,
NULL, fb->pool, scratch_pool));
/* Create a copy of the existing (pre update) BASE node in WORKING,
mark a tree conflict and handle the rest of the update as
shadowed */
SVN_ERR(svn_wc__db_op_make_copy(eb->db, fb->local_abspath,
fb->edit_conflict, NULL,
scratch_pool));
do_notification(eb, fb->local_abspath, svn_node_file,
svn_wc_notify_tree_conflict, scratch_pool);
/* Ok, we introduced a replacement, so we can now handle the rest
as a normal shadowed update */
fb->shadowed = TRUE;
fb->add_existed = FALSE;
fb->already_notified = TRUE;
}
}
return SVN_NO_ERROR;
}
/* Perform the actual merge of file changes between an original file,
identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file
identified by NEW_CHECKSUM.
Merge the result into LOCAL_ABSPATH, which is part of the working copy
identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming
the intermediate files.
The rest of the arguments are passed to svn_wc__internal_merge().
*/
svn_error_t *
svn_wc__perform_file_merge(svn_skel_t **work_items,
svn_skel_t **conflict_skel,
svn_boolean_t *found_conflict,
svn_wc__db_t *db,
const char *local_abspath,
const char *wri_abspath,
const svn_checksum_t *new_checksum,
const svn_checksum_t *original_checksum,
apr_hash_t *old_actual_props,
const apr_array_header_t *ext_patterns,
svn_revnum_t old_revision,
svn_revnum_t target_revision,
const apr_array_header_t *propchanges,
const char *diff3_cmd,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
/* Actual file exists and has local mods:
Now we need to let loose svn_wc__internal_merge() 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 = "";
const char *new_pristine_abspath;
enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged;
svn_skel_t *work_item;
*work_items = NULL;
SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath,
db, wri_abspath, new_checksum,
scratch_pool, scratch_pool));
/* 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 (ext_patterns && ext_patterns->nelts)
{
svn_path_splitext(NULL, &path_ext, local_abspath, scratch_pool);
if (! (*path_ext && svn_cstring_match_glob_list(path_ext, ext_patterns)))
path_ext = "";
}
/* old_revision can be invalid when the conflict is against a
local addition */
if (!SVN_IS_VALID_REVNUM(old_revision))
old_revision = 0;
oldrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s",
old_revision,
*path_ext ? "." : "",
*path_ext ? path_ext : "");
newrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s",
target_revision,
*path_ext ? "." : "",
*path_ext ? path_ext : "");
mine_str = apr_psprintf(scratch_pool, ".mine%s%s",
*path_ext ? "." : "",
*path_ext ? path_ext : "");
if (! original_checksum)
{
SVN_ERR(get_empty_tmp_file(&merge_left, db, wri_abspath,
result_pool, scratch_pool));
delete_left = TRUE;
}
else
SVN_ERR(svn_wc__db_pristine_get_path(&merge_left, db, wri_abspath,
original_checksum,
result_pool, scratch_pool));
/* 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__internal_merge(&work_item,
conflict_skel,
&merge_outcome,
db,
merge_left,
new_pristine_abspath,
local_abspath,
wri_abspath,
oldrev_str, newrev_str, mine_str,
old_actual_props,
FALSE /* dry_run */,
diff3_cmd, NULL, propchanges,
cancel_func, cancel_baton,
result_pool, scratch_pool));
*work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
*found_conflict = (merge_outcome == svn_wc_merge_conflict);
/* If we created a temporary left merge file, get rid of it. */
if (delete_left)
{
SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, wri_abspath,
merge_left,
result_pool, scratch_pool));
*work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
}
return SVN_NO_ERROR;
}
/* 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.
*
* ### transitional: installation of the working file will be handled
* ### by the *INSTALL_PRISTINE flag.
*
* 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 the parent directory's work queue.
*
* Set *CONTENT_STATE to the state of the contents after the
* installation.
*
* Return values are allocated in RESULT_POOL and temporary allocations
* are performed in SCRATCH_POOL.
*/
static svn_error_t *
merge_file(svn_skel_t **work_items,
svn_skel_t **conflict_skel,
svn_boolean_t *install_pristine,
const char **install_from,
svn_wc_notify_state_t *content_state,
struct file_baton *fb,
apr_hash_t *actual_props,
apr_time_t last_changed_date,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
struct edit_baton *eb = fb->edit_baton;
struct dir_baton *pb = fb->dir_baton;
svn_boolean_t is_locally_modified;
svn_boolean_t found_text_conflict = FALSE;
SVN_ERR_ASSERT(! fb->shadowed
&& ! fb->obstruction_found
&& ! fb->edit_obstructed);
/*
When this function is called on file F, we assume the following
things are true:
- The new pristine text of F is present in the pristine store
iff FB->NEW_TEXT_BASE_SHA1_CHECKSUM is not NULL.
- The WC metadata still reflects the old version of F.
(We can still access the old pristine base text of F.)
The goal is to update the local working copy of F to reflect
the changes received from the repository, preserving any local
modifications.
*/
*work_items = NULL;
*install_pristine = FALSE;
*install_from = NULL;
/* Start by splitting the file path, getting an access baton for the parent,
and an entry for the file if any. */
/* 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.
*/
if (fb->adding_file && !fb->add_existed)
{
is_locally_modified = FALSE; /* There is no file: Don't check */
}
else
{
/* The working file is not an obstruction.
So: is the file modified, relative to its ORIGINAL pristine?
This function sets is_locally_modified to FALSE for
files that do not exist and for directories. */
SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified,
eb->db, fb->local_abspath,
FALSE /* exact_comparison */,
scratch_pool));
}
/* For 'textual' merging, we use the following system:
When a file is modified and we have a new BASE:
- For text files
* svn_wc_merge uses diff3
* possibly makes backups and marks files as conflicted.
- For binary files
* svn_wc_merge makes backups and marks files as conflicted.
If a file is not modified and we have a new BASE:
* Install from pristine.
If we have property changes related to magic properties or if the
svn:keywords property is set:
* Retranslate from the working file.
*/
if (! is_locally_modified
&& fb->new_text_base_sha1_checksum)
{
/* 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. */
*install_pristine = TRUE;
}
else if (fb->new_text_base_sha1_checksum)
{
/* Actual file exists and has local mods:
Now we need to let loose svn_wc__merge_internal() to merge
the textual changes into the working file. */
SVN_ERR(svn_wc__perform_file_merge(work_items,
conflict_skel,
&found_text_conflict,
eb->db,
fb->local_abspath,
pb->local_abspath,
fb->new_text_base_sha1_checksum,
fb->add_existed
? NULL
: fb->original_checksum,
actual_props,
eb->ext_patterns,
fb->old_revision,
*eb->target_revision,
fb->propchanges,
eb->diff3_cmd,
eb->cancel_func, eb->cancel_baton,
result_pool, scratch_pool));
} /* end: working file exists and has mods */
else
{
/* There is no new text base, but let's see if the working file needs
to be updated for any other reason. */
apr_hash_t *keywords;
/* Determine if any of the propchanges are the "magic" ones that
might require changing the working file. */
svn_boolean_t magic_props_changed;
magic_props_changed = svn_wc__has_magic_property(fb->propchanges);
SVN_ERR(svn_wc__get_translate_info(NULL, NULL,
&keywords,
NULL,
eb->db, fb->local_abspath,
actual_props, TRUE,
scratch_pool, scratch_pool));
if (magic_props_changed || keywords)
{
/* 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... */
if (is_locally_modified)
{
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__internal_translated_file(
&tmptext, fb->local_abspath, eb->db, fb->local_abspath,
SVN_WC_TRANSLATE_TO_NF
| SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP,
eb->cancel_func, eb->cancel_baton,
result_pool, scratch_pool));
/* We always want to reinstall the working file if the magic
properties have changed, or there are any keywords present.
Note that TMPTEXT might actually refer to the working file
itself (the above function skips a detranslate when not
required). This is acceptable, as we will (re)translate
according to the new properties into a temporary file (from
the working file), and then rename the temp into place. Magic!
*/
*install_pristine = TRUE;
*install_from = tmptext;
}
else
{
/* Use our existing 'copy' from the pristine store instead
of making a new copy. This way we can use the standard code
to update the recorded size and modification time.
(Issue #3842) */
*install_pristine = TRUE;
}
}
}
/* Set the returned content state. */
if (found_text_conflict)
*content_state = svn_wc_notify_state_conflicted;
else if (fb->new_text_base_sha1_checksum)
{
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;
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_md5_digest,
apr_pool_t *pool)
{
struct file_baton *fb = file_baton;
struct dir_baton *pdb = fb->dir_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_md5_checksum = NULL;
apr_hash_t *new_base_props = NULL;
apr_hash_t *new_actual_props = NULL;
apr_array_header_t *entry_prop_changes;
apr_array_header_t *dav_prop_changes;
apr_array_header_t *regular_prop_changes;
apr_hash_t *current_base_props = NULL;
apr_hash_t *current_actual_props = NULL;
apr_hash_t *local_actual_props = NULL;
svn_skel_t *all_work_items = NULL;
svn_skel_t *conflict_skel = NULL;
svn_skel_t *work_item;
apr_pool_t *scratch_pool = fb->pool; /* Destroyed at function exit */
svn_boolean_t keep_recorded_info = FALSE;
const svn_checksum_t *new_checksum;
apr_array_header_t *iprops = NULL;
if (fb->skip_this)
{
svn_pool_destroy(fb->pool);
SVN_ERR(maybe_release_dir_info(pdb));
return SVN_NO_ERROR;
}
if (fb->edited)
conflict_skel = fb->edit_conflict;
if (expected_md5_digest)
SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
expected_md5_digest, scratch_pool));
if (fb->new_text_base_md5_checksum && expected_md5_checksum
&& !svn_checksum_match(expected_md5_checksum,
fb->new_text_base_md5_checksum))
return svn_error_trace(
svn_checksum_mismatch_err(expected_md5_checksum,
fb->new_text_base_md5_checksum,
scratch_pool,
_("Checksum mismatch for '%s'"),
svn_dirent_local_style(
fb->local_abspath, pool)));
/* Gather the changes for each kind of property. */
SVN_ERR(svn_categorize_props(fb->propchanges, &entry_prop_changes,
&dav_prop_changes, &regular_prop_changes,
scratch_pool));
/* Extract the changed_* and lock state information. */
{
svn_revnum_t new_changed_rev;
apr_time_t new_changed_date;
const char *new_changed_author;
SVN_ERR(accumulate_last_change(&new_changed_rev,
&new_changed_date,
&new_changed_author,
entry_prop_changes,
scratch_pool, scratch_pool));
if (SVN_IS_VALID_REVNUM(new_changed_rev))
fb->changed_rev = new_changed_rev;
if (new_changed_date != 0)
fb->changed_date = new_changed_date;
if (new_changed_author != NULL)
fb->changed_author = new_changed_author;
}
/* Determine whether the file has become unlocked. */
{
int i;
lock_state = svn_wc_notify_lock_state_unchanged;
for (i = 0; i < entry_prop_changes->nelts; ++i)
{
const svn_prop_t *prop
= &APR_ARRAY_IDX(entry_prop_changes, i, svn_prop_t);
/* If we see a change to the LOCK_TOKEN entry prop, then the only
possible change is its REMOVAL. Thus, the lock has been removed,
and we should likewise remove our cached copy of it. */
if (! strcmp(prop->name, SVN_PROP_ENTRY_LOCK_TOKEN))
{
/* If we lose the lock, but not because we are switching to
another url, remove the state lock from the wc */
if (! eb->switch_repos_relpath
|| strcmp(fb->new_repos_relpath, fb->old_repos_relpath) == 0)
{
SVN_ERR_ASSERT(prop->value == NULL);
SVN_ERR(svn_wc__db_lock_remove(eb->db, fb->local_abspath, NULL,
scratch_pool));
lock_state = svn_wc_notify_lock_state_unlocked;
}
break;
}
}
}
/* 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. */
/* Write log commands to merge REGULAR_PROPS into the existing
properties of FB->LOCAL_ABSPATH. Update *PROP_STATE to reflect
the result of the regular prop merge.
BASE_PROPS and WORKING_PROPS are hashes of the base and
working props of the file; if NULL they are read from the wc. */
/* ### some of this feels like voodoo... */
if ((!fb->adding_file || fb->add_existed)
&& !fb->shadowed)
SVN_ERR(svn_wc__get_actual_props(&local_actual_props,
eb->db, fb->local_abspath,
scratch_pool, scratch_pool));
if (local_actual_props == NULL)
local_actual_props = apr_hash_make(scratch_pool);
if (fb->add_existed)
{
/* This node already exists. Grab the current pristine properties. */
SVN_ERR(svn_wc__db_read_pristine_props(&current_base_props,
eb->db, fb->local_abspath,
scratch_pool, scratch_pool));
current_actual_props = local_actual_props;
}
else if (!fb->adding_file)
{
/* Get the BASE properties for proper merging. */
SVN_ERR(svn_wc__db_base_get_props(&current_base_props,
eb->db, fb->local_abspath,
scratch_pool, scratch_pool));
current_actual_props = local_actual_props;
}
/* Note: even if the node existed before, it may not have
pristine props (e.g a local-add) */
if (current_base_props == NULL)
current_base_props = apr_hash_make(scratch_pool);
/* And new nodes need an empty set of ACTUAL props. */
if (current_actual_props == NULL)
current_actual_props = apr_hash_make(scratch_pool);
prop_state = svn_wc_notify_state_unknown;
if (! fb->shadowed)
{
svn_boolean_t install_pristine;
const char *install_from = NULL;
/* Merge the 'regular' props into the existing working proplist. */
/* 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. */
new_base_props = svn_prop__patch(current_base_props, regular_prop_changes,
scratch_pool);
SVN_ERR(svn_wc__merge_props(&conflict_skel,
&prop_state,
&new_actual_props,
eb->db,
fb->local_abspath,
NULL /* server_baseprops (update, not merge) */,
current_base_props,
current_actual_props,
regular_prop_changes, /* propchanges */
scratch_pool,
scratch_pool));
/* We will ALWAYS have properties to save (after a not-dry-run merge). */
SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL);
/* Merge the text. This will queue some additional work. */
if (!fb->obstruction_found && !fb->edit_obstructed)
{
svn_error_t *err;
err = merge_file(&work_item, &conflict_skel,
&install_pristine, &install_from,
&content_state, fb, current_actual_props,
fb->changed_date, scratch_pool, scratch_pool);
if (err && err->apr_err == SVN_ERR_WC_PATH_ACCESS_DENIED)
{
if (eb->notify_func)
{
svn_wc_notify_t *notify =svn_wc_create_notify(
fb->local_abspath,
svn_wc_notify_update_skip_access_denied,
scratch_pool);
notify->kind = svn_node_file;
notify->err = err;
eb->notify_func(eb->notify_baton, notify, scratch_pool);
}
svn_error_clear(err);
SVN_ERR(remember_skipped_tree(eb, fb->local_abspath,
scratch_pool));
fb->skip_this = TRUE;
svn_pool_destroy(fb->pool);
SVN_ERR(maybe_release_dir_info(pdb));
return SVN_NO_ERROR;
}
else
SVN_ERR(err);
all_work_items = svn_wc__wq_merge(all_work_items, work_item,
scratch_pool);
}
else
{
install_pristine = FALSE;
if (fb->new_text_base_sha1_checksum)
content_state = svn_wc_notify_state_changed;
else
content_state = svn_wc_notify_state_unchanged;
}
if (install_pristine)
{
svn_boolean_t record_fileinfo;
/* If we are installing from the pristine contents, then go ahead and
record the fileinfo. That will be the "proper" values. Installing
from some random file means the fileinfo does NOT correspond to
the pristine (in which case, the fileinfo will be cleared for
safety's sake). */
record_fileinfo = (install_from == NULL);
SVN_ERR(svn_wc__wq_build_file_install(&work_item,
eb->db,
fb->local_abspath,
install_from,
eb->use_commit_times,
record_fileinfo,
scratch_pool, scratch_pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item,
scratch_pool);
}
else if (lock_state == svn_wc_notify_lock_state_unlocked
&& !fb->obstruction_found)
{
/* If a lock was removed and we didn't update the text contents, we
might need to set the file read-only.
Note: this will also update the executable flag, but ... meh. */
SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, eb->db,
fb->local_abspath,
scratch_pool, scratch_pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item,
scratch_pool);
}
if (! install_pristine
&& (content_state == svn_wc_notify_state_unchanged))
{
/* It is safe to keep the current recorded timestamp and size */
keep_recorded_info = TRUE;
}
/* Clean up any temporary files. */
/* Remove the INSTALL_FROM file, as long as it doesn't refer to the
working file. */
if (install_from != NULL
&& strcmp(install_from, fb->local_abspath) != 0)
{
SVN_ERR(svn_wc__wq_build_file_remove(&work_item, eb->db,
fb->local_abspath, install_from,
scratch_pool, scratch_pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item,
scratch_pool);
}
}
else
{
/* Adding or updating a BASE node under a locally added node. */
apr_hash_t *fake_actual_props;
if (fb->adding_file)
fake_actual_props = apr_hash_make(scratch_pool);
else
fake_actual_props = current_base_props;
/* Store the incoming props (sent as propchanges) in new_base_props
and create a set of new actual props to use for notifications */
new_base_props = svn_prop__patch(current_base_props, regular_prop_changes,
scratch_pool);
SVN_ERR(svn_wc__merge_props(&conflict_skel,
&prop_state,
&new_actual_props,
eb->db,
fb->local_abspath,
NULL /* server_baseprops (not merging) */,
current_base_props /* pristine_props */,
fake_actual_props /* actual_props */,
regular_prop_changes, /* propchanges */
scratch_pool,
scratch_pool));
if (fb->new_text_base_sha1_checksum)
content_state = svn_wc_notify_state_changed;
else
content_state = svn_wc_notify_state_unchanged;
}
/* Insert/replace the BASE node with all of the new metadata. */
/* Set the 'checksum' column of the file's BASE_NODE row to
* NEW_TEXT_BASE_SHA1_CHECKSUM. The pristine text identified by that
* checksum is already in the pristine store. */
new_checksum = fb->new_text_base_sha1_checksum;
/* If we don't have a NEW checksum, then the base must not have changed.
Just carry over the old checksum. */
if (new_checksum == NULL)
new_checksum = fb->original_checksum;
if (conflict_skel)
{
SVN_ERR(complete_conflict(conflict_skel,
fb->edit_baton,
fb->local_abspath,
fb->old_repos_relpath,
fb->old_revision,
fb->new_repos_relpath,
svn_node_file, svn_node_file,
fb->dir_baton->deletion_conflicts
? svn_hash_gets(
fb->dir_baton->deletion_conflicts,
fb->name)
: NULL,
fb->pool, scratch_pool));
SVN_ERR(svn_wc__conflict_create_markers(&work_item,
eb->db, fb->local_abspath,
conflict_skel,
scratch_pool, scratch_pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item,
scratch_pool);
}
/* Any inherited props to be set set for this base node? */
if (eb->wcroot_iprops)
{
iprops = svn_hash_gets(eb->wcroot_iprops, fb->local_abspath);
/* close_edit may also update iprops for switched nodes, catching
those for which close_directory is never called (e.g. a switch
with no changes). So as a minor optimization we remove any
iprops from the hash so as not to set them again in
close_edit. */
if (iprops)
svn_hash_sets(eb->wcroot_iprops, fb->local_abspath, NULL);
}
SVN_ERR(svn_wc__db_base_add_file(eb->db, fb->local_abspath,
eb->wcroot_abspath,
fb->new_repos_relpath,
eb->repos_root, eb->repos_uuid,
*eb->target_revision,
new_base_props,
fb->changed_rev,
fb->changed_date,
fb->changed_author,
new_checksum,
(dav_prop_changes->nelts > 0)
? svn_prop_array_to_hash(
dav_prop_changes,
scratch_pool)
: NULL,
(fb->add_existed && fb->adding_file),
(! fb->shadowed) && new_base_props,
new_actual_props,
iprops,
keep_recorded_info,
(fb->shadowed && fb->obstruction_found),
conflict_skel,
all_work_items,
scratch_pool));
if (conflict_skel && eb->conflict_func)
SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, fb->local_abspath,
svn_node_file,
conflict_skel,
NULL /* merge_options */,
eb->conflict_func,
eb->conflict_baton,
eb->cancel_func,
eb->cancel_baton,
scratch_pool));
/* Deal with the WORKING tree, based on updates to the BASE tree. */
svn_hash_sets(fb->dir_baton->not_present_nodes, fb->name, NULL);
/* Send a notification to the callback function. (Skip notifications
about files which were already notified for another reason.) */
if (eb->notify_func && !fb->already_notified
&& (fb->edited || lock_state == svn_wc_notify_lock_state_unlocked))
{
svn_wc_notify_t *notify;
svn_wc_notify_action_t action = svn_wc_notify_update_update;
if (fb->edited)
{
if (fb->shadowed || fb->edit_obstructed)
action = fb->adding_file
? svn_wc_notify_update_shadowed_add
: svn_wc_notify_update_shadowed_update;
else if (fb->obstruction_found || fb->add_existed)
{
if (content_state != svn_wc_notify_state_conflicted)
action = svn_wc_notify_exists;
}
else if (fb->adding_file)
{
action = svn_wc_notify_update_add;
}
}
else
{
SVN_ERR_ASSERT(lock_state == svn_wc_notify_lock_state_unlocked);
action = svn_wc_notify_update_broken_lock;
}
/* If the file was moved-away, notify for the moved-away node.
* The original location only had its BASE info changed and
* we don't usually notify about such changes. */
notify = svn_wc_create_notify(fb->local_abspath, action, scratch_pool);
notify->kind = svn_node_file;
notify->content_state = content_state;
notify->prop_state = prop_state;
notify->lock_state = lock_state;
notify->revision = *eb->target_revision;
notify->old_revision = fb->old_revision;
/* Fetch the mimetype from the actual properties */
notify->mime_type = svn_prop_get_value(new_actual_props,
SVN_PROP_MIME_TYPE);
eb->notify_func(eb->notify_baton, notify, scratch_pool);
}
svn_pool_destroy(fb->pool); /* Destroy scratch_pool */
/* We have one less referrer to the directory */
SVN_ERR(maybe_release_dir_info(pdb));
return SVN_NO_ERROR;
}
/* Implements svn_wc__proplist_receiver_t.
* Check for the presence of an svn:keywords property and queues an install_file
* work queue item if present. Thus, when the work queue is run to complete the
* switch operation, all files with keywords will go through the translation
* process so URLs etc are updated. */
static svn_error_t *
update_keywords_after_switch_cb(void *baton,
const char *local_abspath,
apr_hash_t *props,
apr_pool_t *scratch_pool)
{
struct edit_baton *eb = baton;
svn_string_t *propval;
svn_boolean_t modified;
svn_boolean_t record_fileinfo;
svn_skel_t *work_items;
const char *install_from;
propval = svn_hash_gets(props, SVN_PROP_KEYWORDS);
if (!propval)
return SVN_NO_ERROR;
SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db,
local_abspath, FALSE,
scratch_pool));
if (modified)
{
const char *temp_dir_abspath;
svn_stream_t *working_stream;
svn_stream_t *install_from_stream;
SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, eb->db,
local_abspath, scratch_pool,
scratch_pool));
SVN_ERR(svn_stream_open_readonly(&working_stream, local_abspath,
scratch_pool, scratch_pool));
SVN_ERR(svn_stream_open_unique(&install_from_stream, &install_from,
temp_dir_abspath, svn_io_file_del_none,
scratch_pool, scratch_pool));
SVN_ERR(svn_stream_copy3(working_stream, install_from_stream,
eb->cancel_func, eb->cancel_baton,
scratch_pool));
record_fileinfo = FALSE;
}
else
{
install_from = NULL;
record_fileinfo = TRUE;
}
SVN_ERR(svn_wc__wq_build_file_install(&work_items, eb->db, local_abspath,
install_from,
eb->use_commit_times,
record_fileinfo,
scratch_pool, scratch_pool));
if (install_from)
{
svn_skel_t *work_item;
SVN_ERR(svn_wc__wq_build_file_remove(&work_item, eb->db,
local_abspath, install_from,
scratch_pool, scratch_pool));
work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
}
SVN_ERR(svn_wc__db_wq_add(eb->db, local_abspath, work_items,
scratch_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;
apr_pool_t *scratch_pool = eb->pool;
/* The editor didn't even open the root; we have to take care of
some cleanup stuffs. */
if (! eb->root_opened
&& *eb->target_basename == '\0')
{
/* We need to "un-incomplete" the root directory. */
SVN_ERR(svn_wc__db_temp_op_end_directory_update(eb->db,
eb->anchor_abspath,
scratch_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__db_op_bump_revisions_post_update(eb->db,
eb->target_abspath,
eb->requested_depth,
eb->switch_repos_relpath,
eb->repos_root,
eb->repos_uuid,
*(eb->target_revision),
eb->skipped_trees,
eb->wcroot_iprops,
! eb->edited,
eb->notify_func,
eb->notify_baton,
eb->pool));
if (*eb->target_basename != '\0')
{
svn_wc__db_status_t status;
svn_error_t *err;
/* Note: we are fetching information about the *target*, not anchor.
There is no guarantee that the target has a BASE node.
For example:
The node was not present in BASE, but locally-added, and the
update did not create a new BASE node "under" the local-add.
If there is no BASE node for the target, then we certainly don't
have to worry about removing it. */
err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
eb->db, eb->target_abspath,
scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
svn_error_clear(err);
}
else if (status == svn_wc__db_status_excluded)
{
/* There is a small chance that the explicit target of an update/
switch is gone in the repository, in that specific case the
node hasn't been re-added to the BASE tree by this update.
If so, we should get rid of this excluded node now. */
SVN_ERR(svn_wc__db_base_remove(eb->db, eb->target_abspath,
TRUE, FALSE, FALSE,
SVN_INVALID_REVNUM,
NULL, NULL, scratch_pool));
}
}
}
/* Update keywords in switched files.
GOTO #1975 (the year of the Altair 8800). */
if (eb->switch_repos_relpath)
{
svn_depth_t depth;
if (eb->requested_depth > svn_depth_empty)
depth = eb->requested_depth;
else
depth = svn_depth_infinity;
SVN_ERR(svn_wc__db_read_props_streamily(eb->db,
eb->target_abspath,
depth,
FALSE, /* pristine */
NULL, /* changelists */
update_keywords_after_switch_cb,
eb,
eb->cancel_func,
eb->cancel_baton,
scratch_pool));
}
/* The edit is over: run the wq with proper cancel support,
but first kill the handler that would run it on the pool
cleanup at the end of this function. */
apr_pool_cleanup_kill(eb->pool, eb, cleanup_edit_baton);
SVN_ERR(svn_wc__wq_run(eb->db, eb->wcroot_abspath,
eb->cancel_func, eb->cancel_baton,
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__db_t *db,
const char *anchor_abspath,
const char *target_basename,
apr_hash_t *wcroot_iprops,
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_boolean_t adds_as_modification,
svn_boolean_t server_performs_filtering,
svn_boolean_t clean_checkout,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_dirents_func_t fetch_dirents_func,
void *fetch_dirents_baton,
svn_wc_conflict_resolver_func2_t conflict_func,
void *conflict_baton,
svn_wc_external_update_t external_func,
void *external_baton,
const char *diff3_cmd,
const apr_array_header_t *preserved_exts,
const svn_delta_editor_t **editor,
void **edit_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
struct edit_baton *eb;
void *inner_baton;
apr_pool_t *edit_pool = svn_pool_create(result_pool);
svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
const svn_delta_editor_t *inner_editor;
const char *repos_root, *repos_uuid;
struct svn_wc__shim_fetch_baton_t *sfb;
svn_delta_shim_callbacks_t *shim_callbacks =
svn_delta_shim_callbacks_default(edit_pool);
/* An unknown depth can't be sticky. */
if (depth == svn_depth_unknown)
depth_is_sticky = FALSE;
/* Get the anchor's repository root and uuid. The anchor must already exist
in BASE. */
SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, &repos_root,
&repos_uuid, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
db, anchor_abspath,
result_pool, scratch_pool));
/* With WC-NG we need a valid repository root */
SVN_ERR_ASSERT(repos_root != NULL && repos_uuid != NULL);
/* Disallow a switch operation to change the repository root of the target,
if that is known. */
if (switch_url && !svn_uri__is_ancestor(repos_root, switch_url))
return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL,
_("'%s'\nis not the same repository as\n'%s'"),
switch_url, repos_root);
/* Construct an edit baton. */
eb = apr_pcalloc(edit_pool, sizeof(*eb));
eb->pool = edit_pool;
eb->use_commit_times = use_commit_times;
eb->target_revision = target_revision;
eb->repos_root = repos_root;
eb->repos_uuid = repos_uuid;
eb->db = db;
eb->target_basename = target_basename;
eb->anchor_abspath = anchor_abspath;
eb->wcroot_iprops = wcroot_iprops;
SVN_ERR(svn_wc__db_get_wcroot(&eb->wcroot_abspath, db, anchor_abspath,
edit_pool, scratch_pool));
if (switch_url)
eb->switch_repos_relpath =
svn_uri_skip_ancestor(repos_root, switch_url, scratch_pool);
else
eb->switch_repos_relpath = NULL;
if (svn_path_is_empty(target_basename))
eb->target_abspath = eb->anchor_abspath;
else
eb->target_abspath = svn_dirent_join(eb->anchor_abspath, target_basename,
edit_pool);
eb->requested_depth = depth;
eb->depth_is_sticky = depth_is_sticky;
eb->notify_func = notify_func;
eb->notify_baton = notify_baton;
eb->external_func = external_func;
eb->external_baton = external_baton;
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->allow_unver_obstructions = allow_unver_obstructions;
eb->adds_as_modification = adds_as_modification;
eb->clean_checkout = clean_checkout;
eb->skipped_trees = apr_hash_make(edit_pool);
eb->dir_dirents = apr_hash_make(edit_pool);
eb->ext_patterns = preserved_exts;
apr_pool_cleanup_register(edit_pool, eb, cleanup_edit_baton,
apr_pool_cleanup_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;
if (!depth_is_sticky
&& depth != svn_depth_unknown
&& svn_depth_empty <= depth && depth < svn_depth_infinity
&& fetch_dirents_func)
{
/* We are asked to perform an update at a depth less than the ambient
depth. In this case the update won't describe additions that would
have been reported if we updated at the ambient depth. */
svn_error_t *err;
svn_node_kind_t dir_kind;
svn_wc__db_status_t dir_status;
const char *dir_repos_relpath;
svn_depth_t dir_depth;
/* we have to do this on the target of the update, not the anchor */
err = svn_wc__db_base_get_info(&dir_status, &dir_kind, NULL,
&dir_repos_relpath, NULL, NULL, NULL,
NULL, NULL, &dir_depth, NULL, NULL, NULL,
NULL, NULL, NULL,
db, eb->target_abspath,
scratch_pool, scratch_pool);
if (!err
&& dir_kind == svn_node_dir
&& dir_status == svn_wc__db_status_normal)
{
if (dir_depth > depth)
{
apr_hash_t *dirents;
/* If we switch, we should look at the new relpath */
if (eb->switch_repos_relpath)
dir_repos_relpath = eb->switch_repos_relpath;
SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents,
repos_root, dir_repos_relpath,
edit_pool, scratch_pool));
if (dirents != NULL && apr_hash_count(dirents))
svn_hash_sets(eb->dir_dirents,
apr_pstrdup(edit_pool, dir_repos_relpath),
dirents);
}
if (depth == svn_depth_immediates)
{
/* Worst case scenario of issue #3569 fix: We have to do the
same for all existing subdirs, but then we check for
svn_depth_empty. */
const apr_array_header_t *children;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
SVN_ERR(svn_wc__db_base_get_children(&children, db,
eb->target_abspath,
scratch_pool,
iterpool));
for (i = 0; i < children->nelts; i++)
{
const char *child_abspath;
const char *child_name;
svn_pool_clear(iterpool);
child_name = APR_ARRAY_IDX(children, i, const char *);
child_abspath = svn_dirent_join(eb->target_abspath,
child_name, iterpool);
SVN_ERR(svn_wc__db_base_get_info(&dir_status, &dir_kind,
NULL, &dir_repos_relpath,
NULL, NULL, NULL, NULL,
NULL, &dir_depth, NULL,
NULL, NULL, NULL, NULL,
NULL,
db, child_abspath,
iterpool, iterpool));
if (dir_kind == svn_node_dir
&& dir_status == svn_wc__db_status_normal
&& dir_depth > svn_depth_empty)
{
apr_hash_t *dirents;
/* If we switch, we should look at the new relpath */
if (eb->switch_repos_relpath)
dir_repos_relpath = svn_relpath_join(
eb->switch_repos_relpath,
child_name, iterpool);
SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents,
repos_root, dir_repos_relpath,
edit_pool, iterpool));
if (dirents != NULL && apr_hash_count(dirents))
svn_hash_sets(eb->dir_dirents,
apr_pstrdup(edit_pool,
dir_repos_relpath),
dirents);
}
}
}
}
else if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
svn_error_clear(err);
else
SVN_ERR(err);
}
/* 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.) */
if (!server_performs_filtering
&& !depth_is_sticky)
SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
&inner_baton,
db,
anchor_abspath,
target_basename,
inner_editor,
inner_baton,
result_pool));
SVN_ERR(svn_delta_get_cancellation_editor(cancel_func,
cancel_baton,
inner_editor,
inner_baton,
editor,
edit_baton,
result_pool));
sfb = apr_palloc(result_pool, sizeof(*sfb));
sfb->db = db;
sfb->base_abspath = eb->anchor_abspath;
sfb->fetch_base = TRUE;
shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
shim_callbacks->fetch_baton = sfb;
SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
NULL, NULL, shim_callbacks,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__get_update_editor(const svn_delta_editor_t **editor,
void **edit_baton,
svn_revnum_t *target_revision,
svn_wc_context_t *wc_ctx,
const char *anchor_abspath,
const char *target_basename,
apr_hash_t *wcroot_iprops,
svn_boolean_t use_commit_times,
svn_depth_t depth,
svn_boolean_t depth_is_sticky,
svn_boolean_t allow_unver_obstructions,
svn_boolean_t adds_as_modification,
svn_boolean_t server_performs_filtering,
svn_boolean_t clean_checkout,
const char *diff3_cmd,
const apr_array_header_t *preserved_exts,
svn_wc_dirents_func_t fetch_dirents_func,
void *fetch_dirents_baton,
svn_wc_conflict_resolver_func2_t conflict_func,
void *conflict_baton,
svn_wc_external_update_t external_func,
void *external_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return make_editor(target_revision, wc_ctx->db, anchor_abspath,
target_basename, wcroot_iprops, use_commit_times,
NULL, depth, depth_is_sticky, allow_unver_obstructions,
adds_as_modification, server_performs_filtering,
clean_checkout,
notify_func, notify_baton,
cancel_func, cancel_baton,
fetch_dirents_func, fetch_dirents_baton,
conflict_func, conflict_baton,
external_func, external_baton,
diff3_cmd, preserved_exts, editor, edit_baton,
result_pool, scratch_pool);
}
svn_error_t *
svn_wc__get_switch_editor(const svn_delta_editor_t **editor,
void **edit_baton,
svn_revnum_t *target_revision,
svn_wc_context_t *wc_ctx,
const char *anchor_abspath,
const char *target_basename,
const char *switch_url,
apr_hash_t *wcroot_iprops,
svn_boolean_t use_commit_times,
svn_depth_t depth,
svn_boolean_t depth_is_sticky,
svn_boolean_t allow_unver_obstructions,
svn_boolean_t server_performs_filtering,
const char *diff3_cmd,
const apr_array_header_t *preserved_exts,
svn_wc_dirents_func_t fetch_dirents_func,
void *fetch_dirents_baton,
svn_wc_conflict_resolver_func2_t conflict_func,
void *conflict_baton,
svn_wc_external_update_t external_func,
void *external_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, scratch_pool));
return make_editor(target_revision, wc_ctx->db, anchor_abspath,
target_basename, wcroot_iprops, use_commit_times,
switch_url,
depth, depth_is_sticky, allow_unver_obstructions,
FALSE /* adds_as_modification */,
server_performs_filtering,
FALSE /* clean_checkout */,
notify_func, notify_baton,
cancel_func, cancel_baton,
fetch_dirents_func, fetch_dirents_baton,
conflict_func, conflict_baton,
external_func, external_baton,
diff3_cmd, preserved_exts,
editor, edit_baton,
result_pool, scratch_pool);
}
/* ### Note that this function is completely different from the rest of the
update editor in what it updates. The update editor changes only BASE
and ACTUAL and this function just changes WORKING and ACTUAL.
In the entries world this function shared a lot of code with the
update editor but in the wonderful new WC-NG world it will probably
do more and more by itself and would be more logically grouped with
the add/copy functionality in adm_ops.c and copy.c. */
svn_error_t *
svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx,
const char *local_abspath,
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,
apr_pool_t *scratch_pool)
{
svn_wc__db_t *db = wc_ctx->db;
const char *dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
svn_wc__db_status_t status;
svn_node_kind_t kind;
const char *tmp_text_base_abspath;
svn_checksum_t *new_text_base_md5_checksum;
svn_checksum_t *new_text_base_sha1_checksum;
const char *source_abspath = NULL;
svn_skel_t *all_work_items = NULL;
svn_skel_t *work_item;
const char *repos_root_url;
const char *repos_uuid;
const char *original_repos_relpath;
svn_revnum_t changed_rev;
apr_time_t changed_date;
const char *changed_author;
svn_stream_t *tmp_base_contents;
svn_wc__db_install_data_t *install_data;
svn_error_t *err;
apr_pool_t *pool = scratch_pool;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(new_base_contents != NULL);
SVN_ERR_ASSERT(new_base_props != NULL);
/* We should have a write lock on this file's parent directory. */
SVN_ERR(svn_wc__write_check(db, dir_abspath, pool));
err = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL,
db, local_abspath, scratch_pool, scratch_pool);
if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
else if(err)
svn_error_clear(err);
else
switch (status)
{
case svn_wc__db_status_not_present:
case svn_wc__db_status_deleted:
break;
default:
return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
_("Node '%s' exists."),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, &repos_root_url,
&repos_uuid, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
db, dir_abspath, scratch_pool, scratch_pool));
switch (status)
{
case svn_wc__db_status_normal:
case svn_wc__db_status_added:
break;
case svn_wc__db_status_deleted:
return
svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL,
_("Can't add '%s' to a parent directory"
" scheduled for deletion"),
svn_dirent_local_style(local_abspath,
scratch_pool));
default:
return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err,
_("Can't find parent directory's node while"
" trying to add '%s'"),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
if (kind != svn_node_dir)
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
_("Can't schedule an addition of '%s'"
" below a not-directory node"),
svn_dirent_local_style(local_abspath,
scratch_pool));
/* Fabricate the anticipated new URL of the target and check the
copyfrom URL to be in the same repository. */
if (copyfrom_url != NULL)
{
/* Find the repository_root via the parent directory, which
is always versioned before this function is called */
if (!repos_root_url)
{
/* The parent is an addition, scan upwards to find the right info */
SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
&repos_root_url, &repos_uuid,
NULL, NULL, NULL, NULL,
wc_ctx->db, dir_abspath,
scratch_pool, scratch_pool));
}
SVN_ERR_ASSERT(repos_root_url);
original_repos_relpath =
svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool);
if (!original_repos_relpath)
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Copyfrom-url '%s' has different repository"
" root than '%s'"),
copyfrom_url, repos_root_url);
}
else
{
original_repos_relpath = NULL;
copyfrom_rev = SVN_INVALID_REVNUM; /* Just to be sure. */
}
/* Set CHANGED_* to reflect the entry props in NEW_BASE_PROPS, and
filter NEW_BASE_PROPS so it contains only regular props. */
{
apr_array_header_t *regular_props;
apr_array_header_t *entry_props;
SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_base_props, pool),
&entry_props, NULL, &regular_props,
pool));
/* Put regular props back into a hash table. */
new_base_props = svn_prop_array_to_hash(regular_props, pool);
/* Get the change_* info from the entry props. */
SVN_ERR(accumulate_last_change(&changed_rev,
&changed_date,
&changed_author,
entry_props, pool, pool));
}
/* Copy NEW_BASE_CONTENTS into a temporary file so our log can refer to
it, and set TMP_TEXT_BASE_ABSPATH to its path. Compute its
NEW_TEXT_BASE_MD5_CHECKSUM and NEW_TEXT_BASE_SHA1_CHECKSUM as we copy. */
if (copyfrom_url)
{
SVN_ERR(svn_wc__db_pristine_prepare_install(&tmp_base_contents,
&install_data,
&new_text_base_sha1_checksum,
&new_text_base_md5_checksum,
wc_ctx->db, local_abspath,
scratch_pool, scratch_pool));
}
else
{
const char *tmp_dir_abspath;
/* We are not installing a PRISTINE file, but we use the same code to
create whatever we want to install */
SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmp_dir_abspath,
db, dir_abspath,
scratch_pool, scratch_pool));
SVN_ERR(svn_stream_open_unique(&tmp_base_contents, &tmp_text_base_abspath,
tmp_dir_abspath, svn_io_file_del_none,
scratch_pool, scratch_pool));
new_text_base_sha1_checksum = NULL;
new_text_base_md5_checksum = NULL;
}
SVN_ERR(svn_stream_copy3(new_base_contents, tmp_base_contents,
cancel_func, cancel_baton, pool));
/* If the caller gave us a new working file, copy it to a safe (temporary)
location and set SOURCE_ABSPATH to that path. We'll then translate/copy
that into place after the node's state has been created. */
if (new_contents)
{
const char *temp_dir_abspath;
svn_stream_t *tmp_contents;
SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db,
local_abspath, pool, pool));
SVN_ERR(svn_stream_open_unique(&tmp_contents, &source_abspath,
temp_dir_abspath, svn_io_file_del_none,
pool, pool));
SVN_ERR(svn_stream_copy3(new_contents, tmp_contents,
cancel_func, cancel_baton, pool));
}
/* Install new text base for copied files. Added files do NOT have a
text base. */
if (copyfrom_url != NULL)
{
SVN_ERR(svn_wc__db_pristine_install(install_data,
new_text_base_sha1_checksum,
new_text_base_md5_checksum, pool));
}
else
{
/* ### There's something wrong around here. Sometimes (merge from a
foreign repository, at least) we are called with copyfrom_url =
NULL and an empty new_base_contents (and an empty set of
new_base_props). Why an empty "new base"?
That happens in merge_tests.py 54,87,88,89,143.
In that case, having been given this supposed "new base" file, we
copy it and calculate its checksum but do not install it. Why?
That must be wrong.
To crudely work around one issue with this, that we shouldn't
record a checksum in the database if we haven't installed the
corresponding pristine text, for now we'll just set the checksum
to NULL.
The proper solution is probably more like: the caller should pass
NULL for the missing information, and this function should learn to
handle that. */
new_text_base_sha1_checksum = NULL;
new_text_base_md5_checksum = NULL;
}
/* For added files without NEW_CONTENTS, then generate the working file
from the provided "pristine" contents. */
if (new_contents == NULL && copyfrom_url == NULL)
source_abspath = tmp_text_base_abspath;
{
svn_boolean_t record_fileinfo;
/* If new contents were provided, then we do NOT want to record the
file information. We assume the new contents do not match the
"proper" values for RECORDED_SIZE and RECORDED_TIME. */
record_fileinfo = (new_contents == NULL);
/* Install the working copy file (with appropriate translation) from
the appropriate source. SOURCE_ABSPATH will be NULL, indicating an
installation from the pristine (available for copied/moved files),
or it will specify a temporary file where we placed a "pristine"
(for an added file) or a detranslated local-mods file. */
SVN_ERR(svn_wc__wq_build_file_install(&work_item,
db, local_abspath,
source_abspath,
FALSE /* use_commit_times */,
record_fileinfo,
pool, pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
/* If we installed from somewhere besides the official pristine, then
it is a temporary file, which needs to be removed. */
if (source_abspath != NULL)
{
SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, local_abspath,
source_abspath,
pool, pool));
all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
}
}
SVN_ERR(svn_wc__db_op_copy_file(db, local_abspath,
new_base_props,
changed_rev,
changed_date,
changed_author,
original_repos_relpath,
original_repos_relpath ? repos_root_url
: NULL,
original_repos_relpath ? repos_uuid : NULL,
copyfrom_rev,
new_text_base_sha1_checksum,
TRUE,
new_props,
FALSE /* is_move */,
NULL /* conflict */,
all_work_items,
pool));
return svn_error_trace(svn_wc__wq_run(db, dir_abspath,
cancel_func, cancel_baton,
pool));
}
svn_error_t *
svn_wc__complete_directory_add(svn_wc_context_t *wc_ctx,
const char *local_abspath,
apr_hash_t *new_original_props,
const char *copyfrom_url,
svn_revnum_t copyfrom_rev,
apr_pool_t *scratch_pool)
{
svn_wc__db_status_t status;
svn_node_kind_t kind;
const char *original_repos_relpath;
const char *original_root_url;
const char *original_uuid;
svn_boolean_t had_props;
svn_boolean_t props_mod;
svn_revnum_t original_revision;
svn_revnum_t changed_rev;
apr_time_t changed_date;
const char *changed_author;
SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
&original_repos_relpath, &original_root_url,
&original_uuid, &original_revision, NULL, NULL,
NULL, NULL, NULL, NULL, &had_props, &props_mod,
NULL, NULL, NULL,
wc_ctx->db, local_abspath,
scratch_pool, scratch_pool));
if (status != svn_wc__db_status_added
|| kind != svn_node_dir
|| had_props
|| props_mod
|| !original_repos_relpath)
{
return svn_error_createf(
SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
_("'%s' is not an unmodified copied directory"),
svn_dirent_local_style(local_abspath, scratch_pool));
}
if (original_revision != copyfrom_rev
|| strcmp(copyfrom_url,
svn_path_url_add_component2(original_root_url,
original_repos_relpath,
scratch_pool)))
{
return svn_error_createf(
SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND, NULL,
_("Copyfrom '%s' doesn't match original location of '%s'"),
copyfrom_url,
svn_dirent_local_style(local_abspath, scratch_pool));
}
{
apr_array_header_t *regular_props;
apr_array_header_t *entry_props;
SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_original_props,
scratch_pool),
&entry_props, NULL, &regular_props,
scratch_pool));
/* Put regular props back into a hash table. */
new_original_props = svn_prop_array_to_hash(regular_props, scratch_pool);
/* Get the change_* info from the entry props. */
SVN_ERR(accumulate_last_change(&changed_rev,
&changed_date,
&changed_author,
entry_props, scratch_pool, scratch_pool));
}
return svn_error_trace(
svn_wc__db_op_copy_dir(wc_ctx->db, local_abspath,
new_original_props,
changed_rev, changed_date, changed_author,
original_repos_relpath, original_root_url,
original_uuid, original_revision,
NULL /* children */,
svn_depth_infinity,
FALSE /* is_move */,
NULL /* conflict */,
NULL /* work_items */,
scratch_pool));
}