blob: 3ccd2e7afc15124a8a34a9c6b5e23b96837315fb [file] [log] [blame]
/*
* adm_ops.c: routines for affecting working copy administrative
* information. NOTE: this code doesn't know where the adm
* info is actually stored. Instead, generic handles to
* adm data are requested via a reference to some PATH
* (PATH being a regular, non-administrative directory or
* file in the working copy).
*
* ====================================================================
* 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 <string.h>
#include <stdlib.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include <apr_time.h>
#include <apr_errno.h>
#include "svn_private_config.h"
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_hash.h"
#include "svn_wc.h"
#include "svn_io.h"
#include "svn_time.h"
#include "svn_sorts.h"
#include "wc.h"
#include "adm_files.h"
#include "conflicts.h"
#include "workqueue.h"
#include "private/svn_dep_compat.h"
#include "private/svn_sorts_private.h"
#include "private/svn_subr_private.h"
struct svn_wc_committed_queue_t
{
/* The pool in which ->queue is allocated. */
apr_pool_t *pool;
/* Mapping (const char *) wcroot_abspath to svn_wc__db_commit_queue_t * */
apr_hash_t *wc_queues;
};
typedef struct committed_queue_item_t
{
const char *local_abspath;
svn_boolean_t recurse; /* Use legacy recursion */
svn_boolean_t committed; /* Process the node as committed */
svn_boolean_t remove_lock; /* Remove existing lock on node */
svn_boolean_t remove_changelist; /* Remove changelist on node */
/* The pristine text checksum. NULL if the old value should be kept
and for directories */
const svn_checksum_t *new_sha1_checksum;
apr_hash_t *new_dav_cache; /* New DAV cache for the node */
} committed_queue_item_t;
apr_pool_t *
svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue)
{
return queue->pool;
}
apr_hash_t *
svn_wc__prop_array_to_hash(const apr_array_header_t *props,
apr_pool_t *result_pool)
{
int i;
apr_hash_t *prophash;
if (props == NULL || props->nelts == 0)
return NULL;
prophash = apr_hash_make(result_pool);
for (i = 0; i < props->nelts; i++)
{
const svn_prop_t *prop = APR_ARRAY_IDX(props, i, const svn_prop_t *);
if (prop->value != NULL)
svn_hash_sets(prophash, prop->name, prop->value);
}
return prophash;
}
svn_wc_committed_queue_t *
svn_wc_committed_queue_create(apr_pool_t *pool)
{
svn_wc_committed_queue_t *q;
q = apr_palloc(pool, sizeof(*q));
q->pool = pool;
q->wc_queues = apr_hash_make(pool);
return q;
}
svn_error_t *
svn_wc_queue_committed4(svn_wc_committed_queue_t *queue,
svn_wc_context_t *wc_ctx,
const char *local_abspath,
svn_boolean_t recurse,
svn_boolean_t is_committed,
const apr_array_header_t *wcprop_changes,
svn_boolean_t remove_lock,
svn_boolean_t remove_changelist,
const svn_checksum_t *sha1_checksum,
apr_pool_t *scratch_pool)
{
const char *wcroot_abspath;
svn_wc__db_commit_queue_t *db_queue;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
/* Use the same pool as the one QUEUE was allocated in,
to prevent lifetime issues. Intermediate operations
should use SCRATCH_POOL. */
SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath,
wc_ctx->db, local_abspath,
scratch_pool, scratch_pool));
db_queue = svn_hash_gets(queue->wc_queues, wcroot_abspath);
if (! db_queue)
{
wcroot_abspath = apr_pstrdup(queue->pool, wcroot_abspath);
SVN_ERR(svn_wc__db_create_commit_queue(&db_queue,
wc_ctx->db, wcroot_abspath,
queue->pool, scratch_pool));
svn_hash_sets(queue->wc_queues, wcroot_abspath, db_queue);
}
return svn_error_trace(
svn_wc__db_commit_queue_add(db_queue, local_abspath, recurse,
is_committed, remove_lock,
remove_changelist, sha1_checksum,
svn_wc__prop_array_to_hash(wcprop_changes,
queue->pool),
queue->pool, scratch_pool));
}
svn_error_t *
svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue,
svn_wc_context_t *wc_ctx,
svn_revnum_t new_revnum,
const char *rev_date,
const char *rev_author,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
apr_array_header_t *wcs;
int i;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_time_t new_date;
if (rev_date)
SVN_ERR(svn_time_from_cstring(&new_date, rev_date, iterpool));
else
new_date = 0;
/* Process the wc's in order of their paths. */
wcs = svn_sort__hash(queue->wc_queues, svn_sort_compare_items_as_paths,
scratch_pool);
for (i = 0; i < wcs->nelts; i++)
{
const svn_sort__item_t *sort_item
= &APR_ARRAY_IDX(wcs, i, svn_sort__item_t);
svn_wc__db_commit_queue_t *db_queue = sort_item->value;
svn_pool_clear(iterpool);
SVN_ERR(svn_wc__db_process_commit_queue(wc_ctx->db, db_queue,
new_revnum, new_date, rev_author,
iterpool));
}
/* Make sure nothing happens if this function is called again. */
apr_hash_clear(queue->wc_queues);
/* Ok; everything is committed now. Now we can start calling callbacks */
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
for (i = 0; i < wcs->nelts; i++)
{
const svn_sort__item_t *sort_item
= &APR_ARRAY_IDX(wcs, i, svn_sort__item_t);
const char *wcroot_abspath = sort_item->key;
svn_pool_clear(iterpool);
SVN_ERR(svn_wc__wq_run(wc_ctx->db, wcroot_abspath,
cancel_func, cancel_baton,
iterpool));
}
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
/* Schedule the single node at LOCAL_ABSPATH, of kind KIND, for addition in
* its parent directory in the WC. It will have the regular properties
* provided in PROPS, or none if that is NULL.
*
* If the node is a file, set its on-disk executable and read-only bits to
* match its properties and lock state,
* ### only if it has an svn:executable or svn:needs-lock property.
* ### This is to match the previous behaviour of setting its props
* afterwards by calling svn_wc_prop_set4(), but is not very clean.
*
* Sync the on-disk executable and read-only bits accordingly.
*/
static svn_error_t *
add_from_disk(svn_wc__db_t *db,
const char *local_abspath,
svn_node_kind_t kind,
const apr_hash_t *props,
apr_pool_t *scratch_pool)
{
if (kind == svn_node_file)
{
svn_skel_t *work_item = NULL;
if (props && (svn_prop_get_value(props, SVN_PROP_EXECUTABLE)
|| svn_prop_get_value(props, SVN_PROP_NEEDS_LOCK)))
SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
scratch_pool, scratch_pool));
SVN_ERR(svn_wc__db_op_add_file(db, local_abspath, props, work_item,
scratch_pool));
if (work_item)
SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
}
else
{
SVN_ERR(svn_wc__db_op_add_directory(db, local_abspath, props, NULL,
scratch_pool));
}
return SVN_NO_ERROR;
}
/* Set *REPOS_ROOT_URL and *REPOS_UUID to the repository of the parent of
LOCAL_ABSPATH. REPOS_ROOT_URL and/or REPOS_UUID may be NULL if not
wanted. Check that the parent of LOCAL_ABSPATH is a versioned directory
in a state in which a new child node can be scheduled for addition;
return an error if not. */
static svn_error_t *
check_can_add_to_parent(const char **repos_root_url,
const char **repos_uuid,
svn_wc__db_t *db,
const char *local_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
svn_wc__db_status_t parent_status;
svn_node_kind_t parent_kind;
svn_error_t *err;
SVN_ERR(svn_wc__write_check(db, parent_abspath, scratch_pool));
err = svn_wc__db_read_info(&parent_status, &parent_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, parent_abspath, result_pool, scratch_pool);
if (err
|| parent_status == svn_wc__db_status_not_present
|| parent_status == svn_wc__db_status_excluded
|| parent_status == svn_wc__db_status_server_excluded)
{
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));
}
else if (parent_status == 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));
}
else if (parent_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));
/* If we haven't found the repository info yet, find it now. */
if ((repos_root_url && ! *repos_root_url)
|| (repos_uuid && ! *repos_uuid))
{
if (parent_status == svn_wc__db_status_added)
SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL,
repos_root_url, repos_uuid, NULL,
NULL, NULL, NULL,
db, parent_abspath,
result_pool, scratch_pool));
else
SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL,
repos_root_url, repos_uuid, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL,
db, parent_abspath,
result_pool, scratch_pool));
}
return SVN_NO_ERROR;
}
/* Check that the on-disk item at LOCAL_ABSPATH can be scheduled for
* addition to its WC parent directory.
*
* Set *KIND_P to the kind of node to be added, *DB_ROW_EXISTS_P to whether
* it is already a versioned path, and if so, *IS_WC_ROOT_P to whether it's
* a WC root.
*
* ### The checks here, and the outputs, are geared towards svn_wc_add4().
*/
static svn_error_t *
check_can_add_node(svn_node_kind_t *kind_p,
svn_boolean_t *db_row_exists_p,
svn_boolean_t *is_wc_root_p,
svn_wc__db_t *db,
const char *local_abspath,
const char *copyfrom_url,
svn_revnum_t copyfrom_rev,
apr_pool_t *scratch_pool)
{
const char *base_name = svn_dirent_basename(local_abspath, scratch_pool);
svn_boolean_t is_wc_root;
svn_node_kind_t kind;
svn_boolean_t is_special;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR_ASSERT(!copyfrom_url || (svn_uri_is_canonical(copyfrom_url,
scratch_pool)
&& SVN_IS_VALID_REVNUM(copyfrom_rev)));
/* Check that the proposed node has an acceptable name. */
if (svn_wc_is_adm_dir(base_name, scratch_pool))
return svn_error_createf
(SVN_ERR_ENTRY_FORBIDDEN, NULL,
_("Can't create an entry with a reserved name while trying to add '%s'"),
svn_dirent_local_style(local_abspath, scratch_pool));
SVN_ERR(svn_path_check_valid(local_abspath, scratch_pool));
/* Make sure something's there; set KIND and *KIND_P. */
SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special,
scratch_pool));
if (kind == svn_node_none)
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("'%s' not found"),
svn_dirent_local_style(local_abspath,
scratch_pool));
if (kind == svn_node_unknown)
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Unsupported node kind for path '%s'"),
svn_dirent_local_style(local_abspath,
scratch_pool));
if (kind_p)
*kind_p = kind;
/* Determine whether a DB row for this node EXISTS, and whether it
IS_WC_ROOT. If it exists, check that it is in an acceptable state for
adding the new node; if not, return an error. */
{
svn_wc__db_status_t status;
svn_boolean_t conflicted;
svn_boolean_t exists;
svn_error_t *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,
&conflicted,
NULL, NULL, NULL, NULL, NULL, NULL,
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);
exists = FALSE;
is_wc_root = FALSE;
}
else
{
is_wc_root = FALSE;
exists = TRUE;
/* Note that the node may be in conflict even if it does not
* exist on disk (certain tree conflict scenarios). */
if (conflicted)
return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
_("'%s' is an existing item in conflict; "
"please mark the conflict as resolved "
"before adding a new item here"),
svn_dirent_local_style(local_abspath,
scratch_pool));
switch (status)
{
case svn_wc__db_status_not_present:
break;
case svn_wc__db_status_deleted:
/* A working copy root should never have a WORKING_NODE */
SVN_ERR_ASSERT(!is_wc_root);
break;
case svn_wc__db_status_normal:
SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, local_abspath,
scratch_pool));
if (is_wc_root && copyfrom_url)
{
/* Integrate a sub working copy in a parent working copy
(legacy behavior) */
break;
}
else if (is_wc_root && is_special)
{
/* Adding a symlink to a working copy root.
(special_tests.py 23: externals as symlink targets) */
break;
}
/* else: Fall through in default error */
default:
return svn_error_createf(
SVN_ERR_ENTRY_EXISTS, NULL,
_("'%s' is already under version control"),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
} /* err */
if (db_row_exists_p)
*db_row_exists_p = exists;
if (is_wc_root_p)
*is_wc_root_p = is_wc_root;
}
return SVN_NO_ERROR;
}
/* Convert the nested pristine working copy rooted at LOCAL_ABSPATH into
* a copied subtree in the outer working copy.
*
* LOCAL_ABSPATH must be the root of a nested working copy that has no
* local modifications. The parent directory of LOCAL_ABSPATH must be a
* versioned directory in the outer WC, and must belong to the same
* repository as the nested WC. The nested WC will be integrated into the
* parent's WC, and will no longer be a separate WC. */
static svn_error_t *
integrate_nested_wc_as_copy(svn_wc_context_t *wc_ctx,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
svn_wc__db_t *db = wc_ctx->db;
const char *moved_abspath;
/* Drop any references to the wc that is to be rewritten */
SVN_ERR(svn_wc__db_drop_root(db, local_abspath, scratch_pool));
/* Move the admin dir from the wc to a temporary location: MOVED_ABSPATH */
{
const char *tmpdir_abspath;
const char *moved_adm_abspath;
const char *adm_abspath;
SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db,
svn_dirent_dirname(local_abspath,
scratch_pool),
scratch_pool, scratch_pool));
SVN_ERR(svn_io_open_unique_file3(NULL, &moved_abspath, tmpdir_abspath,
svn_io_file_del_on_close,
scratch_pool, scratch_pool));
SVN_ERR(svn_io_dir_make(moved_abspath, APR_OS_DEFAULT, scratch_pool));
adm_abspath = svn_wc__adm_child(local_abspath, "", scratch_pool);
moved_adm_abspath = svn_wc__adm_child(moved_abspath, "", scratch_pool);
SVN_ERR(svn_io_file_move(adm_abspath, moved_adm_abspath, scratch_pool));
}
/* Copy entries from temporary location into the main db */
SVN_ERR(svn_wc_copy3(wc_ctx, moved_abspath, local_abspath,
TRUE /* metadata_only */,
NULL, NULL, NULL, NULL, scratch_pool));
/* Cleanup the temporary admin dir */
SVN_ERR(svn_wc__db_drop_root(db, moved_abspath, scratch_pool));
SVN_ERR(svn_io_remove_dir2(moved_abspath, FALSE, NULL, NULL,
scratch_pool));
/* The subdir is now part of our parent working copy. Our caller assumes
that we return the new node locked, so obtain a lock if we didn't
receive the lock via our depth infinity lock */
{
svn_boolean_t owns_lock;
SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath,
FALSE, scratch_pool));
if (!owns_lock)
SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE,
scratch_pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_add4(svn_wc_context_t *wc_ctx,
const char *local_abspath,
svn_depth_t depth,
const char *copyfrom_url,
svn_revnum_t copyfrom_rev,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
svn_wc__db_t *db = wc_ctx->db;
svn_node_kind_t kind;
svn_boolean_t db_row_exists;
svn_boolean_t is_wc_root;
const char *repos_root_url;
const char *repos_uuid;
SVN_ERR(check_can_add_node(&kind, &db_row_exists, &is_wc_root,
db, local_abspath, copyfrom_url, copyfrom_rev,
scratch_pool));
/* Get REPOS_ROOT_URL and REPOS_UUID. Check that the
parent is a versioned directory in an acceptable state. */
SVN_ERR(check_can_add_to_parent(&repos_root_url, &repos_uuid,
db, local_abspath, scratch_pool,
scratch_pool));
/* If we're performing a repos-to-WC copy, check that the copyfrom
repository is the same as the parent dir's repository. */
if (copyfrom_url && !svn_uri__is_ancestor(repos_root_url, copyfrom_url))
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("The URL '%s' has a different repository "
"root than its parent"), copyfrom_url);
/* Verify that we can actually integrate the inner working copy */
if (is_wc_root)
{
const char *repos_relpath, *inner_repos_root_url, *inner_repos_uuid;
const char *inner_url;
SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
&inner_repos_root_url,
&inner_repos_uuid, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL,
db, local_abspath,
scratch_pool, scratch_pool));
if (strcmp(inner_repos_uuid, repos_uuid)
|| strcmp(repos_root_url, inner_repos_root_url))
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Can't schedule the working copy at '%s' "
"from repository '%s' with uuid '%s' "
"for addition under a working copy from "
"repository '%s' with uuid '%s'."),
svn_dirent_local_style(local_abspath,
scratch_pool),
inner_repos_root_url, inner_repos_uuid,
repos_root_url, repos_uuid);
inner_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
scratch_pool);
if (strcmp(copyfrom_url, inner_url))
return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Can't add '%s' with URL '%s', but with "
"the data from '%s'"),
svn_dirent_local_style(local_abspath,
scratch_pool),
copyfrom_url, inner_url);
}
if (!copyfrom_url) /* Case 2a: It's a simple add */
{
SVN_ERR(add_from_disk(db, local_abspath, kind, NULL,
scratch_pool));
if (kind == svn_node_dir && !db_row_exists)
{
/* If using the legacy 1.6 interface the parent lock may not
be recursive and add is expected to lock the new dir.
### Perhaps the lock should be created in the same
transaction that adds the node? */
svn_boolean_t owns_lock;
SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath,
FALSE, scratch_pool));
if (!owns_lock)
SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE,
scratch_pool));
}
}
else if (!is_wc_root) /* Case 2b: It's a copy from the repository */
{
if (kind == svn_node_file)
{
/* This code should never be used, as it doesn't install proper
pristine and/or properties. But it was not an error in the old
version of this function.
===> Use svn_wc_add_repos_file4() directly! */
svn_stream_t *content = svn_stream_empty(scratch_pool);
SVN_ERR(svn_wc_add_repos_file4(wc_ctx, local_abspath,
content, NULL, NULL, NULL,
copyfrom_url, copyfrom_rev,
cancel_func, cancel_baton,
scratch_pool));
}
else
{
const char *repos_relpath =
svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool);
SVN_ERR(svn_wc__db_op_copy_dir(db, local_abspath,
apr_hash_make(scratch_pool),
copyfrom_rev, 0, NULL,
repos_relpath,
repos_root_url, repos_uuid,
copyfrom_rev,
NULL /* children */, depth,
FALSE /* is_move */,
NULL /* conflicts */,
NULL /* work items */,
scratch_pool));
}
}
else /* Case 1: Integrating a separate WC into this one, in place */
{
SVN_ERR(integrate_nested_wc_as_copy(wc_ctx, local_abspath,
scratch_pool));
}
/* Report the addition to the caller. */
if (notify_func != NULL)
{
svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
svn_wc_notify_add,
scratch_pool);
notify->kind = kind;
(*notify_func)(notify_baton, notify, scratch_pool);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_add_from_disk3(svn_wc_context_t *wc_ctx,
const char *local_abspath,
const apr_hash_t *props,
svn_boolean_t skip_checks,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
svn_node_kind_t kind;
SVN_ERR(check_can_add_node(&kind, NULL, NULL, wc_ctx->db, local_abspath,
NULL, SVN_INVALID_REVNUM, scratch_pool));
SVN_ERR(check_can_add_to_parent(NULL, NULL, wc_ctx->db, local_abspath,
scratch_pool, scratch_pool));
/* Canonicalize and check the props */
if (props)
{
apr_hash_t *new_props;
SVN_ERR(svn_wc__canonicalize_props(
&new_props,
local_abspath, kind, props, skip_checks,
scratch_pool, scratch_pool));
props = new_props;
}
/* Add to the DB and maybe update on-disk executable read-only bits */
SVN_ERR(add_from_disk(wc_ctx->db, local_abspath, kind, props,
scratch_pool));
/* Report the addition to the caller. */
if (notify_func != NULL)
{
svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
svn_wc_notify_add,
scratch_pool);
notify->kind = kind;
notify->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
(*notify_func)(notify_baton, notify, scratch_pool);
}
return SVN_NO_ERROR;
}
/* Return a path where nothing exists on disk, within the admin directory
belonging to the WCROOT_ABSPATH directory. */
static const char *
nonexistent_path(const char *wcroot_abspath, apr_pool_t *scratch_pool)
{
return svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_NONEXISTENT_PATH,
scratch_pool);
}
svn_error_t *
svn_wc_get_pristine_copy_path(const char *path,
const char **pristine_path,
apr_pool_t *pool)
{
svn_wc__db_t *db;
const char *local_abspath;
svn_error_t *err;
SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, TRUE, pool, pool));
/* DB is now open. This is seemingly a "light" function that a caller
may use repeatedly despite error return values. The rest of this
function should aggressively close DB, even in the error case. */
err = svn_wc__text_base_path_to_read(pristine_path, db, local_abspath,
pool, pool);
if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
{
/* The node doesn't exist, so return a non-existent path located
in WCROOT/.svn/ */
const char *wcroot_abspath;
svn_error_clear(err);
err = svn_wc__db_get_wcroot(&wcroot_abspath, db, local_abspath,
pool, pool);
if (err == NULL)
*pristine_path = nonexistent_path(wcroot_abspath, pool);
}
return svn_error_compose_create(err, svn_wc__db_close(db));
}
svn_error_t *
svn_wc_get_pristine_contents2(svn_stream_t **contents,
svn_wc_context_t *wc_ctx,
const char *local_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
return svn_error_trace(svn_wc__get_pristine_contents(contents, NULL,
wc_ctx->db,
local_abspath,
result_pool,
scratch_pool));
}
typedef struct get_pristine_lazyopen_baton_t
{
svn_wc_context_t *wc_ctx;
const char *wri_abspath;
const svn_checksum_t *checksum;
} get_pristine_lazyopen_baton_t;
/* Implements svn_stream_lazyopen_func_t */
static svn_error_t *
get_pristine_lazyopen_func(svn_stream_t **stream,
void *baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
get_pristine_lazyopen_baton_t *b = baton;
const svn_checksum_t *sha1_checksum;
/* svn_wc__db_pristine_read() wants a SHA1, so if we have an MD5,
we'll use it to lookup the SHA1. */
if (b->checksum->kind == svn_checksum_sha1)
sha1_checksum = b->checksum;
else
SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, b->wc_ctx->db,
b->wri_abspath, b->checksum,
scratch_pool, scratch_pool));
SVN_ERR(svn_wc__db_pristine_read(stream, NULL, b->wc_ctx->db,
b->wri_abspath, sha1_checksum,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents,
svn_wc_context_t *wc_ctx,
const char *wri_abspath,
const svn_checksum_t *checksum,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_boolean_t present;
*contents = NULL;
SVN_ERR(svn_wc__db_pristine_check(&present, wc_ctx->db, wri_abspath,
checksum, scratch_pool));
if (present)
{
get_pristine_lazyopen_baton_t *gpl_baton;
gpl_baton = apr_pcalloc(result_pool, sizeof(*gpl_baton));
gpl_baton->wc_ctx = wc_ctx;
gpl_baton->wri_abspath = wri_abspath;
gpl_baton->checksum = checksum;
*contents = svn_stream_lazyopen_create(get_pristine_lazyopen_func,
gpl_baton, FALSE, result_pool);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_add_lock2(svn_wc_context_t *wc_ctx,
const char *local_abspath,
const svn_lock_t *lock,
apr_pool_t *scratch_pool)
{
svn_wc__db_lock_t db_lock;
svn_error_t *err;
const svn_string_t *needs_lock;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR(svn_wc__write_check(wc_ctx->db,
svn_dirent_dirname(local_abspath, scratch_pool),
scratch_pool));
db_lock.token = lock->token;
db_lock.owner = lock->owner;
db_lock.comment = lock->comment;
db_lock.date = lock->creation_date;
err = svn_wc__db_lock_add(wc_ctx->db, local_abspath, &db_lock, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
/* Remap the error. */
svn_error_clear(err);
return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
_("'%s' is not under version control"),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
/* if svn:needs-lock is present, then make the file read-write. */
err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath,
SVN_PROP_NEEDS_LOCK, scratch_pool,
scratch_pool);
if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
{
/* The node has non wc representation (e.g. deleted), so
we don't want to touch the in-wc file */
svn_error_clear(err);
return SVN_NO_ERROR;
}
SVN_ERR(err);
if (needs_lock)
SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_remove_lock2(svn_wc_context_t *wc_ctx,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
svn_skel_t *work_item;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR(svn_wc__write_check(wc_ctx->db,
svn_dirent_dirname(local_abspath, scratch_pool),
scratch_pool));
SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item,
wc_ctx->db, local_abspath,
scratch_pool, scratch_pool));
err = svn_wc__db_lock_remove(wc_ctx->db, local_abspath, work_item,
scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
/* Remap the error. */
svn_error_clear(err);
return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
_("'%s' is not under version control"),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
return svn_error_trace(svn_wc__wq_run(wc_ctx->db, local_abspath,
NULL, NULL /* cancel*/,
scratch_pool));
}
svn_error_t *
svn_wc_set_changelist2(svn_wc_context_t *wc_ctx,
const char *local_abspath,
const char *new_changelist,
svn_depth_t depth,
const apr_array_header_t *changelist_filter,
svn_cancel_func_t cancel_func,
void *cancel_baton,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *scratch_pool)
{
/* Assert that we aren't being asked to set an empty changelist. */
SVN_ERR_ASSERT(! (new_changelist && new_changelist[0] == '\0'));
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
SVN_ERR(svn_wc__db_op_set_changelist(wc_ctx->db, local_abspath,
new_changelist, changelist_filter,
depth, notify_func, notify_baton,
cancel_func, cancel_baton,
scratch_pool));
return SVN_NO_ERROR;
}
struct get_cl_fn_baton
{
svn_wc__db_t *db;
apr_hash_t *clhash;
svn_changelist_receiver_t callback_func;
void *callback_baton;
};
static svn_error_t *
get_node_changelist(const char *local_abspath,
svn_node_kind_t kind,
void *baton,
apr_pool_t *scratch_pool)
{
struct get_cl_fn_baton *b = baton;
const char *changelist;
SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, &changelist,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
b->db, local_abspath,
scratch_pool, scratch_pool));
if (!b->clhash
|| (changelist && svn_hash_gets(b->clhash, changelist) != NULL))
SVN_ERR(b->callback_func(b->callback_baton, local_abspath,
changelist, scratch_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_get_changelists(svn_wc_context_t *wc_ctx,
const char *local_abspath,
svn_depth_t depth,
const apr_array_header_t *changelist_filter,
svn_changelist_receiver_t callback_func,
void *callback_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
struct get_cl_fn_baton gnb;
gnb.db = wc_ctx->db;
gnb.clhash = NULL;
gnb.callback_func = callback_func;
gnb.callback_baton = callback_baton;
if (changelist_filter)
SVN_ERR(svn_hash_from_cstring_keys(&gnb.clhash, changelist_filter,
scratch_pool));
return svn_error_trace(
svn_wc__internal_walk_children(wc_ctx->db, local_abspath, FALSE,
changelist_filter, get_node_changelist,
&gnb, depth,
cancel_func, cancel_baton,
scratch_pool));
}
svn_boolean_t
svn_wc__internal_changelist_match(svn_wc__db_t *db,
const char *local_abspath,
const apr_hash_t *clhash,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
const char *changelist;
if (clhash == NULL)
return TRUE;
err = svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, &changelist,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
db, local_abspath, scratch_pool, scratch_pool);
if (err)
{
svn_error_clear(err);
return FALSE;
}
return (changelist
&& svn_hash_gets((apr_hash_t *)clhash, changelist) != NULL);
}
svn_boolean_t
svn_wc__changelist_match(svn_wc_context_t *wc_ctx,
const char *local_abspath,
const apr_hash_t *clhash,
apr_pool_t *scratch_pool)
{
return svn_wc__internal_changelist_match(wc_ctx->db, local_abspath, clhash,
scratch_pool);
}