blob: 91295f2703bcb59c633ccb747044581cfd824c60 [file] [log] [blame]
/*
* entries.c : manipulating the administrative `entries' file.
*
* ====================================================================
* 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 <assert.h>
#include <apr_strings.h>
#include "svn_error.h"
#include "svn_types.h"
#include "svn_time.h"
#include "svn_pools.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_ctype.h"
#include "svn_string.h"
#include "svn_hash.h"
#include "wc.h"
#include "adm_files.h"
#include "conflicts.h"
#include "entries.h"
#include "lock.h"
#include "tree_conflicts.h"
#include "wc_db.h"
#include "wc-queries.h" /* for STMT_* */
#define SVN_WC__I_AM_WC_DB
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
#include "private/svn_sqlite.h"
#include "token-map.h"
#include "wc_db_private.h"
#define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x))))
/* Temporary structures which mirror the tables in wc-metadata.sql.
For detailed descriptions of each field, see that file. */
typedef struct db_node_t {
apr_int64_t wc_id;
const char *local_relpath;
int op_depth;
apr_int64_t repos_id;
const char *repos_relpath;
const char *parent_relpath;
svn_wc__db_status_t presence;
svn_revnum_t revision;
svn_node_kind_t kind;
svn_checksum_t *checksum;
svn_filesize_t recorded_size;
svn_revnum_t changed_rev;
apr_time_t changed_date;
const char *changed_author;
svn_depth_t depth;
apr_time_t recorded_time;
apr_hash_t *properties;
svn_boolean_t file_external;
apr_array_header_t *inherited_props;
} db_node_t;
typedef struct db_actual_node_t {
apr_int64_t wc_id;
const char *local_relpath;
const char *parent_relpath;
apr_hash_t *properties;
const char *conflict_old;
const char *conflict_new;
const char *conflict_working;
const char *prop_reject;
const char *changelist;
/* ### enum for text_mod */
const char *tree_conflict_data;
} db_actual_node_t;
/*** reading and writing the entries file ***/
/* */
static svn_wc_entry_t *
alloc_entry(apr_pool_t *pool)
{
svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
entry->revision = SVN_INVALID_REVNUM;
entry->copyfrom_rev = SVN_INVALID_REVNUM;
entry->cmt_rev = SVN_INVALID_REVNUM;
entry->kind = svn_node_none;
entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
entry->depth = svn_depth_infinity;
entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
entry->file_external_rev.kind = svn_opt_revision_unspecified;
return entry;
}
/* Is the entry in a 'hidden' state in the sense of the 'show_hidden'
* switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */
svn_error_t *
svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry)
{
/* In English, the condition is: "the entry is not present, and I haven't
scheduled something over the top of it." */
if (entry->deleted
|| entry->absent
|| entry->depth == svn_depth_exclude)
{
/* These kinds of nodes cannot be marked for deletion (which also
means no "replace" either). */
SVN_ERR_ASSERT(entry->schedule == svn_wc_schedule_add
|| entry->schedule == svn_wc_schedule_normal);
/* Hidden if something hasn't been added over it.
### is this even possible with absent or excluded nodes? */
*hidden = entry->schedule != svn_wc_schedule_add;
}
else
*hidden = FALSE;
return SVN_NO_ERROR;
}
/* Hit the database to check the file external information for the given
entry. The entry will be modified in place. */
static svn_error_t *
check_file_external(svn_wc_entry_t *entry,
svn_wc__db_t *db,
const char *local_abspath,
const char *wri_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc__db_status_t status;
svn_node_kind_t kind;
const char *repos_relpath;
svn_revnum_t peg_revision;
svn_revnum_t revision;
svn_error_t *err;
err = svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL,
&repos_relpath, &peg_revision, &revision,
db, local_abspath, wri_abspath,
result_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
svn_error_clear(err);
return SVN_NO_ERROR;
}
if (status == svn_wc__db_status_normal
&& kind == svn_node_file)
{
entry->file_external_path = repos_relpath;
if (SVN_IS_VALID_REVNUM(peg_revision))
{
entry->file_external_peg_rev.kind = svn_opt_revision_number;
entry->file_external_peg_rev.value.number = peg_revision;
entry->file_external_rev = entry->file_external_peg_rev;
}
if (SVN_IS_VALID_REVNUM(revision))
{
entry->file_external_rev.kind = svn_opt_revision_number;
entry->file_external_rev.value.number = revision;
}
}
return SVN_NO_ERROR;
}
/* Fill in the following fields of ENTRY:
REVISION
REPOS
UUID
CMT_REV
CMT_DATE
CMT_AUTHOR
DEPTH
DELETED
Return: KIND, REPOS_RELPATH, CHECKSUM
*/
static svn_error_t *
get_info_for_deleted(svn_wc_entry_t *entry,
svn_node_kind_t *kind,
const char **repos_relpath,
const svn_checksum_t **checksum,
svn_wc__db_lock_t **lock,
svn_wc__db_t *db,
const char *entry_abspath,
svn_wc__db_wcroot_t *wcroot,
const char *entry_relpath,
const svn_wc_entry_t *parent_entry,
svn_boolean_t have_base,
svn_boolean_t have_more_work,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
if (have_base && !have_more_work)
{
apr_int64_t repos_id;
/* This is the delete of a BASE node */
SVN_ERR(svn_wc__db_base_get_info_internal(
NULL, kind,
&entry->revision,
repos_relpath,
&repos_id,
&entry->cmt_rev,
&entry->cmt_date,
&entry->cmt_author,
&entry->depth,
checksum,
NULL,
lock,
&entry->has_props, NULL,
NULL,
wcroot, entry_relpath,
result_pool,
scratch_pool));
SVN_ERR(svn_wc__db_fetch_repos_info(&entry->repos, &entry->uuid,
wcroot, repos_id, result_pool));
}
else
{
const char *work_del_relpath;
const char *parent_repos_relpath;
const char *parent_relpath;
apr_int64_t repos_id;
/* This is a deleted child of a copy/move-here,
so we need to scan up the WORKING tree to find the root of
the deletion. Then examine its parent to discover its
future location in the repository. */
SVN_ERR(svn_wc__db_read_pristine_info(NULL, kind,
&entry->cmt_rev,
&entry->cmt_date,
&entry->cmt_author,
&entry->depth,
checksum,
NULL,
&entry->has_props, NULL,
db,
entry_abspath,
result_pool,
scratch_pool));
/* working_size and text_time unavailable */
SVN_ERR(svn_wc__db_scan_deletion_internal(
NULL,
NULL,
&work_del_relpath, NULL,
wcroot, entry_relpath,
scratch_pool, scratch_pool));
SVN_ERR_ASSERT(work_del_relpath != NULL);
parent_relpath = svn_relpath_dirname(work_del_relpath, scratch_pool);
/* The parent directory of the delete root must be added, so we
can find the required information there */
SVN_ERR(svn_wc__db_scan_addition_internal(
NULL, NULL,
&parent_repos_relpath,
&repos_id,
NULL, NULL, NULL,
wcroot, parent_relpath,
result_pool, scratch_pool));
SVN_ERR(svn_wc__db_fetch_repos_info(&entry->repos, &entry->uuid,
wcroot, repos_id, result_pool));
/* Now glue it all together */
*repos_relpath = svn_relpath_join(parent_repos_relpath,
svn_relpath_skip_ancestor(
parent_relpath,
entry_relpath),
result_pool);
/* Even though this is the delete of a WORKING node, there might still
be a BASE node somewhere below with an interesting revision */
if (have_base)
{
svn_wc__db_status_t status;
SVN_ERR(svn_wc__db_base_get_info_internal(
&status, NULL, &entry->revision,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, lock, NULL, NULL,
NULL,
wcroot, entry_relpath,
result_pool, scratch_pool));
if (status == svn_wc__db_status_not_present)
entry->deleted = TRUE;
}
}
/* Do some extra work for the child nodes. */
if (!SVN_IS_VALID_REVNUM(entry->revision) && parent_entry != NULL)
{
/* For child nodes without a revision, pick up the parent's
revision. */
entry->revision = parent_entry->revision;
}
return SVN_NO_ERROR;
}
/*
* Encode tree conflict descriptions into a single string.
*
* Set *CONFLICT_DATA to a string, allocated in POOL, that encodes the tree
* conflicts in CONFLICTS in a form suitable for storage in a single string
* field in a WC entry. CONFLICTS is a hash of zero or more pointers to
* svn_wc_conflict_description2_t objects, index by their basenames. All of the
* conflict victim paths must be siblings.
*
* Do all allocations in POOL.
*
* @see svn_wc__read_tree_conflicts()
*/
static svn_error_t *
write_tree_conflicts(const char **conflict_data,
apr_hash_t *conflicts,
apr_pool_t *pool)
{
svn_skel_t *skel = svn_skel__make_empty_list(pool);
apr_hash_index_t *hi;
for (hi = apr_hash_first(pool, conflicts); hi; hi = apr_hash_next(hi))
{
svn_skel_t *c_skel;
SVN_ERR(svn_wc__serialize_conflict(&c_skel, apr_hash_this_val(hi),
pool, pool));
svn_skel__prepend(c_skel, skel);
}
*conflict_data = svn_skel__unparse(skel, pool)->data;
return SVN_NO_ERROR;
}
/* Read one entry from wc_db. It will be allocated in RESULT_POOL and
returned in *NEW_ENTRY.
DIR_ABSPATH is the name of the directory to read this entry from, and
it will be named NAME (use "" for "this dir").
DB specifies the wc_db database, and WC_ID specifies which working copy
this information is being read from.
If this node is "this dir", then PARENT_ENTRY should be NULL. Otherwise,
it should refer to the entry for the child's parent directory.
### All database read operations should really use wcroot, dir_relpath,
as that restores obstruction compatibility with <= 1.6.0
but that has been the case since the introduction of WC-NG in 1.7.0
Temporary allocations are made in SCRATCH_POOL. */
static svn_error_t *
read_one_entry(const svn_wc_entry_t **new_entry,
svn_wc__db_t *db,
const char *dir_abspath,
svn_wc__db_wcroot_t *wcroot,
const char *dir_relpath,
const char *name,
const svn_wc_entry_t *parent_entry,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_node_kind_t kind;
svn_wc__db_status_t status;
svn_wc__db_lock_t *lock;
const char *repos_relpath;
const svn_checksum_t *checksum;
svn_filesize_t translated_size;
svn_wc_entry_t *entry = alloc_entry(result_pool);
const char *entry_relpath;
const char *entry_abspath;
apr_int64_t repos_id;
apr_int64_t original_repos_id;
const char *original_repos_relpath;
const char *original_root_url;
svn_boolean_t conflicted;
svn_boolean_t have_base;
svn_boolean_t have_more_work;
svn_boolean_t op_root;
entry->name = apr_pstrdup(result_pool, name);
entry_relpath = svn_relpath_join(dir_relpath, entry->name, scratch_pool);
entry_abspath = svn_dirent_join(dir_abspath, entry->name, scratch_pool);
SVN_ERR(svn_wc__db_read_info_internal(
&status,
&kind,
&entry->revision,
&repos_relpath,
&repos_id,
&entry->cmt_rev,
&entry->cmt_date,
&entry->cmt_author,
&entry->depth,
&checksum,
NULL,
&original_repos_relpath,
&original_repos_id,
&entry->copyfrom_rev,
&lock,
&translated_size,
&entry->text_time,
&entry->changelist,
&conflicted,
&op_root,
&entry->has_props /* have_props */,
&entry->has_prop_mods /* props_mod */,
&have_base,
&have_more_work,
NULL /* have_work */,
wcroot, entry_relpath,
result_pool, scratch_pool));
SVN_ERR(svn_wc__db_fetch_repos_info(&entry->repos, &entry->uuid,
wcroot, repos_id, result_pool));
SVN_ERR(svn_wc__db_fetch_repos_info(&original_root_url, NULL,
wcroot, original_repos_id,
result_pool));
if (entry->has_prop_mods)
entry->has_props = TRUE;
if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
{
/* get the tree conflict data. */
apr_hash_t *tree_conflicts = NULL;
const apr_array_header_t *conflict_victims;
int k;
SVN_ERR(svn_wc__db_read_conflict_victims(&conflict_victims, db,
dir_abspath,
scratch_pool,
scratch_pool));
for (k = 0; k < conflict_victims->nelts; k++)
{
int j;
const apr_array_header_t *child_conflicts;
const char *child_name;
const char *child_abspath;
child_name = APR_ARRAY_IDX(conflict_victims, k, const char *);
child_abspath = svn_dirent_join(dir_abspath, child_name,
scratch_pool);
SVN_ERR(svn_wc__read_conflicts(&child_conflicts, NULL,
db, child_abspath,
FALSE /* create tempfiles */,
TRUE /* tree_conflicts_only */,
scratch_pool, scratch_pool));
for (j = 0; j < child_conflicts->nelts; j++)
{
const svn_wc_conflict_description2_t *conflict =
APR_ARRAY_IDX(child_conflicts, j,
svn_wc_conflict_description2_t *);
if (conflict->kind == svn_wc_conflict_kind_tree)
{
if (!tree_conflicts)
tree_conflicts = apr_hash_make(scratch_pool);
svn_hash_sets(tree_conflicts, child_name, conflict);
}
}
}
if (tree_conflicts)
{
SVN_ERR(write_tree_conflicts(&entry->tree_conflict_data,
tree_conflicts, result_pool));
}
}
if (status == svn_wc__db_status_normal
|| status == svn_wc__db_status_incomplete)
{
/* Plain old BASE node. */
entry->schedule = svn_wc_schedule_normal;
/* Grab inherited repository information, if necessary. */
if (repos_relpath == NULL)
{
SVN_ERR(svn_wc__db_base_get_info_internal(
NULL, NULL, NULL, &repos_relpath,
&repos_id, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL,
wcroot, entry_relpath,
result_pool, scratch_pool));
SVN_ERR(svn_wc__db_fetch_repos_info(&entry->repos, &entry->uuid,
wcroot, repos_id, result_pool));
}
entry->incomplete = (status == svn_wc__db_status_incomplete);
}
else if (status == svn_wc__db_status_deleted)
{
svn_node_kind_t path_kind;
/* ### we don't have to worry about moves, so this is a delete. */
entry->schedule = svn_wc_schedule_delete;
/* If there are multiple working layers or no BASE layer, then
this is a WORKING delete or WORKING not-present. */
if (have_more_work || !have_base)
entry->copied = TRUE;
else if (have_base && !have_more_work)
entry->copied = FALSE;
else
{
const char *work_del_relpath;
SVN_ERR(svn_wc__db_scan_deletion_internal(
NULL, NULL,
&work_del_relpath, NULL,
wcroot, entry_relpath,
scratch_pool, scratch_pool));
if (work_del_relpath)
entry->copied = TRUE;
}
/* If there is still a directory on-disk we keep it, if not it is
already deleted. Simple, isn't it?
Before single-db we had to keep the administative area alive until
after the commit really deletes it. Setting keep alive stopped the
commit processing from deleting the directory. We don't delete it
any more, so all we have to do is provide some 'sane' value.
*/
SVN_ERR(svn_io_check_path(entry_abspath, &path_kind, scratch_pool));
entry->keep_local = (path_kind == svn_node_dir);
}
else if (status == svn_wc__db_status_added)
{
svn_wc__db_status_t work_status;
const char *op_root_abspath;
const char *scanned_original_relpath;
svn_revnum_t original_revision;
/* For child nodes, pick up the parent's revision. */
if (*entry->name != '\0')
{
assert(parent_entry != NULL);
assert(entry->revision == SVN_INVALID_REVNUM);
entry->revision = parent_entry->revision;
}
if (have_base)
{
svn_wc__db_status_t base_status;
/* ENTRY->REVISION is overloaded. When a node is schedule-add
or -replace, then REVISION refers to the BASE node's revision
that is being overwritten. We need to fetch it now. */
SVN_ERR(svn_wc__db_base_get_info_internal(
&base_status, NULL,
&entry->revision,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL,
wcroot, entry_relpath,
scratch_pool,
scratch_pool));
if (base_status == svn_wc__db_status_not_present)
{
/* The underlying node is DELETED in this revision. */
entry->deleted = TRUE;
/* This is an add since there isn't a node to replace. */
entry->schedule = svn_wc_schedule_add;
}
else
entry->schedule = svn_wc_schedule_replace;
}
else
{
/* There is NO 'not-present' BASE_NODE for this node.
Therefore, we are looking at some kind of add/copy
rather than a replace. */
/* ### if this looks like a plain old add, then rev=0. */
if (!SVN_IS_VALID_REVNUM(entry->copyfrom_rev)
&& !SVN_IS_VALID_REVNUM(entry->cmt_rev))
entry->revision = 0;
entry->schedule = svn_wc_schedule_add;
}
/* If we don't have "real" data from the entry (obstruction),
then we cannot begin a scan for data. The original node may
have important data. Set up stuff to kill that idea off,
and finish up this entry. */
{
const char *op_root_relpath;
SVN_ERR(svn_wc__db_scan_addition_internal(
&work_status,
&op_root_relpath,
&repos_relpath,
&repos_id,
&scanned_original_relpath,
NULL /* original_repos_id */,
&original_revision,
wcroot, entry_relpath,
result_pool, scratch_pool));
SVN_ERR(svn_wc__db_fetch_repos_info(&entry->repos, &entry->uuid,
wcroot, repos_id, result_pool));
if (!op_root_relpath)
op_root_abspath = NULL;
else
op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath,
scratch_pool);
/* In wc.db we want to keep the valid revision of the not-present
BASE_REV, but when we used entries we set the revision to 0
when adding a new node over a not present base node. */
if (work_status == svn_wc__db_status_added
&& entry->deleted)
entry->revision = 0;
}
if (!SVN_IS_VALID_REVNUM(entry->cmt_rev)
&& scanned_original_relpath == NULL)
{
/* There is NOT a last-changed revision (last-changed date and
author may be unknown, but we can always check the rev).
The absence of a revision implies this node was added WITHOUT
any history. Avoid the COPIED checks in the else block. */
/* ### scan_addition may need to be updated to avoid returning
### status_copied in this case. */
}
/* For backwards-compatibility purposes we treat moves just like
* regular copies. */
else if (work_status == svn_wc__db_status_copied ||
work_status == svn_wc__db_status_moved_here)
{
entry->copied = TRUE;
/* If this is a child of a copied subtree, then it should be
schedule_normal. */
if (original_repos_relpath == NULL)
{
/* ### what if there is a BASE node under there? */
entry->schedule = svn_wc_schedule_normal;
}
/* Copied nodes need to mirror their copyfrom_rev, if they
don't have a revision of their own already. */
if (!SVN_IS_VALID_REVNUM(entry->revision)
|| entry->revision == 0 /* added */)
entry->revision = original_revision;
}
/* Does this node have copyfrom_* information? */
if (scanned_original_relpath != NULL)
{
svn_boolean_t is_copied_child;
svn_boolean_t is_mixed_rev = FALSE;
SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied ||
work_status == svn_wc__db_status_moved_here);
/* If this node inherits copyfrom information from an
ancestor node, then it must be a copied child. */
is_copied_child = (original_repos_relpath == NULL);
/* If this node has copyfrom information on it, then it may
be an actual copy-root, or it could be participating in
a mixed-revision copied tree. So if we don't already know
this is a copied child, then we need to look for this
mixed-revision situation. */
if (!is_copied_child)
{
const char *parent_relpath;
svn_error_t *err;
const char *parent_repos_relpath;
const char *parent_root_url;
apr_int64_t parent_repos_id;
const char *op_root_relpath;
/* When we insert entries into the database, we will
construct additional copyfrom records for mixed-revision
copies. The old entries would simply record the different
revision in the entry->revision field. That is not
available within wc-ng, so additional copies are made
(see the logic inside write_entry()). However, when
reading these back *out* of the database, the additional
copies look like new "Added" nodes rather than a simple
mixed-rev working copy.
That would be a behavior change if we did not compensate.
If there is copyfrom information for this node, then the
code below looks at the parent to detect if it *also* has
copyfrom information, and if the copyfrom_url would align
properly. If it *does*, then we omit storing copyfrom_url
and copyfrom_rev (ie. inherit the copyfrom info like a
normal child), and update entry->revision with the
copyfrom_rev in order to (re)create the mixed-rev copied
subtree that was originally presented for storage. */
/* Get the copyfrom information from our parent.
Note that the parent could be added/copied/moved-here.
There is no way for it to be deleted/moved-away and
have *this* node appear as copied. */
parent_relpath = svn_relpath_dirname(entry_relpath,
scratch_pool);
err = svn_wc__db_scan_addition_internal(
NULL,
&op_root_relpath,
NULL, NULL,
&parent_repos_relpath,
&parent_repos_id,
NULL,
wcroot, parent_relpath,
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);
op_root_abspath = NULL;
parent_repos_relpath = NULL;
parent_root_url = NULL;
}
else
{
SVN_ERR(svn_wc__db_fetch_repos_info(&parent_root_url, NULL,
wcroot, parent_repos_id,
scratch_pool));
op_root_abspath = svn_dirent_join(wcroot->abspath,
op_root_relpath,
scratch_pool);
}
if (parent_root_url != NULL
&& strcmp(original_root_url, parent_root_url) == 0)
{
const char *relpath_to_entry = svn_dirent_is_child(
op_root_abspath, entry_abspath, NULL);
const char *entry_repos_relpath = svn_relpath_join(
parent_repos_relpath, relpath_to_entry, scratch_pool);
/* The copyfrom repos roots matched.
Now we look to see if the copyfrom path of the parent
would align with our own path. If so, then it means
this copyfrom was spontaneously created and inserted
for mixed-rev purposes and can be eliminated without
changing the semantics of a mixed-rev copied subtree.
See notes/api-errata/wc003.txt for some additional
detail, and potential issues. */
if (strcmp(entry_repos_relpath,
original_repos_relpath) == 0)
{
is_copied_child = TRUE;
is_mixed_rev = TRUE;
}
}
}
if (is_copied_child)
{
/* We won't be settig the copyfrom_url, yet need to
clear out the copyfrom_rev. Thus, this node becomes a
child of a copied subtree (rather than its own root). */
entry->copyfrom_rev = SVN_INVALID_REVNUM;
/* Children in a copied subtree are schedule normal
since we don't plan to actually *do* anything with
them. Their operation is implied by ancestors. */
entry->schedule = svn_wc_schedule_normal;
/* And *finally* we turn this entry into the mixed
revision node that it was intended to be. This
node's revision is taken from the copyfrom record
that we spontaneously constructed. */
if (is_mixed_rev)
entry->revision = original_revision;
}
else if (original_repos_relpath != NULL)
{
entry->copyfrom_url =
svn_path_url_add_component2(original_root_url,
original_repos_relpath,
result_pool);
}
else
{
/* NOTE: if original_repos_relpath == NULL, then the
second call to scan_addition() will not have occurred.
Thus, this use of OP_ROOT_ABSPATH still contains the
original value where we fetched a value for
SCANNED_REPOS_RELPATH. */
const char *relpath_to_entry = svn_dirent_is_child(
op_root_abspath, entry_abspath, NULL);
const char *entry_repos_relpath = svn_relpath_join(
scanned_original_relpath, relpath_to_entry, scratch_pool);
entry->copyfrom_url =
svn_path_url_add_component2(original_root_url,
entry_repos_relpath,
result_pool);
}
}
}
else if (status == svn_wc__db_status_not_present)
{
/* ### buh. 'deleted' nodes are actually supposed to be
### schedule "normal" since we aren't going to actually *do*
### anything to this node at commit time. */
entry->schedule = svn_wc_schedule_normal;
entry->deleted = TRUE;
}
else if (status == svn_wc__db_status_server_excluded)
{
entry->absent = TRUE;
}
else if (status == svn_wc__db_status_excluded)
{
entry->schedule = svn_wc_schedule_normal;
entry->depth = svn_depth_exclude;
}
else
{
/* ### we should have handled all possible status values. */
SVN_ERR_MALFUNCTION();
}
/* ### higher levels want repos information about deleted nodes, even
### tho they are not "part of" a repository any more. */
if (entry->schedule == svn_wc_schedule_delete)
{
SVN_ERR(get_info_for_deleted(entry,
&kind,
&repos_relpath,
&checksum,
&lock,
db, entry_abspath,
wcroot, entry_relpath,
parent_entry,
have_base, have_more_work,
result_pool, scratch_pool));
}
/* ### default to the infinite depth if we don't know it. */
if (entry->depth == svn_depth_unknown)
entry->depth = svn_depth_infinity;
if (kind == svn_node_dir)
entry->kind = svn_node_dir;
else if (kind == svn_node_file)
entry->kind = svn_node_file;
else if (kind == svn_node_symlink)
entry->kind = svn_node_file; /* ### no symlink kind */
else
entry->kind = svn_node_unknown;
/* We should always have a REPOS_RELPATH, except for:
- deleted nodes
- certain obstructed nodes
- not-present nodes
- absent nodes
- excluded nodes
### the last three should probably have an "implied" REPOS_RELPATH
*/
SVN_ERR_ASSERT(repos_relpath != NULL
|| entry->schedule == svn_wc_schedule_delete
|| status == svn_wc__db_status_not_present
|| status == svn_wc__db_status_server_excluded
|| status == svn_wc__db_status_excluded);
if (repos_relpath)
entry->url = svn_path_url_add_component2(entry->repos,
repos_relpath,
result_pool);
if (checksum)
{
/* We got a SHA-1, get the corresponding MD-5. */
if (checksum->kind != svn_checksum_md5)
SVN_ERR(svn_wc__db_pristine_get_md5(&checksum, db,
dir_abspath, checksum,
scratch_pool, scratch_pool));
SVN_ERR_ASSERT(checksum->kind == svn_checksum_md5);
entry->checksum = svn_checksum_to_cstring(checksum, result_pool);
}
if (conflicted)
{
svn_skel_t *conflict;
svn_boolean_t text_conflicted;
svn_boolean_t prop_conflicted;
SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, NULL, NULL,
wcroot, entry_relpath,
scratch_pool, scratch_pool));
SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted,
&prop_conflicted, NULL,
db, dir_abspath, conflict,
scratch_pool, scratch_pool));
if (text_conflicted)
{
const char *my_abspath;
const char *their_old_abspath;
const char *their_abspath;
SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath,
&their_old_abspath,
&their_abspath,
db, dir_abspath,
conflict, scratch_pool,
scratch_pool));
if (my_abspath)
entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool);
if (their_old_abspath)
entry->conflict_old = svn_dirent_basename(their_old_abspath,
result_pool);
if (their_abspath)
entry->conflict_new = svn_dirent_basename(their_abspath,
result_pool);
}
if (prop_conflicted)
{
const char *prej_abspath;
SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL,
NULL, NULL, NULL,
db, dir_abspath,
conflict, scratch_pool,
scratch_pool));
if (prej_abspath)
entry->prejfile = svn_dirent_basename(prej_abspath, result_pool);
}
}
if (lock)
{
entry->lock_token = lock->token;
entry->lock_owner = lock->owner;
entry->lock_comment = lock->comment;
entry->lock_creation_date = lock->date;
}
/* Let's check for a file external. ugh. */
if (status == svn_wc__db_status_normal
&& kind == svn_node_file)
SVN_ERR(check_file_external(entry, db, entry_abspath, dir_abspath,
result_pool, scratch_pool));
entry->working_size = translated_size;
*new_entry = entry;
return SVN_NO_ERROR;
}
/* Read entries for PATH/LOCAL_ABSPATH from DB. The entries
will be allocated in RESULT_POOL, with temporary allocations in
SCRATCH_POOL. The entries are returned in RESULT_ENTRIES. */
static svn_error_t *
read_entries_new(apr_hash_t **result_entries,
svn_wc__db_t *db,
const char *dir_abspath,
svn_wc__db_wcroot_t *wcroot,
const char *dir_relpath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_hash_t *entries;
const apr_array_header_t *children;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
const svn_wc_entry_t *parent_entry;
entries = apr_hash_make(result_pool);
SVN_ERR(read_one_entry(&parent_entry,
db, dir_abspath,
wcroot, dir_relpath,
"" /* name */,
NULL /* parent_entry */,
result_pool, iterpool));
svn_hash_sets(entries, "", parent_entry);
/* Use result_pool so that the child names (used by reference, rather
than copied) appear in result_pool. */
SVN_ERR(svn_wc__db_read_children(&children, db,
dir_abspath,
scratch_pool, iterpool));
for (i = children->nelts; i--; )
{
const char *name = APR_ARRAY_IDX(children, i, const char *);
const svn_wc_entry_t *entry;
svn_pool_clear(iterpool);
SVN_ERR(read_one_entry(&entry,
db, dir_abspath,
wcroot, dir_relpath,
name, parent_entry,
result_pool, iterpool));
svn_hash_sets(entries, entry->name, entry);
}
svn_pool_destroy(iterpool);
*result_entries = entries;
return SVN_NO_ERROR;
}
static svn_error_t *
read_entry_pair_txn(const svn_wc_entry_t **parent_entry,
const svn_wc_entry_t **entry,
svn_wc__db_t *db,
const char *dir_abspath,
svn_wc__db_wcroot_t *wcroot,
const char *dir_relpath,
const char *name,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
SVN_ERR(read_one_entry(parent_entry,
db, dir_abspath,
wcroot, dir_relpath,
"" /* name */,
NULL /* parent_entry */,
result_pool, scratch_pool));
/* If we need the entry for "this dir", then return the parent_entry
in both outputs. Otherwise, read the child node. */
if (*name == '\0')
{
/* If the retrieved node is a FILE, then we have a problem. We asked
for a directory. This implies there is an obstructing, unversioned
directory where a FILE should be. We navigated from the obstructing
subdir up to the parent dir, then returned the FILE found there.
Let's return WC_MISSING cuz the caller thought we had a dir, but
that (versioned subdir) isn't there. */
if ((*parent_entry)->kind == svn_node_file)
{
*parent_entry = NULL;
return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
_("'%s' is not a versioned working copy"),
svn_dirent_local_style(dir_abspath,
scratch_pool));
}
*entry = *parent_entry;
}
else
{
const apr_array_header_t *children;
int i;
/* Default to not finding the child. */
*entry = NULL;
/* Determine whether the parent KNOWS about this child. If it does
not, then we should not attempt to look for it.
For example: the parent doesn't "know" about the child, but the
versioned directory *does* exist on disk. We don't want to look
into that subdir. */
SVN_ERR(svn_wc__db_read_children(&children, db, dir_abspath,
scratch_pool, scratch_pool));
for (i = children->nelts; i--; )
{
const char *child = APR_ARRAY_IDX(children, i, const char *);
if (strcmp(child, name) == 0)
{
svn_error_t *err;
err = read_one_entry(entry,
db, dir_abspath,
wcroot, dir_relpath,
name, *parent_entry,
result_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
/* No problem. Clear the error and leave the default value
of "missing". */
svn_error_clear(err);
}
/* Found it. No need to keep searching. */
break;
}
}
/* if the loop ends without finding a child, then we have the default
ENTRY value of NULL. */
}
return SVN_NO_ERROR;
}
/* Read a pair of entries from wc_db in the directory DIR_ABSPATH. Return
the directory's entry in *PARENT_ENTRY and NAME's entry in *ENTRY. The
two returned pointers will be the same if NAME=="" ("this dir").
The parent entry must exist.
The requested entry MAY exist. If it does not, then NULL will be returned.
The resulting entries are allocated in RESULT_POOL, and all temporary
allocations are made in SCRATCH_POOL. */
static svn_error_t *
read_entry_pair(const svn_wc_entry_t **parent_entry,
const svn_wc_entry_t **entry,
svn_wc__db_t *db,
const char *dir_abspath,
const char *name,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc__db_wcroot_t *wcroot;
const char *dir_relpath;
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath,
db, dir_abspath,
scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
SVN_WC__DB_WITH_TXN(read_entry_pair_txn(parent_entry, entry,
db, dir_abspath,
wcroot, dir_relpath,
name,
result_pool, scratch_pool),
wcroot);
return SVN_NO_ERROR;
}
/* */
static svn_error_t *
read_entries(apr_hash_t **entries,
svn_wc__db_t *db,
const char *dir_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc__db_wcroot_t *wcroot;
const char *dir_relpath;
int wc_format;
SVN_ERR(svn_wc__db_temp_get_format(&wc_format, db, dir_abspath,
scratch_pool));
if (wc_format < SVN_WC__WC_NG_VERSION)
return svn_error_trace(svn_wc__read_entries_old(entries,
dir_abspath,
result_pool,
scratch_pool));
SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath,
db, dir_abspath,
scratch_pool, scratch_pool));
VERIFY_USABLE_WCROOT(wcroot);
SVN_WC__DB_WITH_TXN(read_entries_new(entries,
db, dir_abspath,
wcroot, dir_relpath,
result_pool, scratch_pool),
wcroot);
return SVN_NO_ERROR;
}
/* For a given LOCAL_ABSPATH, using DB, set *ADM_ABSPATH to the directory in
which the entry information is located, and *ENTRY_NAME to the entry name
to access that entry.
KIND is as in svn_wc__get_entry().
Return the results in RESULT_POOL and use SCRATCH_POOL for temporary
allocations. */
static svn_error_t *
get_entry_access_info(const char **adm_abspath,
const char **entry_name,
svn_wc__db_t *db,
const char *local_abspath,
svn_node_kind_t kind,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc_adm_access_t *adm_access;
svn_boolean_t read_from_subdir = FALSE;
/* If the caller didn't know the node kind, then stat the path. Maybe
it is really there, and we can speed up the steps below. */
if (kind == svn_node_unknown)
{
svn_node_kind_t on_disk;
/* Do we already have an access baton for LOCAL_ABSPATH? */
adm_access = svn_wc__adm_retrieve_internal2(db, local_abspath,
scratch_pool);
if (adm_access)
{
/* Sweet. The node is a directory. */
on_disk = svn_node_dir;
}
else
{
svn_boolean_t special;
/* What's on disk? */
SVN_ERR(svn_io_check_special_path(local_abspath, &on_disk, &special,
scratch_pool));
}
if (on_disk != svn_node_dir)
{
/* If this is *anything* besides a directory (FILE, NONE, or
UNKNOWN), then we cannot treat it as a versioned directory
containing entries to read. Leave READ_FROM_SUBDIR as FALSE,
so that the parent will be examined.
For NONE and UNKNOWN, it may be that metadata exists for the
node, even though on-disk is unhelpful.
If NEED_PARENT_STUB is TRUE, and the entry is not a DIRECTORY,
then we'll error.
If NEED_PARENT_STUB if FALSE, and we successfully read a stub,
then this on-disk node is obstructing the read. */
}
else
{
/* We found a directory for this UNKNOWN node. Determine whether
we need to read inside it. */
read_from_subdir = TRUE;
}
}
else if (kind == svn_node_dir)
{
read_from_subdir = TRUE;
}
if (read_from_subdir)
{
/* KIND must be a DIR or UNKNOWN (and we found a subdir). We want
the "real" data, so treat LOCAL_ABSPATH as a versioned directory. */
*adm_abspath = apr_pstrdup(result_pool, local_abspath);
*entry_name = "";
}
else
{
/* FILE node needs to read the parent directory. Or a DIR node
needs to read from the parent to get at the stub entry. Or this
is an UNKNOWN node, and we need to examine the parent. */
svn_dirent_split(adm_abspath, entry_name, local_abspath, result_pool);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__get_entry(const svn_wc_entry_t **entry,
svn_wc__db_t *db,
const char *local_abspath,
svn_boolean_t allow_unversioned,
svn_node_kind_t kind,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const char *dir_abspath;
const char *entry_name;
SVN_ERR(get_entry_access_info(&dir_abspath, &entry_name, db, local_abspath,
kind, scratch_pool, scratch_pool));
{
const svn_wc_entry_t *parent_entry;
svn_error_t *err;
/* NOTE: if KIND is UNKNOWN and we decided to examine the *parent*
directory, then it is possible we moved out of the working copy.
If the on-disk node is a DIR, and we asked for a stub, then we
obviously can't provide that (parent has no info). If the on-disk
node is a FILE/NONE/UNKNOWN, then it is obstructing the real
LOCAL_ABSPATH (or it was never a versioned item). In all these
cases, the read_entries() will (properly) throw an error.
NOTE: if KIND is a DIR and we asked for the real data, but it is
obstructed on-disk by some other node kind (NONE, FILE, UNKNOWN),
then this will throw an error. */
err = read_entry_pair(&parent_entry, entry,
db, dir_abspath, entry_name,
result_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_MISSING || kind != svn_node_unknown
|| *entry_name != '\0')
return svn_error_trace(err);
svn_error_clear(err);
/* The caller didn't know the node type, we saw a directory there,
we attempted to read IN that directory, and then wc_db reports
that it is NOT a working copy directory. It is possible that
one of two things has happened:
1) a directory is obstructing a file in the parent
2) the (versioned) directory's contents have been removed
Let's assume situation (1); if that is true, then we can just
return the newly-found data.
If we assumed (2), then a valid result still won't help us
since the caller asked for the actual contents, not the stub
(which is why we read *into* the directory). However, if we
assume (1) and get back a stub, then we have verified a
missing, versioned directory, and can return an error
describing that.
Redo the fetch, but "insist" we are trying to find a file.
This will read from the parent directory of the "file". */
err = svn_wc__get_entry(entry, db, local_abspath, allow_unversioned,
svn_node_file, result_pool, scratch_pool);
if (err == SVN_NO_ERROR)
return SVN_NO_ERROR;
if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND)
return svn_error_trace(err);
svn_error_clear(err);
/* We asked for a FILE, but the node found is a DIR. Thus, we
are looking at a stub. Originally, we tried to read into the
subdir because NEED_PARENT_STUB is FALSE. The stub we just
read is not going to work for the caller, so inform them of
the missing subdirectory. */
SVN_ERR_ASSERT(*entry != NULL && (*entry)->kind == svn_node_dir);
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("Admin area of '%s' is missing"),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
}
if (*entry == NULL)
{
if (allow_unversioned)
return SVN_NO_ERROR;
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("'%s' is not under version control"),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
/* The caller had the wrong information. */
if ((kind == svn_node_file && (*entry)->kind != svn_node_file)
|| (kind == svn_node_dir && (*entry)->kind != svn_node_dir))
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
_("'%s' is not of the right kind"),
svn_dirent_local_style(local_abspath,
scratch_pool));
return SVN_NO_ERROR;
}
/* TODO ### Rewrite doc string to mention ENTRIES_ALL; not ADM_ACCESS.
Prune the deleted entries from the cached entries in ADM_ACCESS, and
return that collection in *ENTRIES_PRUNED. SCRATCH_POOL is used for local,
short term, memory allocation, RESULT_POOL for permanent stuff. */
static svn_error_t *
prune_deleted(apr_hash_t **entries_pruned,
apr_hash_t *entries_all,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_hash_index_t *hi;
if (!entries_all)
{
*entries_pruned = NULL;
return SVN_NO_ERROR;
}
/* I think it will be common for there to be no deleted entries, so
it is worth checking for that case as we can optimise it. */
for (hi = apr_hash_first(scratch_pool, entries_all);
hi;
hi = apr_hash_next(hi))
{
svn_boolean_t hidden;
SVN_ERR(svn_wc__entry_is_hidden(&hidden,
apr_hash_this_val(hi)));
if (hidden)
break;
}
if (! hi)
{
/* There are no deleted entries, so we can use the full hash */
*entries_pruned = entries_all;
return SVN_NO_ERROR;
}
/* Construct pruned hash without deleted entries */
*entries_pruned = apr_hash_make(result_pool);
for (hi = apr_hash_first(scratch_pool, entries_all);
hi;
hi = apr_hash_next(hi))
{
const void *key = apr_hash_this_key(hi);
const svn_wc_entry_t *entry = apr_hash_this_val(hi);
svn_boolean_t hidden;
SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry));
if (!hidden)
svn_hash_sets(*entries_pruned, key, entry);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__entries_read_internal(apr_hash_t **entries,
svn_wc_adm_access_t *adm_access,
svn_boolean_t show_hidden,
apr_pool_t *pool)
{
apr_hash_t *new_entries;
new_entries = svn_wc__adm_access_entries(adm_access);
if (! new_entries)
{
svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
const char *local_abspath = svn_wc__adm_access_abspath(adm_access);
apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access);
SVN_ERR(read_entries(&new_entries, db, local_abspath,
result_pool, pool));
svn_wc__adm_access_set_entries(adm_access, new_entries);
}
if (show_hidden)
*entries = new_entries;
else
SVN_ERR(prune_deleted(entries, new_entries,
svn_wc__adm_access_pool_internal(adm_access),
pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_entries_read(apr_hash_t **entries,
svn_wc_adm_access_t *adm_access,
svn_boolean_t show_hidden,
apr_pool_t *pool)
{
return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access,
show_hidden, pool));
}
/* No transaction required: called from write_entry which is itself
transaction-wrapped. */
static svn_error_t *
insert_node(svn_sqlite__db_t *sdb,
const db_node_t *node,
apr_pool_t *scratch_pool)
{
svn_sqlite__stmt_t *stmt;
svn_boolean_t present = (node->presence == svn_wc__db_status_normal
|| node->presence == svn_wc__db_status_incomplete);
SVN_ERR_ASSERT(node->op_depth > 0 || node->repos_relpath);
SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnnnsn",
node->wc_id,
node->local_relpath,
node->op_depth,
node->parent_relpath,
/* Setting depth for files? */
(node->kind == svn_node_dir && present)
? svn_depth_to_word(node->depth)
: NULL));
if (present && node->repos_relpath)
{
SVN_ERR(svn_sqlite__bind_revnum(stmt, 11, node->changed_rev));
SVN_ERR(svn_sqlite__bind_int64(stmt, 12, node->changed_date));
SVN_ERR(svn_sqlite__bind_text(stmt, 13, node->changed_author));
}
if (node->repos_relpath
&& node->presence != svn_wc__db_status_base_deleted)
{
SVN_ERR(svn_sqlite__bind_int64(stmt, 5,
node->repos_id));
SVN_ERR(svn_sqlite__bind_text(stmt, 6,
node->repos_relpath));
SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, node->revision));
}
SVN_ERR(svn_sqlite__bind_token(stmt, 8, presence_map, node->presence));
if (node->kind == svn_node_none)
SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown"));
else
SVN_ERR(svn_sqlite__bind_token(stmt, 10, kind_map, node->kind));
if (node->kind == svn_node_file && present)
{
if (!node->checksum
&& node->op_depth == 0
&& node->presence != svn_wc__db_status_not_present
&& node->presence != svn_wc__db_status_excluded
&& node->presence != svn_wc__db_status_server_excluded)
return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
_("The file '%s' has no checksum"),
svn_dirent_local_style(node->local_relpath,
scratch_pool));
SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, node->checksum,
scratch_pool));
if (node->repos_relpath)
{
if (node->recorded_size != SVN_INVALID_FILESIZE)
SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->recorded_size));
SVN_ERR(svn_sqlite__bind_int64(stmt, 17, node->recorded_time));
}
}
/* ### Never set, props done later */
if (node->properties && present && node->repos_relpath)
SVN_ERR(svn_sqlite__bind_properties(stmt, 15, node->properties,
scratch_pool));
if (node->file_external)
SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));
if (node->inherited_props && present)
SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, node->inherited_props,
scratch_pool));
SVN_ERR(svn_sqlite__insert(NULL, stmt));
return SVN_NO_ERROR;
}
/* */
static svn_error_t *
insert_actual_node(svn_sqlite__db_t *sdb,
svn_wc__db_t *db,
const char *wri_abspath,
const db_actual_node_t *actual_node,
apr_pool_t *scratch_pool)
{
svn_sqlite__stmt_t *stmt;
svn_skel_t *conflict_data = NULL;
SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE));
SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id));
SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath));
SVN_ERR(svn_sqlite__bind_text(stmt, 3, actual_node->parent_relpath));
if (actual_node->properties)
SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties,
scratch_pool));
if (actual_node->changelist)
SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist));
SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(
&conflict_data,
db, wri_abspath,
actual_node->local_relpath,
actual_node->conflict_old,
actual_node->conflict_working,
actual_node->conflict_new,
actual_node->prop_reject,
actual_node->tree_conflict_data,
actual_node->tree_conflict_data
? strlen(actual_node->tree_conflict_data)
: 0,
scratch_pool, scratch_pool));
if (conflict_data)
{
svn_stringbuf_t *data = svn_skel__unparse(conflict_data, scratch_pool);
SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len));
}
/* Execute and reset the insert clause. */
return svn_error_trace(svn_sqlite__insert(NULL, stmt));
}
static svn_boolean_t
is_switched(db_node_t *parent,
db_node_t *child,
apr_pool_t *scratch_pool)
{
if (parent && child)
{
if (parent->repos_id != child->repos_id)
return TRUE;
if (parent->repos_relpath && child->repos_relpath)
{
const char *unswitched
= svn_relpath_join(parent->repos_relpath,
svn_relpath_basename(child->local_relpath,
scratch_pool),
scratch_pool);
if (strcmp(unswitched, child->repos_relpath))
return TRUE;
}
}
return FALSE;
}
struct write_baton {
db_node_t *base;
db_node_t *work;
db_node_t *below_work;
apr_hash_t *tree_conflicts;
};
#define WRITE_ENTRY_ASSERT(expr) \
if (!(expr)) \
return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, \
_("Unable to upgrade '%s' at line %d"), \
svn_dirent_local_style( \
svn_dirent_join(root_abspath, \
local_relpath, \
scratch_pool), \
scratch_pool), __LINE__)
/* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and
REPOS_ROOT will all be used for writing ENTRY.
### transitioning from straight sql to using the wc_db APIs. For the
### time being, we'll need both parameters. */
static svn_error_t *
write_entry(struct write_baton **entry_node,
const struct write_baton *parent_node,
svn_wc__db_t *db,
svn_sqlite__db_t *sdb,
apr_int64_t wc_id,
apr_int64_t repos_id,
const svn_wc_entry_t *entry,
const svn_wc__text_base_info_t *text_base_info,
const char *local_relpath,
const char *tmp_entry_abspath,
const char *root_abspath,
const svn_wc_entry_t *this_dir,
svn_boolean_t create_locks,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
db_node_t *base_node = NULL;
db_node_t *working_node = NULL, *below_working_node = NULL;
db_actual_node_t *actual_node = NULL;
const char *parent_relpath;
apr_hash_t *tree_conflicts;
if (*local_relpath == '\0')
parent_relpath = NULL;
else
parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
/* This is how it should work, it doesn't work like this yet because
we need proper op_depth to layer the working nodes.
Using "svn add", "svn rm", "svn cp" only files can be replaced
pre-wcng; directories can only be normal, deleted or added.
Files cannot be replaced within a deleted directory, so replaced
files can only exist in a normal directory, or a directory that
is added+copied. In a normal directory a replaced file needs a
base node and a working node, in an added+copied directory a
replaced file needs two working nodes at different op-depths.
With just the above operations the conversion for files and
directories is straightforward:
pre-wcng wcng
parent child parent child
normal normal base base
add+copied normal+copied work work
normal+copied normal+copied work work
normal delete base base+work
delete delete base+work base+work
add+copied delete work work
normal add base work
add add work work
add+copied add work work
normal add+copied base work
add add+copied work work
add+copied add+copied work work
normal replace base base+work
add+copied replace work work+work
normal replace+copied base base+work
add+copied replace+copied work work+work
However "svn merge" make this more complicated. The pre-wcng
"svn merge" is capable of replacing a directory, that is it can
mark the whole tree deleted, and then copy another tree on top.
The entries then represent the replacing tree overlayed on the
deleted tree.
original replace schedule in
tree tree combined tree
A A replace+copied
A/f delete+copied
A/g A/g replace+copied
A/h add+copied
A/B A/B replace+copied
A/B/f delete+copied
A/B/g A/B/g replace+copied
A/B/h add+copied
A/C delete+copied
A/C/f delete+copied
A/D add+copied
A/D/f add+copied
The original tree could be normal tree, or an add+copied tree.
Committing such a merge generally worked, but making further tree
modifications before commit sometimes failed.
The root of the replace is handled like the file replace:
pre-wcng wcng
parent child parent child
normal replace+copied base base+work
add+copied replace+copied work work+work
although obviously the node is a directory rather than a file.
There are then more conversion states where the parent is
replaced.
pre-wcng wcng
parent child parent child
replace+copied add [base|work]+work work
replace+copied add+copied [base|work]+work work
replace+copied delete+copied [base|work]+work [base|work]+work
delete+copied delete+copied [base|work]+work [base|work]+work
replace+copied replace+copied [base|work]+work [base|work]+work
*/
WRITE_ENTRY_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal);
WRITE_ENTRY_ASSERT(!parent_node || parent_node->base
|| parent_node->below_work || parent_node->work);
switch (entry->schedule)
{
case svn_wc_schedule_normal:
if (entry->copied ||
(entry->depth == svn_depth_exclude
&& parent_node && !parent_node->base && parent_node->work))
working_node = MAYBE_ALLOC(working_node, result_pool);
else
base_node = MAYBE_ALLOC(base_node, result_pool);
break;
case svn_wc_schedule_add:
working_node = MAYBE_ALLOC(working_node, result_pool);
if (entry->deleted)
{
if (parent_node->base)
base_node = MAYBE_ALLOC(base_node, result_pool);
else
below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
}
break;
case svn_wc_schedule_delete:
working_node = MAYBE_ALLOC(working_node, result_pool);
if (parent_node->base)
base_node = MAYBE_ALLOC(base_node, result_pool);
if (parent_node->work)
below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
break;
case svn_wc_schedule_replace:
working_node = MAYBE_ALLOC(working_node, result_pool);
if (parent_node->base)
base_node = MAYBE_ALLOC(base_node, result_pool);
else
below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
break;
}
/* Something deleted in this revision means there should always be a
BASE node to indicate the not-present node. */
if (entry->deleted)
{
WRITE_ENTRY_ASSERT(base_node || below_working_node);
WRITE_ENTRY_ASSERT(!entry->incomplete);
if (base_node)
base_node->presence = svn_wc__db_status_not_present;
else
below_working_node->presence = svn_wc__db_status_not_present;
}
else if (entry->absent)
{
WRITE_ENTRY_ASSERT(base_node && !working_node && !below_working_node);
WRITE_ENTRY_ASSERT(!entry->incomplete);
base_node->presence = svn_wc__db_status_server_excluded;
}
if (entry->copied)
{
db_node_t *work = parent_node->work
? parent_node->work
: parent_node->below_work;
if (entry->copyfrom_url)
{
working_node->repos_id = repos_id;
working_node->repos_relpath = svn_uri_skip_ancestor(
this_dir->repos, entry->copyfrom_url,
result_pool);
working_node->revision = entry->copyfrom_rev;
working_node->op_depth
= svn_wc__db_op_depth_for_upgrade(local_relpath);
if (work && work->repos_relpath
&& work->repos_id == repos_id
&& work->revision == entry->copyfrom_rev)
{
const char *name;
name = svn_relpath_skip_ancestor(work->repos_relpath,
working_node->repos_relpath);
if (name
&& !strcmp(name, svn_relpath_basename(local_relpath, NULL)))
{
working_node->op_depth = work->op_depth;
}
}
}
else if (work && work->repos_relpath)
{
working_node->repos_id = repos_id;
working_node->repos_relpath
= svn_relpath_join(work->repos_relpath,
svn_relpath_basename(local_relpath, NULL),
result_pool);
working_node->revision = work->revision;
working_node->op_depth = work->op_depth;
}
else if (parent_node->below_work
&& parent_node->below_work->repos_relpath)
{
/* Parent deleted, this not-present or similar */
working_node->repos_id = repos_id;
working_node->repos_relpath
= svn_relpath_join(parent_node->below_work->repos_relpath,
svn_relpath_basename(local_relpath, NULL),
result_pool);
working_node->revision = parent_node->below_work->revision;
working_node->op_depth = parent_node->below_work->op_depth;
}
else
return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
_("No copyfrom URL for '%s'"),
svn_dirent_local_style(local_relpath,
scratch_pool));
if (work && work->op_depth != working_node->op_depth
&& work->repos_relpath
&& work->repos_id == working_node->repos_id
&& work->presence == svn_wc__db_status_normal
&& !below_working_node)
{
/* Introduce a not-present node! */
below_working_node = MAYBE_ALLOC(below_working_node, scratch_pool);
below_working_node->wc_id = wc_id;
below_working_node->op_depth = work->op_depth;
below_working_node->local_relpath = local_relpath;
below_working_node->parent_relpath = parent_relpath;
below_working_node->presence = svn_wc__db_status_not_present;
below_working_node->repos_id = repos_id;
below_working_node->repos_relpath = working_node->local_relpath;
SVN_ERR(insert_node(sdb, below_working_node, scratch_pool));
below_working_node = NULL; /* Don't write a present intermediate! */
}
}
if (entry->conflict_old)
{
actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
if (parent_relpath && entry->conflict_old)
actual_node->conflict_old = svn_relpath_join(parent_relpath,
entry->conflict_old,
scratch_pool);
else
actual_node->conflict_old = entry->conflict_old;
if (parent_relpath && entry->conflict_new)
actual_node->conflict_new = svn_relpath_join(parent_relpath,
entry->conflict_new,
scratch_pool);
else
actual_node->conflict_new = entry->conflict_new;
if (parent_relpath && entry->conflict_wrk)
actual_node->conflict_working = svn_relpath_join(parent_relpath,
entry->conflict_wrk,
scratch_pool);
else
actual_node->conflict_working = entry->conflict_wrk;
}
if (entry->prejfile)
{
actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
actual_node->prop_reject = svn_relpath_join((entry->kind == svn_node_dir
? local_relpath
: parent_relpath),
entry->prejfile,
scratch_pool);
}
if (entry->changelist)
{
actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
actual_node->changelist = entry->changelist;
}
/* ### set the text_mod value? */
if (entry_node && entry->tree_conflict_data)
{
/* Issues #3840/#3916: 1.6 stores multiple tree conflicts on the
parent node, 1.7 stores them directly on the conflited nodes.
So "((skel1) (skel2))" becomes "(skel1)" and "(skel2)" */
svn_skel_t *skel;
skel = svn_skel__parse(entry->tree_conflict_data,
strlen(entry->tree_conflict_data),
scratch_pool);
tree_conflicts = apr_hash_make(result_pool);
skel = skel->children;
while (skel)
{
svn_wc_conflict_description2_t *conflict;
svn_skel_t *new_skel;
const char *key;
/* *CONFLICT is allocated so it is safe to use a non-const pointer */
SVN_ERR(svn_wc__deserialize_conflict(
(const svn_wc_conflict_description2_t**)&conflict,
skel,
svn_dirent_join(root_abspath,
local_relpath,
scratch_pool),
scratch_pool, scratch_pool));
WRITE_ENTRY_ASSERT(conflict->kind == svn_wc_conflict_kind_tree);
SVN_ERR(svn_wc__serialize_conflict(&new_skel, conflict,
scratch_pool, scratch_pool));
/* Store in hash to be retrieved when writing the child
row. */
key = svn_dirent_skip_ancestor(root_abspath, conflict->local_abspath);
svn_hash_sets(tree_conflicts, apr_pstrdup(result_pool, key),
svn_skel__unparse(new_skel, result_pool)->data);
skel = skel->next;
}
}
else
tree_conflicts = NULL;
if (parent_node && parent_node->tree_conflicts)
{
const char *tree_conflict_data =
svn_hash_gets(parent_node->tree_conflicts, local_relpath);
if (tree_conflict_data)
{
actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
actual_node->tree_conflict_data = tree_conflict_data;
}
/* Reset hash so that we don't write the row again when writing
actual-only nodes */
svn_hash_sets(parent_node->tree_conflicts, local_relpath, NULL);
}
if (entry->file_external_path != NULL)
{
base_node = MAYBE_ALLOC(base_node, result_pool);
}
/* Insert the base node. */
if (base_node)
{
base_node->wc_id = wc_id;
base_node->local_relpath = local_relpath;
base_node->op_depth = 0;
base_node->parent_relpath = parent_relpath;
base_node->revision = entry->revision;
base_node->recorded_time = entry->text_time;
base_node->recorded_size = entry->working_size;
if (entry->depth != svn_depth_exclude)
base_node->depth = entry->depth;
else
{
base_node->presence = svn_wc__db_status_excluded;
base_node->depth = svn_depth_infinity;
}
if (entry->deleted)
{
WRITE_ENTRY_ASSERT(base_node->presence
== svn_wc__db_status_not_present);
/* ### should be svn_node_unknown, but let's store what we have. */
base_node->kind = entry->kind;
}
else if (entry->absent)
{
WRITE_ENTRY_ASSERT(base_node->presence
== svn_wc__db_status_server_excluded);
/* ### should be svn_node_unknown, but let's store what we have. */
base_node->kind = entry->kind;
/* Store the most likely revision in the node to avoid
base nodes without a valid revision. Of course
we remember that the data is still incomplete. */
if (!SVN_IS_VALID_REVNUM(base_node->revision) && parent_node->base)
base_node->revision = parent_node->base->revision;
}
else
{
base_node->kind = entry->kind;
if (base_node->presence != svn_wc__db_status_excluded)
{
/* All subdirs are initially incomplete, they stop being
incomplete when the entries file in the subdir is
upgraded and remain incomplete if that doesn't happen. */
if (entry->kind == svn_node_dir
&& strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
{
base_node->presence = svn_wc__db_status_incomplete;
/* Store the most likely revision in the node to avoid
base nodes without a valid revision. Of course
we remember that the data is still incomplete. */
if (parent_node->base)
base_node->revision = parent_node->base->revision;
}
else if (entry->incomplete)
{
/* ### nobody should have set the presence. */
WRITE_ENTRY_ASSERT(base_node->presence
== svn_wc__db_status_normal);
base_node->presence = svn_wc__db_status_incomplete;
}
}
}
if (entry->kind == svn_node_dir)
base_node->checksum = NULL;
else
{
if (text_base_info && text_base_info->revert_base.sha1_checksum)
base_node->checksum = text_base_info->revert_base.sha1_checksum;
else if (text_base_info && text_base_info->normal_base.sha1_checksum)
base_node->checksum = text_base_info->normal_base.sha1_checksum;
else
base_node->checksum = NULL;
/* The base MD5 checksum is available in the entry, unless there
* is a copied WORKING node. If possible, verify that the entry
* checksum matches the base file that we found. */
if (! (working_node && entry->copied))
{
svn_checksum_t *entry_md5_checksum, *found_md5_checksum;
SVN_ERR(svn_checksum_parse_hex(&entry_md5_checksum,
svn_checksum_md5,
entry->checksum, scratch_pool));
if (text_base_info && text_base_info->revert_base.md5_checksum)
found_md5_checksum = text_base_info->revert_base.md5_checksum;
else if (text_base_info
&& text_base_info->normal_base.md5_checksum)
found_md5_checksum = text_base_info->normal_base.md5_checksum;
else
found_md5_checksum = NULL;
if (entry_md5_checksum && found_md5_checksum &&
!svn_checksum_match(entry_md5_checksum, found_md5_checksum))
return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
_("Bad base MD5 checksum for '%s'; "
"expected: '%s'; found '%s'; "),
svn_dirent_local_style(
svn_dirent_join(root_abspath,
local_relpath,
scratch_pool),
scratch_pool),
svn_checksum_to_cstring_display(
entry_md5_checksum, scratch_pool),
svn_checksum_to_cstring_display(
found_md5_checksum, scratch_pool));
else
{
/* ### Not sure what conditions this should cover. */
/* SVN_ERR_ASSERT(entry->deleted || ...); */
}
}
}
if (this_dir->repos)
{
base_node->repos_id = repos_id;
if (entry->url != NULL)
{
base_node->repos_relpath = svn_uri_skip_ancestor(
this_dir->repos, entry->url,
result_pool);
}
else
{
const char *relpath = svn_uri_skip_ancestor(this_dir->repos,
this_dir->url,
scratch_pool);
if (relpath == NULL || *relpath == '\0')
base_node->repos_relpath = entry->name;
else
base_node->repos_relpath =
svn_dirent_join(relpath, entry->name, result_pool);
}
}
/* TODO: These values should always be present, if they are missing
during an upgrade, set a flag, and then ask the user to talk to the
server.
Note: cmt_rev is the distinguishing value. The others may be 0 or
NULL if the corresponding revprop has been deleted. */
base_node->changed_rev = entry->cmt_rev;
base_node->changed_date = entry->cmt_date;
base_node->changed_author = entry->cmt_author;
if (entry->file_external_path)
base_node->file_external = TRUE;
/* Switched nodes get an empty iprops cache. */
if (parent_node
&& is_switched(parent_node->base, base_node, scratch_pool))
base_node->inherited_props
= apr_array_make(scratch_pool, 0, sizeof(svn_prop_inherited_item_t*));
SVN_ERR(insert_node(sdb, base_node, scratch_pool));
/* We have to insert the lock after the base node, because the node
must exist to lookup various bits of repos related information for
the abs path. */
if (entry->lock_token && create_locks)
{
svn_wc__db_lock_t lock;
lock.token = entry->lock_token;
lock.owner = entry->lock_owner;
lock.comment = entry->lock_comment;
lock.date = entry->lock_creation_date;
SVN_ERR(svn_wc__db_lock_add(db, tmp_entry_abspath, &lock,
scratch_pool));
}
}
if (below_working_node)
{
db_node_t *work
= parent_node->below_work ? parent_node->below_work : parent_node->work;
below_working_node->wc_id = wc_id;
below_working_node->local_relpath = local_relpath;
below_working_node->op_depth = work->op_depth;
below_working_node->parent_relpath = parent_relpath;
below_working_node->presence = svn_wc__db_status_normal;
below_working_node->kind = entry->kind;
below_working_node->repos_id = work->repos_id;
below_working_node->revision = work->revision;
/* This is just guessing. If the node below would have been switched
or if it was updated to a different version, the guess would
fail. But we don't have better information pre wc-ng :( */
if (work->repos_relpath)
below_working_node->repos_relpath
= svn_relpath_join(work->repos_relpath,
svn_relpath_basename(local_relpath, NULL),
result_pool);
else
below_working_node->repos_relpath = NULL;
/* The revert_base checksum isn't available in the entry structure,
so the caller provides it. */
/* text_base_info is NULL for files scheduled to be added. */
below_working_node->checksum = NULL;
if (text_base_info)
{
if (entry->schedule == svn_wc_schedule_delete)
below_working_node->checksum =
text_base_info->normal_base.sha1_checksum;
else
below_working_node->checksum =
text_base_info->revert_base.sha1_checksum;
}
below_working_node->recorded_size = 0;
below_working_node->changed_rev = SVN_INVALID_REVNUM;
below_working_node->changed_date = 0;
below_working_node->changed_author = NULL;
below_working_node->depth = svn_depth_infinity;
below_working_node->recorded_time = 0;
below_working_node->properties = NULL;
if (working_node
&& entry->schedule == svn_wc_schedule_delete
&& working_node->repos_relpath)
{
/* We are lucky, our guesses above are not necessary. The known
correct information is in working. But our op_depth design
expects more information here */
below_working_node->repos_relpath = working_node->repos_relpath;
below_working_node->repos_id = working_node->repos_id;
below_working_node->revision = working_node->revision;
/* Nice for 'svn status' */
below_working_node->changed_rev = entry->cmt_rev;
below_working_node->changed_date = entry->cmt_date;
below_working_node->changed_author = entry->cmt_author;
/* And now remove it from WORKING, because in wc-ng code
should read it from the lower layer */
working_node->repos_relpath = NULL;
working_node->repos_id = 0;
working_node->revision = SVN_INVALID_REVNUM;
}
SVN_ERR(insert_node(sdb, below_working_node, scratch_pool));
}
/* Insert the working node. */
if (working_node)
{
working_node->wc_id = wc_id;
working_node->local_relpath = local_relpath;
working_node->parent_relpath = parent_relpath;
working_node->changed_rev = SVN_INVALID_REVNUM;
working_node->recorded_time = entry->text_time;
working_node->recorded_size = entry->working_size;
if (entry->depth != svn_depth_exclude)
working_node->depth = entry->depth;
else
{
working_node->presence = svn_wc__db_status_excluded;
working_node->depth = svn_depth_infinity;
}
if (entry->kind == svn_node_dir)
working_node->checksum = NULL;
else
{
working_node->checksum = NULL;
/* text_base_info is NULL for files scheduled to be added. */
if (text_base_info)
working_node->checksum = text_base_info->normal_base.sha1_checksum;
/* If an MD5 checksum is present in the entry, we can verify that
* it matches the MD5 of the base file we found earlier. */
#ifdef SVN_DEBUG
if (entry->checksum && text_base_info)
{
svn_checksum_t *md5_checksum;
SVN_ERR(svn_checksum_parse_hex(&md5_checksum, svn_checksum_md5,
entry->checksum, result_pool));
SVN_ERR_ASSERT(
md5_checksum && text_base_info->normal_base.md5_checksum);
SVN_ERR_ASSERT(svn_checksum_match(
md5_checksum, text_base_info->normal_base.md5_checksum));
}
#endif
}
working_node->kind = entry->kind;
if (working_node->presence != svn_wc__db_status_excluded)
{
/* All subdirs start of incomplete, and stop being incomplete
when the entries file in the subdir is upgraded. */
if (entry->kind == svn_node_dir
&& strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
{
working_node->presence = svn_wc__db_status_incomplete;
working_node->kind = svn_node_dir;
}
else if (entry->schedule == svn_wc_schedule_delete)
{
working_node->presence = svn_wc__db_status_base_deleted;
working_node->kind = entry->kind;
}
else
{
/* presence == normal */
working_node->kind = entry->kind;
if (entry->incomplete)
{
/* We shouldn't be overwriting another status. */
WRITE_ENTRY_ASSERT(working_node->presence
== svn_wc__db_status_normal);
working_node->presence = svn_wc__db_status_incomplete;
}
}
}
/* These should generally be unset for added and deleted files,
and contain whatever information we have for copied files. Let's
just store whatever we have.
Note: cmt_rev is the distinguishing value. The others may be 0 or
NULL if the corresponding revprop has been deleted. */
if (working_node->presence != svn_wc__db_status_base_deleted)
{
working_node->changed_rev = entry->cmt_rev;
working_node->changed_date = entry->cmt_date;
working_node->changed_author = entry->cmt_author;
}
if (entry->schedule == svn_wc_schedule_delete
&& parent_node->work
&& parent_node->work->presence == svn_wc__db_status_base_deleted)
{
working_node->op_depth = parent_node->work->op_depth;
}
else if (working_node->presence == svn_wc__db_status_excluded
&& parent_node->work)
{
working_node->op_depth = parent_node->work->op_depth;
}
else if (!entry->copied)
{
working_node->op_depth
= svn_wc__db_op_depth_for_upgrade(local_relpath);
}
SVN_ERR(insert_node(sdb, working_node, scratch_pool));
}
/* Insert the actual node. */
if (actual_node)
{
actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
actual_node->wc_id = wc_id;
actual_node->local_relpath = local_relpath;
actual_node->parent_relpath = parent_relpath;
SVN_ERR(insert_actual_node(sdb, db, tmp_entry_abspath,
actual_node, scratch_pool));
}
if (entry_node)
{
*entry_node = apr_palloc(result_pool, sizeof(**entry_node));
(*entry_node)->base = base_node;
(*entry_node)->work = working_node;
(*entry_node)->below_work = below_working_node;
(*entry_node)->tree_conflicts = tree_conflicts;
}
if (entry->file_external_path)
{
/* TODO: Maybe add a file external registration inside EXTERNALS here,
to allow removing file externals that aren't referenced from
svn:externals.
The svn:externals values are processed anyway after everything is
upgraded */
}
return SVN_NO_ERROR;
}
static svn_error_t *
write_actual_only_entries(apr_hash_t *tree_conflicts,
svn_sqlite__db_t *sdb,
svn_wc__db_t *db,
const char *wri_abspath,
apr_int64_t wc_id,
const char *parent_relpath,
apr_pool_t *scratch_pool)
{
apr_hash_index_t *hi;
for (hi = apr_hash_first(scratch_pool, tree_conflicts);
hi;
hi = apr_hash_next(hi))
{
db_actual_node_t *actual_node = NULL;
actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
actual_node->wc_id = wc_id;
actual_node->local_relpath = apr_hash_this_key(hi);
actual_node->parent_relpath = parent_relpath;
actual_node->tree_conflict_data = apr_hash_this_val(hi);
SVN_ERR(insert_actual_node(sdb, db, wri_abspath, actual_node,
scratch_pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__write_upgraded_entries(void **dir_baton,
void *parent_baton,
svn_wc__db_t *db,
svn_sqlite__db_t *sdb,
apr_int64_t repos_id,
apr_int64_t wc_id,
const char *dir_abspath,
const char *new_root_abspath,
apr_hash_t *entries,
apr_hash_t *text_bases_info,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const svn_wc_entry_t *this_dir;
apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
const char *old_root_abspath, *dir_relpath;
struct write_baton *parent_node = parent_baton;
struct write_baton *dir_node;
/* Get a copy of the "this dir" entry for comparison purposes. */
this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
/* If there is no "this dir" entry, something is wrong. */
if (! this_dir)
return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
_("No default entry in directory '%s'"),
svn_dirent_local_style(dir_abspath,
iterpool));
old_root_abspath = svn_dirent_get_longest_ancestor(dir_abspath,
new_root_abspath,
scratch_pool);
SVN_ERR_ASSERT(old_root_abspath[0]);
dir_relpath = svn_dirent_skip_ancestor(old_root_abspath, dir_abspath);
/* Write out "this dir" */
SVN_ERR(write_entry(&dir_node, parent_node, db, sdb,
wc_id, repos_id, this_dir, NULL, dir_relpath,
svn_dirent_join(new_root_abspath, dir_relpath,
iterpool),
old_root_abspath,
this_dir, FALSE, result_pool, iterpool));
for (hi = apr_hash_first(scratch_pool, entries); hi;
hi = apr_hash_next(hi))
{
const char *name = apr_hash_this_key(hi);
const svn_wc_entry_t *this_entry = apr_hash_this_val(hi);
const char *child_abspath, *child_relpath;
svn_wc__text_base_info_t *text_base_info
= svn_hash_gets(text_bases_info, name);
svn_pool_clear(iterpool);
/* Don't rewrite the "this dir" entry! */
if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0)
continue;
/* Write the entry. Pass TRUE for create locks, because we still
use this function for upgrading old working copies. */
child_abspath = svn_dirent_join(dir_abspath, name, iterpool);
child_relpath = svn_dirent_skip_ancestor(old_root_abspath, child_abspath);
SVN_ERR(write_entry(NULL, dir_node, db, sdb,
wc_id, repos_id,
this_entry, text_base_info, child_relpath,
svn_dirent_join(new_root_abspath, child_relpath,
iterpool),
old_root_abspath,
this_dir, TRUE, iterpool, iterpool));
}
if (dir_node->tree_conflicts)
SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, db,
new_root_abspath, wc_id, dir_relpath,
iterpool));
*dir_baton = dir_node;
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
svn_wc_entry_t *
svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool)
{
svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry));
/* Perform a trivial copy ... */
*dupentry = *entry;
/* ...and then re-copy stuff that needs to be duped into our pool. */
if (entry->name)
dupentry->name = apr_pstrdup(pool, entry->name);
if (entry->url)
dupentry->url = apr_pstrdup(pool, entry->url);
if (entry->repos)
dupentry->repos = apr_pstrdup(pool, entry->repos);
if (entry->uuid)
dupentry->uuid = apr_pstrdup(pool, entry->uuid);
if (entry->copyfrom_url)
dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url);
if (entry->conflict_old)
dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old);
if (entry->conflict_new)
dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new);
if (entry->conflict_wrk)
dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk);
if (entry->prejfile)
dupentry->prejfile = apr_pstrdup(pool, entry->prejfile);
if (entry->checksum)
dupentry->checksum = apr_pstrdup(pool, entry->checksum);
if (entry->cmt_author)
dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author);
if (entry->lock_token)
dupentry->lock_token = apr_pstrdup(pool, entry->lock_token);
if (entry->lock_owner)
dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner);
if (entry->lock_comment)
dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment);
if (entry->changelist)
dupentry->changelist = apr_pstrdup(pool, entry->changelist);
/* NOTE: we do not dup cachable_props or present_props since they
are deprecated. Use "" to indicate "nothing cachable or cached". */
dupentry->cachable_props = "";
dupentry->present_props = "";
if (entry->tree_conflict_data)
dupentry->tree_conflict_data = apr_pstrdup(pool,
entry->tree_conflict_data);
if (entry->file_external_path)
dupentry->file_external_path = apr_pstrdup(pool,
entry->file_external_path);
return dupentry;
}
/*** Generic Entry Walker */
/* A recursive entry-walker, helper for svn_wc_walk_entries3().
*
* For this directory (DIRPATH, ADM_ACCESS), call the "found_entry" callback
* in WALK_CALLBACKS, passing WALK_BATON to it. Then, for each versioned
* entry in this directory, call the "found entry" callback and then recurse
* (if it is a directory and if DEPTH allows).
*
* If SHOW_HIDDEN is true, include entries that are in a 'deleted' or
* 'absent' state (and not scheduled for re-addition), else skip them.
*
* Call CANCEL_FUNC with CANCEL_BATON to allow cancellation.
*/
static svn_error_t *
walker_helper(const char *dirpath,
svn_wc_adm_access_t *adm_access,
const svn_wc_entry_callbacks2_t *walk_callbacks,
void *walk_baton,
svn_depth_t depth,
svn_boolean_t show_hidden,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
apr_hash_t *entries;
apr_hash_index_t *hi;
svn_wc_entry_t *dot_entry;
svn_error_t *err;
svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden,
pool);
if (err)
SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
/* As promised, always return the '.' entry first. */
dot_entry = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
if (! dot_entry)
return walk_callbacks->handle_error
(dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
_("Directory '%s' has no THIS_DIR entry"),
svn_dirent_local_style(dirpath, pool)),
walk_baton, pool);
/* Call the "found entry" callback for this directory as a "this dir"
* entry. Note that if this directory has been reached by recursion, this
* is the second visit as it will already have been visited once as a
* child entry of its parent. */
err = walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, subpool);
if(err)
SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
if (depth == svn_depth_empty)
return SVN_NO_ERROR;
/* Loop over each of the other entries. */
for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
{
const char *name = apr_hash_this_key(hi);
const svn_wc_entry_t *current_entry = apr_hash_this_val(hi);
const char *entrypath;
const char *entry_abspath;
svn_boolean_t hidden;
svn_pool_clear(subpool);
/* See if someone wants to cancel this operation. */
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
/* Skip the "this dir" entry. */
if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
continue;
entrypath = svn_dirent_join(dirpath, name, subpool);
SVN_ERR(svn_wc__entry_is_hidden(&hidden, current_entry));
SVN_ERR(svn_dirent_get_absolute(&entry_abspath, entrypath, subpool));
/* Call the "found entry" callback for this entry. (For a directory,
* this is the first visit: as a child.) */
if (current_entry->kind == svn_node_file
|| depth >= svn_depth_immediates)
{
err = walk_callbacks->found_entry(entrypath, current_entry,
walk_baton, subpool);
if (err)
SVN_ERR(walk_callbacks->handle_error(entrypath, err,
walk_baton, pool));
}
/* Recurse into this entry if appropriate. */
if (current_entry->kind == svn_node_dir
&& !hidden
&& depth >= svn_depth_immediates)
{
svn_wc_adm_access_t *entry_access;
svn_depth_t depth_below_here = depth;
if (depth == svn_depth_immediates)
depth_below_here = svn_depth_empty;
entry_access = svn_wc__adm_retrieve_internal2(db, entry_abspath,
subpool);
if (entry_access)
SVN_ERR(walker_helper(entrypath, entry_access,
walk_callbacks, walk_baton,
depth_below_here, show_hidden,
cancel_func, cancel_baton,
subpool));
}
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__walker_default_error_handler(const char *path,
svn_error_t *err,
void *walk_baton,
apr_pool_t *pool)
{
/* Note: don't trace this. We don't want to insert a false "stack frame"
onto an error generated elsewhere. */
return svn_error_trace(err);
}
/* The public API. */
svn_error_t *
svn_wc_walk_entries3(const char *path,
svn_wc_adm_access_t *adm_access,
const svn_wc_entry_callbacks2_t *walk_callbacks,
void *walk_baton,
svn_depth_t walk_depth,
svn_boolean_t show_hidden,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
const char *local_abspath;
svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
svn_error_t *err;
svn_node_kind_t kind;
svn_wc__db_status_t status;
SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
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,
db, local_abspath,
pool, pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
return svn_error_trace(err);
/* Remap into SVN_ERR_UNVERSIONED_RESOURCE. */
svn_error_clear(err);
return walk_callbacks->handle_error(
path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
_("'%s' is not under version control"),
svn_dirent_local_style(local_abspath, pool)),
walk_baton, pool);
}
if (kind == svn_node_file
|| status == svn_wc__db_status_excluded
|| status == svn_wc__db_status_server_excluded)
{
const svn_wc_entry_t *entry;
/* ### we should stop passing out entry structures.
###
### we should not call handle_error for an error the *callback*
### gave us. let it deal with the problem before returning. */
if (!show_hidden
&& (status == svn_wc__db_status_not_present
|| status == svn_wc__db_status_excluded
|| status == svn_wc__db_status_server_excluded))
{
/* The fool asked to walk a "hidden" node. Report the node as
unversioned.
### this is incorrect behavior. see depth_test 36. the walk
### API will be revamped to avoid entry structures. we should
### be able to solve the problem with the new API. (since we
### shouldn't return a hidden entry here) */
return walk_callbacks->handle_error(
path, svn_error_createf(
SVN_ERR_UNVERSIONED_RESOURCE, NULL,
_("'%s' is not under version control"),
svn_dirent_local_style(local_abspath, pool)),
walk_baton, pool);
}
SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE,
svn_node_file, pool, pool));
err = walk_callbacks->found_entry(path, entry, walk_baton, pool);
if (err)
return walk_callbacks->handle_error(path, err, walk_baton, pool);
return SVN_NO_ERROR;
}
if (kind == svn_node_dir)
return walker_helper(path, adm_access, walk_callbacks, walk_baton,
walk_depth, show_hidden, cancel_func, cancel_baton,
pool);
return walk_callbacks->handle_error(
path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
_("'%s' has an unrecognized node kind"),
svn_dirent_local_style(local_abspath, pool)),
walk_baton, pool);
}