blob: b8cbc0ed30d9430f3aa13f2f769139a5db0a2fce [file] [log] [blame]
/*
* status.c: construct a status structure from an entry structure
*
* ====================================================================
* 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 <assert.h>
#include <string.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_hash.h>
#include "svn_pools.h"
#include "svn_types.h"
#include "svn_delta.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_io.h"
#include "svn_config.h"
#include "svn_time.h"
#include "svn_hash.h"
#include "svn_sorts.h"
#include "svn_private_config.h"
#include "wc.h"
#include "props.h"
#include "private/svn_sorts_private.h"
#include "private/svn_wc_private.h"
#include "private/svn_fspath.h"
#include "private/svn_editor.h"
/* The file internal variant of svn_wc_status3_t, with slightly more
data.
Instead of directly creating svn_wc_status3_t instances, we really
create instances of this struct with slightly more data for processing
by the status walker and status editor.
svn_wc_status3_dup() allocates space for this struct, but doesn't
copy the actual data. The remaining fields are copied by hash_stash(),
which is where the status editor stashes information for producing
later. */
typedef struct svn_wc__internal_status_t
{
svn_wc_status3_t s; /* First member; same pointer*/
svn_boolean_t has_descendants;
svn_boolean_t op_root;
/* Make sure to update hash_stash() when adding values here */
} svn_wc__internal_status_t;
/*** Baton used for walking the local status */
struct walk_status_baton
{
/* The DB handle for managing the working copy state. */
svn_wc__db_t *db;
/*** External handling ***/
/* Target of the status */
const char *target_abspath;
/* Should we ignore text modifications? */
svn_boolean_t ignore_text_mods;
/* Scan the working copy for local modifications and missing nodes. */
svn_boolean_t check_working_copy;
/* Externals info harvested during the status run. */
apr_hash_t *externals;
/*** Repository lock handling ***/
/* The repository root URL, if set. */
const char *repos_root;
/* Repository locks, if set. */
apr_hash_t *repos_locks;
};
/*** Editor batons ***/
struct edit_baton
{
/* For status, the "destination" of the edit. */
const char *anchor_abspath;
const char *target_abspath;
const char *target_basename;
/* The DB handle for managing the working copy state. */
svn_wc__db_t *db;
/* The overall depth of this edit (a dir baton may override this).
*
* If this is svn_depth_unknown, the depths found in the working
* copy will govern the edit; or if the edit depth indicates a
* descent deeper than the found depths are capable of, the found
* depths also govern, of course (there's no point descending into
* something that's not there).
*/
svn_depth_t default_depth;
/* Do we want all statuses (instead of just the interesting ones) ? */
svn_boolean_t get_all;
/* Ignore the svn:ignores. */
svn_boolean_t no_ignore;
/* The comparison revision in the repository. This is a reference
because this editor returns this rev to the driver directly, as
well as in each statushash entry. */
svn_revnum_t *target_revision;
/* Status function/baton. */
svn_wc_status_func4_t status_func;
void *status_baton;
/* Cancellation function/baton. */
svn_cancel_func_t cancel_func;
void *cancel_baton;
/* The configured set of default ignores. */
const apr_array_header_t *ignores;
/* Status item for the path represented by the anchor of the edit. */
svn_wc__internal_status_t *anchor_status;
/* Was open_root() called for this edit drive? */
svn_boolean_t root_opened;
/* The local status baton */
struct walk_status_baton wb;
};
struct dir_baton
{
/* The path to this directory. */
const char *local_abspath;
/* Basename of this directory. */
const char *name;
/* The global edit baton. */
struct edit_baton *edit_baton;
/* Baton for this directory's parent, or NULL if this is the root
directory. */
struct dir_baton *parent_baton;
/* The ambient requested depth below this point in the edit. This
can differ from the parent baton's depth (with the edit baton
considered the ultimate parent baton). For example, if the
parent baton has svn_depth_immediates, then here we should have
svn_depth_empty, because there would be no further recursion, not
even to file children. */
svn_depth_t depth;
/* Is this directory filtered out due to depth? (Note that if this
is TRUE, the depth field is undefined.) */
svn_boolean_t excluded;
/* 'svn status' shouldn't print status lines for things that are
added; we're only interest in asking if objects that the user
*already* has are up-to-date or not. Thus if this flag is set,
the next two will be ignored. :-) */
svn_boolean_t added;
/* Gets set iff there's a change to this directory's properties, to
guide us when syncing adm files later. */
svn_boolean_t prop_changed;
/* This means (in terms of 'svn status') that some child was deleted
or added to the directory */
svn_boolean_t text_changed;
/* Working copy status structures for children of this directory.
This hash maps const char * abspaths to svn_wc_status3_t *
status items. */
apr_hash_t *statii;
/* The pool in which this baton itself is allocated. */
apr_pool_t *pool;
/* The repository root relative path to this item in the repository. */
const char *repos_relpath;
/* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
svn_node_kind_t ood_kind;
svn_revnum_t ood_changed_rev;
apr_time_t ood_changed_date;
const char *ood_changed_author;
};
struct file_baton
{
/* Absolute local path to this file */
const char *local_abspath;
/* The global edit baton. */
struct edit_baton *edit_baton;
/* Baton for this file's parent directory. */
struct dir_baton *dir_baton;
/* Pool specific to this file_baton. */
apr_pool_t *pool;
/* Basename of this file */
const char *name;
/* 'svn status' shouldn't print status lines for things that are
added; we're only interest in asking if objects that the user
*already* has are up-to-date or not. Thus if this flag is set,
the next two will be ignored. :-) */
svn_boolean_t added;
/* This gets set if the file underwent a text change, which guides
the code that syncs up the adm dir and working copy. */
svn_boolean_t text_changed;
/* This gets set if the file underwent a prop change, which guides
the code that syncs up the adm dir and working copy. */
svn_boolean_t prop_changed;
/* The repository root relative path to this item in the repository. */
const char *repos_relpath;
/* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */
svn_node_kind_t ood_kind;
svn_revnum_t ood_changed_rev;
apr_time_t ood_changed_date;
const char *ood_changed_author;
};
/** Code **/
/* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using
information in INFO if available, falling back on
PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and
finally falling back on querying DB. */
static svn_error_t *
get_repos_root_url_relpath(const char **repos_relpath,
const char **repos_root_url,
const char **repos_uuid,
const struct svn_wc__db_info_t *info,
const char *parent_repos_relpath,
const char *parent_repos_root_url,
const char *parent_repos_uuid,
svn_wc__db_t *db,
const char *local_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
if (info->repos_relpath && info->repos_root_url)
{
*repos_relpath = apr_pstrdup(result_pool, info->repos_relpath);
*repos_root_url = apr_pstrdup(result_pool, info->repos_root_url);
*repos_uuid = apr_pstrdup(result_pool, info->repos_uuid);
}
else if (parent_repos_relpath && parent_repos_root_url)
{
*repos_relpath = svn_relpath_join(parent_repos_relpath,
svn_dirent_basename(local_abspath,
NULL),
result_pool);
*repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url);
*repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid);
}
else
{
SVN_ERR(svn_wc__db_read_repos_info(NULL,
repos_relpath, repos_root_url,
repos_uuid,
db, local_abspath,
result_pool, scratch_pool));
}
return SVN_NO_ERROR;
}
static svn_error_t *
internal_status(svn_wc__internal_status_t **status,
svn_wc__db_t *db,
const char *local_abspath,
svn_boolean_t check_working_copy,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
/* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in
RESULT_POOL and use SCRATCH_POOL for temporary allocations.
PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root
and repository relative path of the parent of LOCAL_ABSPATH or NULL if
LOCAL_ABSPATH doesn't have a versioned parent directory.
DIRENT is the local representation of LOCAL_ABSPATH in the working copy or
NULL if the node does not exist on disk.
If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then
*STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be
allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then
don't check for text mods, assume there are none and set and *STATUS
returned to reflect that assumption. If CHECK_WORKING_COPY is FALSE,
do not adjust the result for missing working copy files.
The status struct's repos_lock field will be set to REPOS_LOCK.
*/
static svn_error_t *
assemble_status(svn_wc__internal_status_t **status,
svn_wc__db_t *db,
const char *local_abspath,
const char *parent_repos_root_url,
const char *parent_repos_relpath,
const char *parent_repos_uuid,
const struct svn_wc__db_info_t *info,
const svn_io_dirent2_t *dirent,
svn_boolean_t get_all,
svn_boolean_t ignore_text_mods,
svn_boolean_t check_working_copy,
const svn_lock_t *repos_lock,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc__internal_status_t *inner_stat;
svn_wc_status3_t *stat;
svn_boolean_t switched_p = FALSE;
svn_boolean_t copied = FALSE;
svn_boolean_t conflicted;
const char *moved_from_abspath = NULL;
/* Defaults for two main variables. */
enum svn_wc_status_kind node_status = svn_wc_status_normal;
enum svn_wc_status_kind text_status = svn_wc_status_normal;
enum svn_wc_status_kind prop_status = svn_wc_status_none;
if (!info->repos_relpath || !parent_repos_relpath)
switched_p = FALSE;
else
{
/* A node is switched if it doesn't have the implied repos_relpath */
const char *name = svn_relpath_skip_ancestor(parent_repos_relpath,
info->repos_relpath);
switched_p = !name || (strcmp(name,
svn_dirent_basename(local_abspath, NULL))
!= 0);
}
if (info->status == svn_wc__db_status_incomplete || info->incomplete)
{
/* Highest precedence. */
node_status = svn_wc_status_incomplete;
}
else if (info->status == svn_wc__db_status_deleted)
{
node_status = svn_wc_status_deleted;
if (!info->have_base || info->have_more_work || info->copied)
copied = TRUE;
else if (!info->have_more_work && info->have_base)
copied = FALSE;
else
{
const char *work_del_abspath;
/* Find out details of our deletion. */
SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
&work_del_abspath, NULL,
db, local_abspath,
scratch_pool, scratch_pool));
if (work_del_abspath)
copied = TRUE; /* Working deletion */
}
}
else if (check_working_copy)
{
/* Examine whether our target is missing or obstructed. To detect
* obstructions, we have to look at the on-disk status in DIRENT. */
svn_node_kind_t expected_kind = (info->kind == svn_node_dir)
? svn_node_dir
: svn_node_file;
if (!dirent || dirent->kind != expected_kind)
{
/* A present or added node should be on disk, so it is
reported missing or obstructed. */
if (!dirent || dirent->kind == svn_node_none)
node_status = svn_wc_status_missing;
else
node_status = svn_wc_status_obstructed;
}
}
/* Does the node have props? */
if (info->status != svn_wc__db_status_deleted)
{
if (info->props_mod)
prop_status = svn_wc_status_modified;
else if (info->had_props)
prop_status = svn_wc_status_normal;
}
/* If NODE_STATUS is still normal, after the above checks, then
we should proceed to refine the status.
If it was changed, then the subdir is incomplete or missing/obstructed.
*/
if (info->kind != svn_node_dir
&& node_status == svn_wc_status_normal)
{
svn_boolean_t text_modified_p = FALSE;
/* Implement predecence rules: */
/* 1. Set the two main variables to "discovered" values first (M, C).
Together, these two stati are of lowest precedence, and C has
precedence over M. */
/* If the entry is a file, check for textual modifications */
if ((info->kind == svn_node_file
|| info->kind == svn_node_symlink)
#ifdef HAVE_SYMLINK
&& (info->special == (dirent && dirent->special))
#endif /* HAVE_SYMLINK */
)
{
/* If the on-disk dirent exactly matches the expected state
skip all operations in svn_wc__internal_text_modified_p()
to avoid an extra filestat for every file, which can be
expensive on network drives as a filestat usually can't
be cached there */
if (!info->has_checksum)
text_modified_p = TRUE; /* Local addition -> Modified */
else if (ignore_text_mods
||(dirent
&& info->recorded_size != SVN_INVALID_FILESIZE
&& info->recorded_time != 0
&& info->recorded_size == dirent->filesize
&& info->recorded_time == dirent->mtime))
text_modified_p = FALSE;
else
{
svn_error_t *err;
err = svn_wc__internal_file_modified_p(&text_modified_p,
db, local_abspath,
FALSE, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED)
return svn_error_trace(err);
/* An access denied is very common on Windows when another
application has the file open. Previously we ignored
this error in svn_wc__text_modified_internal_p, where it
should have really errored. */
svn_error_clear(err);
text_modified_p = TRUE;
}
}
}
#ifdef HAVE_SYMLINK
else if (info->special != (dirent && dirent->special))
node_status = svn_wc_status_obstructed;
#endif /* HAVE_SYMLINK */
if (text_modified_p)
text_status = svn_wc_status_modified;
}
conflicted = info->conflicted;
if (conflicted)
{
svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted;
/* ### Check if the conflict was resolved by removing the marker files.
### This should really be moved to the users of this API */
SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted,
&tree_conflicted,
db, local_abspath, scratch_pool));
if (!text_conflicted && !prop_conflicted && !tree_conflicted)
conflicted = FALSE;
}
if (node_status == svn_wc_status_normal)
{
/* 2. Possibly overwrite the text_status variable with "scheduled"
states from the entry (A, D, R). As a group, these states are
of medium precedence. They also override any C or M that may
be in the prop_status field at this point, although they do not
override a C text status.*/
if (info->status == svn_wc__db_status_added)
{
copied = info->copied;
if (!info->op_root)
{ /* Keep status normal */ }
else if (!info->have_base && !info->have_more_work)
{
/* Simple addition or copy, no replacement */
node_status = svn_wc_status_added;
}
else
{
svn_wc__db_status_t below_working;
svn_boolean_t have_base, have_work;
SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work,
&below_working,
db, local_abspath,
scratch_pool));
/* If the node is not present or deleted (read: not present
in working), then the node is not a replacement */
if (below_working != svn_wc__db_status_not_present
&& below_working != svn_wc__db_status_deleted)
{
node_status = svn_wc_status_replaced;
}
else
node_status = svn_wc_status_added;
}
/* Get moved-from info (only for potential op-roots of a move). */
if (info->moved_here && info->op_root)
{
svn_error_t *err;
err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL,
db, local_abspath,
result_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
return svn_error_trace(err);
svn_error_clear(err);
/* We are no longer moved... So most likely we are somehow
changing the db for things like resolving conflicts. */
moved_from_abspath = NULL;
}
}
}
}
if (node_status == svn_wc_status_normal)
node_status = text_status;
if (node_status == svn_wc_status_normal
&& prop_status != svn_wc_status_none)
node_status = prop_status;
/* 5. Easy out: unless we're fetching -every- node, don't bother
to allocate a struct for an uninteresting node.
This filter should match the filter in is_sendable_status() */
if (! get_all)
if (((node_status == svn_wc_status_none)
|| (node_status == svn_wc_status_normal)
|| (node_status == svn_wc_status_deleted && !info->op_root))
&& (! switched_p)
&& (! info->locked)
&& (! info->lock)
&& (! repos_lock)
&& (! info->changelist)
&& (! conflicted)
&& (! info->moved_to))
{
*status = NULL;
return SVN_NO_ERROR;
}
/* 6. Build and return a status structure. */
inner_stat = apr_pcalloc(result_pool, sizeof(*inner_stat));
stat = &inner_stat->s;
inner_stat->has_descendants = info->has_descendants;
inner_stat->op_root = info->op_root;
switch (info->kind)
{
case svn_node_dir:
stat->kind = svn_node_dir;
break;
case svn_node_file:
case svn_node_symlink:
stat->kind = svn_node_file;
break;
case svn_node_unknown:
default:
stat->kind = svn_node_unknown;
}
stat->depth = info->depth;
if (dirent)
{
stat->filesize = (dirent->kind == svn_node_file)
? dirent->filesize
: SVN_INVALID_FILESIZE;
stat->actual_kind = dirent->special ? svn_node_symlink
: dirent->kind;
}
else
{
stat->filesize = SVN_INVALID_FILESIZE;
stat->actual_kind = ignore_text_mods ? svn_node_unknown
: svn_node_none;
}
stat->node_status = node_status;
stat->text_status = text_status;
stat->prop_status = prop_status;
stat->repos_node_status = svn_wc_status_none; /* default */
stat->repos_text_status = svn_wc_status_none; /* default */
stat->repos_prop_status = svn_wc_status_none; /* default */
stat->switched = switched_p;
stat->copied = copied;
stat->repos_lock = repos_lock;
stat->revision = info->revnum;
stat->changed_rev = info->changed_rev;
if (info->changed_author)
stat->changed_author = apr_pstrdup(result_pool, info->changed_author);
stat->changed_date = info->changed_date;
stat->ood_kind = svn_node_none;
stat->ood_changed_rev = SVN_INVALID_REVNUM;
stat->ood_changed_date = 0;
stat->ood_changed_author = NULL;
SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath,
&stat->repos_root_url,
&stat->repos_uuid, info,
parent_repos_relpath,
parent_repos_root_url,
parent_repos_uuid,
db, local_abspath,
result_pool, scratch_pool));
if (info->lock)
{
svn_lock_t *lck = svn_lock_create(result_pool);
lck->path = stat->repos_relpath;
lck->token = info->lock->token;
lck->owner = info->lock->owner;
lck->comment = info->lock->comment;
lck->creation_date = info->lock->date;
stat->lock = lck;
}
else
stat->lock = NULL;
stat->locked = info->locked;
stat->conflicted = conflicted;
stat->versioned = TRUE;
if (info->changelist)
stat->changelist = apr_pstrdup(result_pool, info->changelist);
stat->moved_from_abspath = moved_from_abspath;
/* ### TODO: Handle multiple moved_to values properly */
if (info->moved_to)
stat->moved_to_abspath = apr_pstrdup(result_pool,
info->moved_to->moved_to_abspath);
stat->file_external = info->file_external;
*status = inner_stat;
return SVN_NO_ERROR;
}
/* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data
available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for
temporary allocations.
If IS_IGNORED is non-zero and this is a non-versioned entity, set
the node_status to svn_wc_status_none. Otherwise set the
node_status to svn_wc_status_unversioned.
*/
static svn_error_t *
assemble_unversioned(svn_wc__internal_status_t **status,
svn_wc__db_t *db,
const char *local_abspath,
const svn_io_dirent2_t *dirent,
svn_boolean_t tree_conflicted,
svn_boolean_t is_ignored,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc__internal_status_t *inner_status;
svn_wc_status3_t *stat;
/* return a fairly blank structure. */
inner_status = apr_pcalloc(result_pool, sizeof(*inner_status));
stat = &inner_status->s;
/*stat->versioned = FALSE;*/
stat->kind = svn_node_unknown; /* not versioned */
stat->depth = svn_depth_unknown;
if (dirent)
{
stat->actual_kind = dirent->special ? svn_node_symlink
: dirent->kind;
stat->filesize = (dirent->kind == svn_node_file)
? dirent->filesize
: SVN_INVALID_FILESIZE;
}
else
{
stat->actual_kind = svn_node_none;
stat->filesize = SVN_INVALID_FILESIZE;
}
stat->node_status = svn_wc_status_none;
stat->text_status = svn_wc_status_none;
stat->prop_status = svn_wc_status_none;
stat->repos_node_status = svn_wc_status_none;
stat->repos_text_status = svn_wc_status_none;
stat->repos_prop_status = svn_wc_status_none;
/* If this path has no entry, but IS present on disk, it's
unversioned. If this file is being explicitly ignored (due
to matching an ignore-pattern), the node_status is set to
svn_wc_status_ignored. Otherwise the node_status is set to
svn_wc_status_unversioned. */
if (dirent && dirent->kind != svn_node_none)
{
if (is_ignored)
stat->node_status = svn_wc_status_ignored;
else
stat->node_status = svn_wc_status_unversioned;
}
else if (tree_conflicted)
{
/* If this path has no entry, is NOT present on disk, and IS a
tree conflict victim, report it as conflicted. */
stat->node_status = svn_wc_status_conflicted;
}
stat->revision = SVN_INVALID_REVNUM;
stat->changed_rev = SVN_INVALID_REVNUM;
stat->ood_changed_rev = SVN_INVALID_REVNUM;
stat->ood_kind = svn_node_none;
/* For the case of an incoming delete to a locally deleted path during
an update, we get a tree conflict. */
stat->conflicted = tree_conflicted;
stat->changelist = NULL;
*status = inner_status;
return SVN_NO_ERROR;
}
/* Given an ENTRY object representing PATH, build a status structure
and pass it off to the STATUS_FUNC/STATUS_BATON. All other
arguments are the same as those passed to assemble_status(). */
static svn_error_t *
send_status_structure(const struct walk_status_baton *wb,
const char *local_abspath,
const char *parent_repos_root_url,
const char *parent_repos_relpath,
const char *parent_repos_uuid,
const struct svn_wc__db_info_t *info,
const svn_io_dirent2_t *dirent,
svn_boolean_t get_all,
svn_wc_status_func4_t status_func,
void *status_baton,
apr_pool_t *scratch_pool)
{
svn_wc__internal_status_t *statstruct;
const svn_lock_t *repos_lock = NULL;
/* Check for a repository lock. */
if (wb->repos_locks)
{
const char *repos_relpath, *repos_root_url, *repos_uuid;
SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url,
&repos_uuid,
info, parent_repos_relpath,
parent_repos_root_url,
parent_repos_uuid,
wb->db, local_abspath,
scratch_pool, scratch_pool));
if (repos_relpath)
{
/* repos_lock still uses the deprecated filesystem absolute path
format */
repos_lock = svn_hash_gets(wb->repos_locks,
svn_fspath__join("/", repos_relpath,
scratch_pool));
}
}
SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath,
parent_repos_root_url, parent_repos_relpath,
parent_repos_uuid,
info, dirent, get_all,
wb->ignore_text_mods, wb->check_working_copy,
repos_lock, scratch_pool, scratch_pool));
if (statstruct && status_func)
return svn_error_trace((*status_func)(status_baton, local_abspath,
&statstruct->s,
scratch_pool));
return SVN_NO_ERROR;
}
/* Store in *PATTERNS a list of ignores collected from svn:ignore properties
on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its
repository ancestors (as cached in the working copy), including the default
ignores passed in as IGNORES.
Upon return, *PATTERNS will contain zero or more (const char *)
patterns from the value of the SVN_PROP_IGNORE property set on
the working directory path.
IGNORES is a list of patterns to include; typically this will
be the default ignores as, for example, specified in a config file.
DB, LOCAL_ABSPATH is used to access the working copy.
Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL.
None of the arguments may be NULL.
*/
static svn_error_t *
collect_ignore_patterns(apr_array_header_t **patterns,
svn_wc__db_t *db,
const char *local_abspath,
const apr_array_header_t *ignores,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
int i;
apr_hash_t *props;
apr_array_header_t *inherited_props;
svn_error_t *err;
/* ### assert we are passed a directory? */
*patterns = apr_array_make(result_pool, 1, sizeof(const char *));
/* Copy default ignores into the local PATTERNS array. */
for (i = 0; i < ignores->nelts; i++)
{
const char *ignore = APR_ARRAY_IDX(ignores, i, const char *);
APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool,
ignore);
}
err = svn_wc__db_read_inherited_props(&inherited_props, &props,
db, local_abspath,
SVN_PROP_INHERITABLE_IGNORES,
scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
return svn_error_trace(err);
svn_error_clear(err);
return SVN_NO_ERROR;
}
if (props)
{
const svn_string_t *value;
value = svn_hash_gets(props, SVN_PROP_IGNORE);
if (value)
svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
result_pool);
value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES);
if (value)
svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE,
result_pool);
}
for (i = 0; i < inherited_props->nelts; i++)
{
svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
inherited_props, i, svn_prop_inherited_item_t *);
const svn_string_t *value;
value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
if (value)
svn_cstring_split_append(*patterns, value->data,
"\n\r", FALSE, result_pool);
}
return SVN_NO_ERROR;
}
/* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if
LOCAL_ABSPATH is the drop location for, or an intermediate directory
of the drop location for, an externals definition. Use SCRATCH_POOL
for scratchwork. */
static svn_boolean_t
is_external_path(apr_hash_t *externals,
const char *local_abspath,
apr_pool_t *scratch_pool)
{
apr_hash_index_t *hi;
/* First try: does the path exist as a key in the hash? */
if (svn_hash_gets(externals, local_abspath))
return TRUE;
/* Failing that, we need to check if any external is a child of
LOCAL_ABSPATH. */
for (hi = apr_hash_first(scratch_pool, externals);
hi;
hi = apr_hash_next(hi))
{
const char *external_abspath = apr_hash_this_key(hi);
if (svn_dirent_is_child(local_abspath, external_abspath, NULL))
return TRUE;
}
return FALSE;
}
/* Assuming that LOCAL_ABSPATH is unversioned, send a status structure
for it through STATUS_FUNC/STATUS_BATON unless this path is being
ignored. This function should never be called on a versioned entry.
LOCAL_ABSPATH is the path to the unversioned file whose status is being
requested. PATH_KIND is the node kind of NAME as determined by the
caller. PATH_SPECIAL is the special status of the path, also determined
by the caller.
PATTERNS points to a list of filename patterns which are marked as ignored.
None of these parameter may be NULL.
If NO_IGNORE is TRUE, the item will be added regardless of
whether it is ignored; otherwise we will only add the item if it
does not match any of the patterns in PATTERN or INHERITED_IGNORES.
Allocate everything in POOL.
*/
static svn_error_t *
send_unversioned_item(const struct walk_status_baton *wb,
const char *local_abspath,
const svn_io_dirent2_t *dirent,
svn_boolean_t tree_conflicted,
const apr_array_header_t *patterns,
svn_boolean_t no_ignore,
svn_wc_status_func4_t status_func,
void *status_baton,
apr_pool_t *scratch_pool)
{
svn_boolean_t is_ignored;
svn_boolean_t is_external;
svn_wc__internal_status_t *status;
const char *base_name = svn_dirent_basename(local_abspath, NULL);
is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool);
SVN_ERR(assemble_unversioned(&status,
wb->db, local_abspath,
dirent, tree_conflicted,
is_ignored,
scratch_pool, scratch_pool));
is_external = is_external_path(wb->externals, local_abspath, scratch_pool);
if (is_external)
status->s.node_status = svn_wc_status_external;
/* We can have a tree conflict on an unversioned path, i.e. an incoming
* delete on a locally deleted path during an update. Don't ever ignore
* those! */
if (status->s.conflicted)
is_ignored = FALSE;
/* If we aren't ignoring it, or if it's an externals path, pass this
entry to the status func. */
if (no_ignore
|| !is_ignored
|| is_external)
return svn_error_trace((*status_func)(status_baton, local_abspath,
&status->s, scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
get_dir_status(const struct walk_status_baton *wb,
const char *local_abspath,
svn_boolean_t skip_this_dir,
const char *parent_repos_root_url,
const char *parent_repos_relpath,
const char *parent_repos_uuid,
const struct svn_wc__db_info_t *dir_info,
const svn_io_dirent2_t *dirent,
const apr_array_header_t *ignore_patterns,
svn_depth_t depth,
svn_boolean_t get_all,
svn_boolean_t no_ignore,
svn_wc_status_func4_t status_func,
void *status_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool);
/* Send out a status structure according to the information gathered on one
* child node. (Basically this function is the guts of the loop in
* get_dir_status() and of get_child_status().)
*
* Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the
* dirname of LOCAL_ABSPATH.
*
* INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must
* be an unversioned file or dir, or a versioned file. For versioned
* directories use get_dir_status() instead.
*
* INFO may be NULL for an unversioned node. If such node has a tree conflict,
* UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL,
* UNVERSIONED_TREE_CONFLICTED is ignored.
*
* DIRENT should reflect LOCAL_ABSPATH's dirent information.
*
* DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's
* URL treated with svn_uri_dirname(). ### TODO verify this (externals)
*
* If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this
* call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t*
* containing all ignore patterns, as returned by collect_ignore_patterns() on
* PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed
* non-NULL, it is assumed it already holds those results.
* This speeds up repeated calls with the same PARENT_ABSPATH.
*
* *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other
* allocations are made in SCRATCH_POOL.
*
* The remaining parameters correspond to get_dir_status(). */
static svn_error_t *
one_child_status(const struct walk_status_baton *wb,
const char *local_abspath,
const char *parent_abspath,
const struct svn_wc__db_info_t *info,
const svn_io_dirent2_t *dirent,
const char *dir_repos_root_url,
const char *dir_repos_relpath,
const char *dir_repos_uuid,
svn_boolean_t unversioned_tree_conflicted,
apr_array_header_t **collected_ignore_patterns,
const apr_array_header_t *ignore_patterns,
svn_depth_t depth,
svn_boolean_t get_all,
svn_boolean_t no_ignore,
svn_wc_status_func4_t status_func,
void *status_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_boolean_t conflicted = info ? info->conflicted
: unversioned_tree_conflicted;
if (info
&& info->status != svn_wc__db_status_not_present
&& info->status != svn_wc__db_status_excluded
&& info->status != svn_wc__db_status_server_excluded
&& !(info->kind == svn_node_unknown
&& info->status == svn_wc__db_status_normal))
{
if (depth == svn_depth_files
&& info->kind == svn_node_dir)
{
return SVN_NO_ERROR;
}
SVN_ERR(send_status_structure(wb, local_abspath,
dir_repos_root_url,
dir_repos_relpath,
dir_repos_uuid,
info, dirent, get_all,
status_func, status_baton,
scratch_pool));
/* Descend in subdirectories. */
if (depth == svn_depth_infinity
&& info->has_descendants /* is dir, or was dir and tc descendants */)
{
SVN_ERR(get_dir_status(wb, local_abspath, TRUE,
dir_repos_root_url, dir_repos_relpath,
dir_repos_uuid, info,
dirent, ignore_patterns,
svn_depth_infinity, get_all,
no_ignore,
status_func, status_baton,
cancel_func, cancel_baton,
scratch_pool));
}
return SVN_NO_ERROR;
}
/* If conflicted, fall right through to unversioned.
* With depth_files, show all conflicts, even if their report is only
* about directories. A tree conflict may actually report two different
* kinds, so it's not so easy to define what depth=files means. We could go
* look up the kinds in the conflict ... just show all. */
if (! conflicted)
{
/* We have a node, but its not visible in the WC. It can be a marker
node (not present, (server) excluded), *or* it can be the explictly
passed target of the status walk operation that doesn't exist.
We only report the node when the caller explicitly as
*/
if (dirent == NULL && strcmp(wb->target_abspath, local_abspath) != 0)
return SVN_NO_ERROR; /* Marker node */
if (depth == svn_depth_files && dirent && dirent->kind == svn_node_dir)
return SVN_NO_ERROR;
if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
scratch_pool))
return SVN_NO_ERROR;
}
/* The node exists on disk but there is no versioned information about it,
* or it doesn't exist but is a tree conflicted path or should be
* reported not-present. */
/* Why pass ignore patterns on a tree conflicted node, even if it should
* always show up in clients' status reports anyway? Because the calling
* client decides whether to ignore, and thus this flag needs to be
* determined. For example, in 'svn status', plain unversioned nodes show
* as '? C', where ignored ones show as 'I C'. */
if (ignore_patterns && ! *collected_ignore_patterns)
SVN_ERR(collect_ignore_patterns(collected_ignore_patterns,
wb->db, parent_abspath, ignore_patterns,
result_pool, scratch_pool));
SVN_ERR(send_unversioned_item(wb,
local_abspath,
dirent,
conflicted,
*collected_ignore_patterns,
no_ignore,
status_func, status_baton,
scratch_pool));
return SVN_NO_ERROR;
}
/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and
for all its child nodes (according to DEPTH) through STATUS_FUNC /
STATUS_BATON.
If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported.
All subdirs reached by recursion will be reported regardless of this
parameter's value.
PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's
URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid
retrieving them again. Otherwise they must be NULL.
DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving
it again. Otherwise it must be NULL.
DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported,
so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL.
Other arguments are the same as those passed to
svn_wc_get_status_editor5(). */
static svn_error_t *
get_dir_status(const struct walk_status_baton *wb,
const char *local_abspath,
svn_boolean_t skip_this_dir,
const char *parent_repos_root_url,
const char *parent_repos_relpath,
const char *parent_repos_uuid,
const struct svn_wc__db_info_t *dir_info,
const svn_io_dirent2_t *dirent,
const apr_array_header_t *ignore_patterns,
svn_depth_t depth,
svn_boolean_t get_all,
svn_boolean_t no_ignore,
svn_wc_status_func4_t status_func,
void *status_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
const char *dir_repos_root_url;
const char *dir_repos_relpath;
const char *dir_repos_uuid;
apr_hash_t *dirents, *nodes, *conflicts, *all_children;
apr_array_header_t *sorted_children;
apr_array_header_t *collected_ignore_patterns = NULL;
apr_pool_t *iterpool;
svn_error_t *err;
int i;
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
if (depth == svn_depth_unknown)
depth = svn_depth_infinity;
iterpool = svn_pool_create(scratch_pool);
if (wb->check_working_copy)
{
err = svn_io_get_dirents3(&dirents, local_abspath,
wb->ignore_text_mods /* only_check_type*/,
scratch_pool, iterpool);
if (err
&& (APR_STATUS_IS_ENOENT(err->apr_err)
|| SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
{
svn_error_clear(err);
dirents = apr_hash_make(scratch_pool);
}
else
SVN_ERR(err);
}
else
dirents = apr_hash_make(scratch_pool);
if (!dir_info)
SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath,
!wb->check_working_copy,
scratch_pool, iterpool));
SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
&dir_repos_uuid, dir_info,
parent_repos_relpath,
parent_repos_root_url, parent_repos_uuid,
wb->db, local_abspath,
scratch_pool, iterpool));
/* Create a hash containing all children. The source hashes
don't all map the same types, but only the keys of the result
hash are subsequently used. */
SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts,
wb->db, local_abspath,
!wb->check_working_copy,
scratch_pool, iterpool));
all_children = apr_hash_overlay(scratch_pool, nodes, dirents);
if (apr_hash_count(conflicts) > 0)
all_children = apr_hash_overlay(scratch_pool, conflicts, all_children);
/* Handle "this-dir" first. */
if (! skip_this_dir)
{
/* This code is not conditional on HAVE_SYMLINK as some systems that do
not allow creating symlinks (!HAVE_SYMLINK) can still encounter
symlinks (or in case of Windows also 'Junctions') created by other
methods.
Without this block a working copy in the root of a junction is
reported as an obstruction, because the junction itself is reported as
special.
Systems that have no symlink support at all, would always see
dirent->special as FALSE, so even there enabling this code shouldn't
produce problems.
*/
if (dirent->special)
{
svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool);
/* We're being pointed to "this-dir" via a symlink.
* Get the real node kind and pretend the path is not a symlink.
* This prevents send_status_structure() from treating this-dir
* as a directory obstructed by a file. */
SVN_ERR(svn_io_check_resolved_path(local_abspath,
&this_dirent->kind, iterpool));
this_dirent->special = FALSE;
SVN_ERR(send_status_structure(wb, local_abspath,
parent_repos_root_url,
parent_repos_relpath,
parent_repos_uuid,
dir_info, this_dirent, get_all,
status_func, status_baton,
iterpool));
}
else
SVN_ERR(send_status_structure(wb, local_abspath,
parent_repos_root_url,
parent_repos_relpath,
parent_repos_uuid,
dir_info, dirent, get_all,
status_func, status_baton,
iterpool));
}
/* If the requested depth is empty, we only need status on this-dir. */
if (depth == svn_depth_empty)
return SVN_NO_ERROR;
/* Walk all the children of this directory. */
sorted_children = svn_sort__hash(all_children,
svn_sort_compare_items_lexically,
scratch_pool);
for (i = 0; i < sorted_children->nelts; i++)
{
const void *key;
apr_ssize_t klen;
svn_sort__item_t item;
const char *child_abspath;
svn_io_dirent2_t *child_dirent;
const struct svn_wc__db_info_t *child_info;
svn_pool_clear(iterpool);
item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t);
key = item.key;
klen = item.klen;
child_abspath = svn_dirent_join(local_abspath, key, iterpool);
child_dirent = apr_hash_get(dirents, key, klen);
child_info = apr_hash_get(nodes, key, klen);
SVN_ERR(one_child_status(wb,
child_abspath,
local_abspath,
child_info,
child_dirent,
dir_repos_root_url,
dir_repos_relpath,
dir_repos_uuid,
apr_hash_get(conflicts, key, klen) != NULL,
&collected_ignore_patterns,
ignore_patterns,
depth,
get_all,
no_ignore,
status_func,
status_baton,
cancel_func,
cancel_baton,
scratch_pool,
iterpool));
}
/* Destroy our subpools. */
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
/* Send an svn_wc_status3_t * structure for the versioned file, or for the
* unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an
* explicit target). Does not recurse.
*
* INFO should reflect LOCAL_ABSPATH's information, but should be NULL for
* unversioned nodes. An unversioned and tree-conflicted node however should
* pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE).
*
* DIRENT should reflect LOCAL_ABSPATH.
*
* All allocations made in SCRATCH_POOL.
*
* The remaining parameters correspond to get_dir_status(). */
static svn_error_t *
get_child_status(const struct walk_status_baton *wb,
const char *local_abspath,
const struct svn_wc__db_info_t *info,
const svn_io_dirent2_t *dirent,
const apr_array_header_t *ignore_patterns,
svn_boolean_t get_all,
svn_wc_status_func4_t status_func,
void *status_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
const char *dir_repos_root_url;
const char *dir_repos_relpath;
const char *dir_repos_uuid;
const struct svn_wc__db_info_t *dir_info;
apr_array_header_t *collected_ignore_patterns = NULL;
const char *parent_abspath = svn_dirent_dirname(local_abspath,
scratch_pool);
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
if (dirent->kind == svn_node_none)
dirent = NULL;
SVN_ERR(svn_wc__db_read_single_info(&dir_info,
wb->db, parent_abspath,
!wb->check_working_copy,
scratch_pool, scratch_pool));
SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url,
&dir_repos_uuid, dir_info,
NULL, NULL, NULL,
wb->db, parent_abspath,
scratch_pool, scratch_pool));
/* An unversioned node with a tree conflict will see an INFO != NULL here,
* in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no
* effect and INFO->CONFLICTED counts.
* ### Maybe svn_wc__db_read_children_info() and read_info() should be more
* ### alike? */
SVN_ERR(one_child_status(wb,
local_abspath,
parent_abspath,
info,
dirent,
dir_repos_root_url,
dir_repos_relpath,
dir_repos_uuid,
FALSE, /* unversioned_tree_conflicted */
&collected_ignore_patterns,
ignore_patterns,
svn_depth_empty,
get_all,
TRUE, /* no_ignore. This is an explicit target. */
status_func,
status_baton,
cancel_func,
cancel_baton,
scratch_pool,
scratch_pool));
return SVN_NO_ERROR;
}
/*** Helpers ***/
/* A faux status callback function for stashing STATUS item in an hash
(which is the BATON), keyed on PATH. This implements the
svn_wc_status_func4_t interface. */
static svn_error_t *
hash_stash(void *baton,
const char *path,
const svn_wc_status3_t *status,
apr_pool_t *scratch_pool)
{
apr_hash_t *stat_hash = baton;
apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash);
void *new_status = svn_wc_dup_status3(status, hash_pool);
const svn_wc__internal_status_t *old_status = (const void*)status;
/* Copy the internal/private data. */
svn_wc__internal_status_t *is = new_status;
is->has_descendants = old_status->has_descendants;
is->op_root = old_status->op_root;
assert(! svn_hash_gets(stat_hash, path));
svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), new_status);
return SVN_NO_ERROR;
}
/* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether
baton is a struct *dir_baton or struct *file_baton. If the value doesn't
yet exist, and the REPOS_NODE_STATUS indicates that this is an addition,
create a new status struct using the hash's pool.
If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out
of date (ood) information we want to set in BATON. This is necessary
because this function tweaks the status of out-of-date directories
(BATON == THIS_DIR_BATON) and out-of-date directories' parents
(BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON
contains the ood info we want to bubble up to ancestor directories so these
accurately reflect the fact they have an ood descendant.
Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the
status structure's "network" fields.
Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it
is ignored:
If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is
optionally the revision path was deleted, in all other cases it must
be set to SVN_INVALID_REVNUM. If DELETED_REV is not
SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted,
then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON.
If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is
svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's
ood_last_cmt_rev value - see comment below.
If a new struct was added, set the repos_lock to REPOS_LOCK. */
static svn_error_t *
tweak_statushash(void *baton,
void *this_dir_baton,
svn_boolean_t is_dir_baton,
svn_wc__db_t *db,
svn_boolean_t check_working_copy,
const char *local_abspath,
enum svn_wc_status_kind repos_node_status,
enum svn_wc_status_kind repos_text_status,
enum svn_wc_status_kind repos_prop_status,
svn_revnum_t deleted_rev,
const svn_lock_t *repos_lock,
apr_pool_t *scratch_pool)
{
svn_wc_status3_t *statstruct;
apr_pool_t *pool;
apr_hash_t *statushash;
if (is_dir_baton)
statushash = ((struct dir_baton *) baton)->statii;
else
statushash = ((struct file_baton *) baton)->dir_baton->statii;
pool = apr_hash_pool_get(statushash);
/* Is PATH already a hash-key? */
statstruct = svn_hash_gets(statushash, local_abspath);
/* If not, make it so. */
if (! statstruct)
{
svn_wc__internal_status_t *i_stat;
/* If this item isn't being added, then we're most likely
dealing with a non-recursive (or at least partially
non-recursive) working copy. Due to bugs in how the client
reports the state of non-recursive working copies, the
repository can send back responses about paths that don't
even exist locally. Our best course here is just to ignore
those responses. After all, if the client had reported
correctly in the first, that path would either be mentioned
as an 'add' or not mentioned at all, depending on how we
eventually fix the bugs in non-recursivity. See issue
#2122 for details. */
if (repos_node_status != svn_wc_status_added)
return SVN_NO_ERROR;
/* Use the public API to get a statstruct, and put it into the hash. */
SVN_ERR(internal_status(&i_stat, db, local_abspath,
check_working_copy, pool, scratch_pool));
statstruct = &i_stat->s;
statstruct->repos_lock = repos_lock;
svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct);
}
/* Merge a repos "delete" + "add" into a single "replace". */
if ((repos_node_status == svn_wc_status_added)
&& (statstruct->repos_node_status == svn_wc_status_deleted))
repos_node_status = svn_wc_status_replaced;
/* Tweak the structure's repos fields. */
if (repos_node_status)
statstruct->repos_node_status = repos_node_status;
if (repos_text_status)
statstruct->repos_text_status = repos_text_status;
if (repos_prop_status)
statstruct->repos_prop_status = repos_prop_status;
/* Copy out-of-date info. */
if (is_dir_baton)
{
struct dir_baton *b = this_dir_baton;
if (!statstruct->repos_relpath && b->repos_relpath)
{
if (statstruct->repos_node_status == svn_wc_status_deleted)
{
/* When deleting PATH, BATON is for PATH's parent,
so we must construct PATH's real statstruct->url. */
statstruct->repos_relpath =
svn_relpath_join(b->repos_relpath,
svn_dirent_basename(local_abspath,
NULL),
pool);
}
else
statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
statstruct->repos_root_url =
b->edit_baton->anchor_status->s.repos_root_url;
statstruct->repos_uuid =
b->edit_baton->anchor_status->s.repos_uuid;
}
/* The last committed date, and author for deleted items
isn't available. */
if (statstruct->repos_node_status == svn_wc_status_deleted)
{
statstruct->ood_kind = statstruct->kind;
/* Pre 1.5 servers don't provide the revision a path was deleted.
So we punt and use the last committed revision of the path's
parent, which has some chance of being correct. At worse it
is a higher revision than the path was deleted, but this is
better than nothing... */
if (deleted_rev == SVN_INVALID_REVNUM)
statstruct->ood_changed_rev =
((struct dir_baton *) baton)->ood_changed_rev;
else
statstruct->ood_changed_rev = deleted_rev;
}
else
{
statstruct->ood_kind = b->ood_kind;
statstruct->ood_changed_rev = b->ood_changed_rev;
statstruct->ood_changed_date = b->ood_changed_date;
if (b->ood_changed_author)
statstruct->ood_changed_author =
apr_pstrdup(pool, b->ood_changed_author);
}
}
else
{
struct file_baton *b = baton;
statstruct->ood_changed_rev = b->ood_changed_rev;
statstruct->ood_changed_date = b->ood_changed_date;
if (!statstruct->repos_relpath && b->repos_relpath)
{
statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath);
statstruct->repos_root_url =
b->edit_baton->anchor_status->s.repos_root_url;
statstruct->repos_uuid =
b->edit_baton->anchor_status->s.repos_uuid;
}
statstruct->ood_kind = b->ood_kind;
if (b->ood_changed_author)
statstruct->ood_changed_author =
apr_pstrdup(pool, b->ood_changed_author);
}
return SVN_NO_ERROR;
}
/* Returns the URL for DB */
static const char *
find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool)
{
/* If we have no name, we're the root, return the anchor URL. */
if (! db->name)
return db->edit_baton->anchor_status->s.repos_relpath;
else
{
const char *repos_relpath;
struct dir_baton *pb = db->parent_baton;
const svn_wc_status3_t *status = svn_hash_gets(pb->statii,
db->local_abspath);
/* Note that status->repos_relpath could be NULL in the case of a missing
* directory, which means we need to recurse up another level to get
* a useful relpath. */
if (status && status->repos_relpath)
return status->repos_relpath;
repos_relpath = find_dir_repos_relpath(pb, pool);
return svn_relpath_join(repos_relpath, db->name, pool);
}
}
/* Create a new dir_baton for subdir PATH. */
static svn_error_t *
make_dir_baton(void **dir_baton,
const char *path,
struct edit_baton *edit_baton,
struct dir_baton *parent_baton,
apr_pool_t *result_pool)
{
struct dir_baton *pb = parent_baton;
struct edit_baton *eb = edit_baton;
struct dir_baton *d;
const char *local_abspath;
const svn_wc__internal_status_t *status_in_parent;
apr_pool_t *dir_pool;
if (parent_baton)
dir_pool = svn_pool_create(parent_baton->pool);
else
dir_pool = svn_pool_create(result_pool);
d = apr_pcalloc(dir_pool, sizeof(*d));
SVN_ERR_ASSERT(path || (! pb));
/* Construct the absolute path of this directory. */
if (pb)
local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool);
else
local_abspath = eb->anchor_abspath;
/* Finish populating the baton members. */
d->pool = dir_pool;
d->local_abspath = local_abspath;
d->name = path ? svn_dirent_basename(path, dir_pool) : NULL;
d->edit_baton = edit_baton;
d->parent_baton = parent_baton;
d->statii = apr_hash_make(dir_pool);
d->ood_changed_rev = SVN_INVALID_REVNUM;
d->ood_changed_date = 0;
d->repos_relpath = find_dir_repos_relpath(d, dir_pool);
d->ood_kind = svn_node_dir;
d->ood_changed_author = NULL;
if (pb)
{
if (pb->excluded)
d->excluded = TRUE;
else if (pb->depth == svn_depth_immediates)
d->depth = svn_depth_empty;
else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty)
d->excluded = TRUE;
else if (pb->depth == svn_depth_unknown)
/* This is only tentative, it can be overridden from d's entry
later. */
d->depth = svn_depth_unknown;
else
d->depth = svn_depth_infinity;
}
else
{
d->depth = eb->default_depth;
}
/* Get the status for this path's children. Of course, we only want
to do this if the path is versioned as a directory. */
if (pb)
status_in_parent = svn_hash_gets(pb->statii, d->local_abspath);
else
status_in_parent = eb->anchor_status;
if (status_in_parent
&& (status_in_parent->has_descendants)
&& (! d->excluded)
&& (d->depth == svn_depth_unknown
|| d->depth == svn_depth_infinity
|| d->depth == svn_depth_files
|| d->depth == svn_depth_immediates)
)
{
const svn_wc_status3_t *this_dir_status;
const apr_array_header_t *ignores = eb->ignores;
SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE,
status_in_parent->s.repos_root_url,
NULL /*parent_repos_relpath*/,
status_in_parent->s.repos_uuid,
NULL,
NULL /* dirent */, ignores,
d->depth == svn_depth_files
? svn_depth_files
: svn_depth_immediates,
TRUE, TRUE,
hash_stash, d->statii,
eb->cancel_func, eb->cancel_baton,
dir_pool));
/* If we found a depth here, it should govern. */
this_dir_status = svn_hash_gets(d->statii, d->local_abspath);
if (this_dir_status && this_dir_status->versioned
&& (d->depth == svn_depth_unknown
|| d->depth > status_in_parent->s.depth))
{
d->depth = this_dir_status->depth;
}
}
*dir_baton = d;
return SVN_NO_ERROR;
}
/* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool.
NAME is just one component, not a path. */
static struct file_baton *
make_file_baton(struct dir_baton *parent_dir_baton,
const char *path,
apr_pool_t *pool)
{
struct dir_baton *pb = parent_dir_baton;
struct edit_baton *eb = pb->edit_baton;
struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
/* Finish populating the baton members. */
f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
f->name = svn_dirent_basename(f->local_abspath, NULL);
f->pool = pool;
f->dir_baton = pb;
f->edit_baton = eb;
f->ood_changed_rev = SVN_INVALID_REVNUM;
f->ood_changed_date = 0;
f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool),
f->name, pool);
f->ood_kind = svn_node_file;
f->ood_changed_author = NULL;
return f;
}
/**
* Return a boolean answer to the question "Is @a status something that
* should be reported?". @a no_ignore and @a get_all are the same as
* svn_wc_get_status_editor4().
*
* This implementation should match the filter in assemble_status()
*/
static svn_boolean_t
is_sendable_status(const svn_wc__internal_status_t *i_status,
svn_boolean_t no_ignore,
svn_boolean_t get_all)
{
const svn_wc_status3_t *status = &i_status->s;
/* If the repository status was touched at all, it's interesting. */
if (status->repos_node_status != svn_wc_status_none)
return TRUE;
/* If there is a lock in the repository, send it. */
if (status->repos_lock)
return TRUE;
if (status->conflicted)
return TRUE;
/* If the item is ignored, and we don't want ignores, skip it. */
if ((status->node_status == svn_wc_status_ignored) && (! no_ignore))
return FALSE;
/* If we want everything, we obviously want this single-item subset
of everything. */
if (get_all)
return TRUE;
/* If the item is unversioned, display it. */
if (status->node_status == svn_wc_status_unversioned)
return TRUE;
/* If the text, property or tree state is interesting, send it. */
if ((status->node_status != svn_wc_status_none)
&& (status->node_status != svn_wc_status_normal)
&& !(status->node_status == svn_wc_status_deleted
&& !i_status->op_root))
return TRUE;
/* If it's switched, send it. */
if (status->switched)
return TRUE;
/* If there is a lock token, send it. */
if (status->versioned && status->lock)
return TRUE;
/* If the entry is associated with a changelist, send it. */
if (status->changelist)
return TRUE;
if (status->moved_to_abspath)
return TRUE;
/* Otherwise, don't send it. */
return FALSE;
}
/* Baton for mark_status. */
struct status_baton
{
svn_wc_status_func4_t real_status_func; /* real status function */
void *real_status_baton; /* real status baton */
};
/* A status callback function which wraps the *real* status
function/baton. It simply sets the "repos_node_status" field of the
STATUS to svn_wc_status_deleted and passes it off to the real
status func/baton. Implements svn_wc_status_func4_t */
static svn_error_t *
mark_deleted(void *baton,
const char *local_abspath,
const svn_wc_status3_t *status,
apr_pool_t *scratch_pool)
{
struct status_baton *sb = baton;
svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool);
new_status->repos_node_status = svn_wc_status_deleted;
return sb->real_status_func(sb->real_status_baton, local_abspath,
new_status, scratch_pool);
}
/* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH
and DIR_ENTRY are the on-disk path and entry, respectively, for the
directory itself. Descend into subdirectories according to DEPTH.
Also, if DIR_WAS_DELETED is set, each status that is reported
through this function will have its repos_text_status field showing
a deletion. Use POOL for all allocations. */
static svn_error_t *
handle_statii(struct edit_baton *eb,
const char *dir_repos_root_url,
const char *dir_repos_relpath,
const char *dir_repos_uuid,
apr_hash_t *statii,
svn_boolean_t dir_was_deleted,
svn_depth_t depth,
apr_pool_t *pool)
{
const apr_array_header_t *ignores = eb->ignores;
apr_hash_index_t *hi;
apr_pool_t *iterpool = svn_pool_create(pool);
svn_wc_status_func4_t status_func = eb->status_func;
void *status_baton = eb->status_baton;
struct status_baton sb;
if (dir_was_deleted)
{
sb.real_status_func = eb->status_func;
sb.real_status_baton = eb->status_baton;
status_func = mark_deleted;
status_baton = &sb;
}
/* Loop over all the statii still in our hash, handling each one. */
for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi))
{
const char *local_abspath = apr_hash_this_key(hi);
svn_wc__internal_status_t *status = apr_hash_this_val(hi);
/* Clear the subpool. */
svn_pool_clear(iterpool);
/* Now, handle the status. We don't recurse for svn_depth_immediates
because we already have the subdirectories' statii. */
if (status->has_descendants
&& (depth == svn_depth_unknown
|| depth == svn_depth_infinity))
{
SVN_ERR(get_dir_status(&eb->wb,
local_abspath, TRUE,
dir_repos_root_url, dir_repos_relpath,
dir_repos_uuid,
NULL,
NULL /* dirent */,
ignores, depth, eb->get_all, eb->no_ignore,
status_func, status_baton,
eb->cancel_func, eb->cancel_baton,
iterpool));
}
if (dir_was_deleted)
status->s.repos_node_status = svn_wc_status_deleted;
if (is_sendable_status(status, eb->no_ignore, eb->get_all))
SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, &status->s,
iterpool));
}
/* Destroy the subpool. */
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
/*----------------------------------------------------------------------*/
/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/
/* An svn_delta_editor_t function. */
static svn_error_t *
set_target_revision(void *edit_baton,
svn_revnum_t target_revision,
apr_pool_t *pool)
{
struct edit_baton *eb = edit_baton;
*(eb->target_revision) = target_revision;
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
open_root(void *edit_baton,
svn_revnum_t base_revision,
apr_pool_t *pool,
void **dir_baton)
{
struct edit_baton *eb = edit_baton;
eb->root_opened = TRUE;
return make_dir_baton(dir_baton, NULL, eb, NULL, pool);
}
/* An svn_delta_editor_t function. */
static svn_error_t *
delete_entry(const char *path,
svn_revnum_t revision,
void *parent_baton,
apr_pool_t *pool)
{
struct dir_baton *db = parent_baton;
struct edit_baton *eb = db->edit_baton;
const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
/* Note: when something is deleted, it's okay to tweak the
statushash immediately. No need to wait until close_file or
close_dir, because there's no risk of having to honor the 'added'
flag. We already know this item exists in the working copy. */
SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, eb->wb.check_working_copy,
local_abspath,
svn_wc_status_deleted, 0, 0, revision, NULL, pool));
/* Mark the parent dir -- it lost an entry (unless that parent dir
is the root node and we're not supposed to report on the root
node). */
if (db->parent_baton && (! *eb->target_basename))
SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,
eb->db, eb->wb.check_working_copy,
db->local_abspath,
svn_wc_status_modified, svn_wc_status_modified,
0, SVN_INVALID_REVNUM, NULL, pool));
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
add_directory(const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
apr_pool_t *pool,
void **child_baton)
{
struct dir_baton *pb = parent_baton;
struct edit_baton *eb = pb->edit_baton;
struct dir_baton *new_db;
SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool));
/* Make this dir as added. */
new_db = *child_baton;
new_db->added = TRUE;
/* Mark the parent as changed; it gained an entry. */
pb->text_changed = TRUE;
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
open_directory(const char *path,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *pool,
void **child_baton)
{
struct dir_baton *pb = parent_baton;
return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool);
}
/* An svn_delta_editor_t function. */
static svn_error_t *
change_dir_prop(void *dir_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
struct dir_baton *db = dir_baton;
if (svn_wc_is_normal_prop(name))
db->prop_changed = TRUE;
/* Note any changes to the repository. */
if (value != NULL)
{
if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
db->ood_changed_rev = SVN_STR_TO_REV(value->data);
else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
db->ood_changed_author = apr_pstrdup(db->pool, value->data);
else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
{
apr_time_t tm;
SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool));
db->ood_changed_date = tm;
}
}
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
close_directory(void *dir_baton,
apr_pool_t *pool)
{
struct dir_baton *db = dir_baton;
struct dir_baton *pb = db->parent_baton;
struct edit_baton *eb = db->edit_baton;
apr_pool_t *scratch_pool = db->pool;
/* If nothing has changed and directory has no out of
date descendants, return. */
if (db->added || db->prop_changed || db->text_changed
|| db->ood_changed_rev != SVN_INVALID_REVNUM)
{
enum svn_wc_status_kind repos_node_status;
enum svn_wc_status_kind repos_text_status;
enum svn_wc_status_kind repos_prop_status;
/* If this is a new directory, add it to the statushash. */
if (db->added)
{
repos_node_status = svn_wc_status_added;
repos_text_status = svn_wc_status_none;
repos_prop_status = db->prop_changed ? svn_wc_status_added
: svn_wc_status_none;
}
else
{
repos_node_status = (db->text_changed || db->prop_changed)
? svn_wc_status_modified
: svn_wc_status_none;
repos_text_status = db->text_changed ? svn_wc_status_modified
: svn_wc_status_none;
repos_prop_status = db->prop_changed ? svn_wc_status_modified
: svn_wc_status_none;
}
/* Maybe add this directory to its parent's status hash. Note
that tweak_statushash won't do anything if repos_text_status
is not svn_wc_status_added. */
if (pb)
{
/* ### When we add directory locking, we need to find a
### directory lock here. */
SVN_ERR(tweak_statushash(pb, db, TRUE,
eb->db, eb->wb.check_working_copy,
db->local_abspath,
repos_node_status, repos_text_status,
repos_prop_status, SVN_INVALID_REVNUM, NULL,
scratch_pool));
}
else
{
/* We're editing the root dir of the WC. As its repos
status info isn't otherwise set, set it directly to
trigger invocation of the status callback below. */
eb->anchor_status->s.repos_node_status = repos_node_status;
eb->anchor_status->s.repos_prop_status = repos_prop_status;
eb->anchor_status->s.repos_text_status = repos_text_status;
/* If the root dir is out of date set the ood info directly too. */
if (db->ood_changed_rev != eb->anchor_status->s.revision)
{
eb->anchor_status->s.ood_changed_rev = db->ood_changed_rev;
eb->anchor_status->s.ood_changed_date = db->ood_changed_date;
eb->anchor_status->s.ood_kind = db->ood_kind;
eb->anchor_status->s.ood_changed_author =
apr_pstrdup(pool, db->ood_changed_author);
}
}
}
/* Handle this directory's statuses, and then note in the parent
that this has been done. */
if (pb && ! db->excluded)
{
svn_boolean_t was_deleted = FALSE;
svn_wc__internal_status_t *dir_status;
/* See if the directory was deleted or replaced. */
dir_status = svn_hash_gets(pb->statii, db->local_abspath);
if (dir_status &&
((dir_status->s.repos_node_status == svn_wc_status_deleted)
|| (dir_status->s.repos_node_status == svn_wc_status_replaced)))
was_deleted = TRUE;
/* Now do the status reporting. */
SVN_ERR(handle_statii(eb,
dir_status ? dir_status->s.repos_root_url : NULL,
dir_status ? dir_status->s.repos_relpath : NULL,
dir_status ? dir_status->s.repos_uuid : NULL,
db->statii, was_deleted, db->depth, scratch_pool));
if (dir_status && is_sendable_status(dir_status, eb->no_ignore,
eb->get_all))
SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
&dir_status->s, scratch_pool));
svn_hash_sets(pb->statii, db->local_abspath, NULL);
}
else if (! pb)
{
/* If this is the top-most directory, and the operation had a
target, we should only report the target. */
if (*eb->target_basename)
{
const svn_wc__internal_status_t *tgt_status;
tgt_status = svn_hash_gets(db->statii, eb->target_abspath);
if (tgt_status)
{
if (tgt_status->has_descendants)
{
SVN_ERR(get_dir_status(&eb->wb,
eb->target_abspath, TRUE,
NULL, NULL, NULL, NULL,
NULL /* dirent */,
eb->ignores,
eb->default_depth,
eb->get_all, eb->no_ignore,
eb->status_func, eb->status_baton,
eb->cancel_func, eb->cancel_baton,
scratch_pool));
}
if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all))
SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath,
&tgt_status->s, scratch_pool));
}
}
else
{
/* Otherwise, we report on all our children and ourself.
Note that our directory couldn't have been deleted,
because it is the root of the edit drive. */
SVN_ERR(handle_statii(eb,
eb->anchor_status->s.repos_root_url,
eb->anchor_status->s.repos_relpath,
eb->anchor_status->s.repos_uuid,
db->statii, FALSE, eb->default_depth,
scratch_pool));
if (is_sendable_status(eb->anchor_status, eb->no_ignore,
eb->get_all))
SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath,
&eb->anchor_status->s, scratch_pool));
eb->anchor_status = NULL;
}
}
svn_pool_clear(scratch_pool); /* Clear baton and its pool */
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
add_file(const char *path,
void *parent_baton,
const char *copyfrom_path,
svn_revnum_t copyfrom_revision,
apr_pool_t *pool,
void **file_baton)
{
struct dir_baton *pb = parent_baton;
struct file_baton *new_fb = make_file_baton(pb, path, pool);
/* Mark parent dir as changed */
pb->text_changed = TRUE;
/* Make this file as added. */
new_fb->added = TRUE;
*file_baton = new_fb;
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
open_file(const char *path,
void *parent_baton,
svn_revnum_t base_revision,
apr_pool_t *pool,
void **file_baton)
{
struct dir_baton *pb = parent_baton;
struct file_baton *new_fb = make_file_baton(pb, path, pool);
*file_baton = new_fb;
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
apply_textdelta(void *file_baton,
const char *base_checksum,
apr_pool_t *pool,
svn_txdelta_window_handler_t *handler,
void **handler_baton)
{
struct file_baton *fb = file_baton;
/* Mark file as having textual mods. */
fb->text_changed = TRUE;
/* Send back a NULL window handler -- we don't need the actual diffs. */
*handler_baton = NULL;
*handler = svn_delta_noop_window_handler;
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
change_file_prop(void *file_baton,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
struct file_baton *fb = file_baton;
if (svn_wc_is_normal_prop(name))
fb->prop_changed = TRUE;
/* Note any changes to the repository. */
if (value != NULL)
{
if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
fb->ood_changed_rev = SVN_STR_TO_REV(value->data);
else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool,
value->data);
else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
{
apr_time_t tm;
SVN_ERR(svn_time_from_cstring(&tm, value->data,
fb->dir_baton->pool));
fb->ood_changed_date = tm;
}
}
return SVN_NO_ERROR;
}
/* An svn_delta_editor_t function. */
static svn_error_t *
close_file(void *file_baton,
const char *text_checksum, /* ignored, as we receive no data */
apr_pool_t *pool)
{
struct file_baton *fb = file_baton;
enum svn_wc_status_kind repos_node_status;
enum svn_wc_status_kind repos_text_status;
enum svn_wc_status_kind repos_prop_status;
const svn_lock_t *repos_lock = NULL;
/* If nothing has changed, return. */
if (! (fb->added || fb->prop_changed || fb->text_changed))
return SVN_NO_ERROR;
/* If this is a new file, add it to the statushash. */
if (fb->added)
{
repos_node_status = svn_wc_status_added;
repos_text_status = fb->text_changed ? svn_wc_status_modified
: 0 /* don't tweak */;
repos_prop_status = fb->prop_changed ? svn_wc_status_modified
: 0 /* don't tweak */;
if (fb->edit_baton->wb.repos_locks)
{
const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton,
pool);
/* repos_lock still uses the deprecated filesystem absolute path
format */
const char *repos_relpath = svn_relpath_join(dir_repos_relpath,
fb->name, pool);
repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks,
svn_fspath__join("/", repos_relpath,
pool));
}
}
else
{
repos_node_status = (fb->text_changed || fb->prop_changed)
? svn_wc_status_modified
: 0 /* don't tweak */;
repos_text_status = fb->text_changed ? svn_wc_status_modified
: 0 /* don't tweak */;
repos_prop_status = fb->prop_changed ? svn_wc_status_modified
: 0 /* don't tweak */;
}
return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db,
fb->edit_baton->wb.check_working_copy,
fb->local_abspath, repos_node_status,
repos_text_status, repos_prop_status,
SVN_INVALID_REVNUM, repos_lock, pool);
}
/* An svn_delta_editor_t function. */
static svn_error_t *
close_edit(void *edit_baton,
apr_pool_t *pool)
{
struct edit_baton *eb = edit_baton;
/* If we get here and the root was not opened as part of the edit,
we need to transmit statuses for everything. Otherwise, we
should be done. */
if (eb->root_opened)
return SVN_NO_ERROR;
SVN_ERR(svn_wc__internal_walk_status(eb->db,
eb->target_abspath,
eb->default_depth,
eb->get_all,
eb->no_ignore,
FALSE,
eb->ignores,
eb->status_func,
eb->status_baton,
eb->cancel_func,
eb->cancel_baton,
pool));
return SVN_NO_ERROR;
}
/*** Public API ***/
svn_error_t *
svn_wc__get_status_editor(const svn_delta_editor_t **editor,
void **edit_baton,
void **set_locks_baton,
svn_revnum_t *edit_revision,
svn_wc_context_t *wc_ctx,
const char *anchor_abspath,
const char *target_basename,
svn_depth_t depth,
svn_boolean_t get_all,
svn_boolean_t check_working_copy,
svn_boolean_t no_ignore,
svn_boolean_t depth_as_sticky,
svn_boolean_t server_performs_filtering,
const apr_array_header_t *ignore_patterns,
svn_wc_status_func4_t status_func,
void *status_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
struct edit_baton *eb;
svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool);
void *inner_baton;
struct svn_wc__shim_fetch_baton_t *sfb;
const svn_delta_editor_t *inner_editor;
svn_delta_shim_callbacks_t *shim_callbacks =
svn_delta_shim_callbacks_default(result_pool);
/* Construct an edit baton. */
eb = apr_pcalloc(result_pool, sizeof(*eb));
eb->default_depth = depth;
eb->target_revision = edit_revision;
eb->db = wc_ctx->db;
eb->get_all = get_all;
eb->no_ignore = no_ignore;
eb->status_func = status_func;
eb->status_baton = status_baton;
eb->cancel_func = cancel_func;
eb->cancel_baton = cancel_baton;
eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath);
eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename,
result_pool);
eb->target_basename = apr_pstrdup(result_pool, target_basename);
eb->root_opened = FALSE;
eb->wb.db = wc_ctx->db;
eb->wb.target_abspath = eb->target_abspath;
eb->wb.ignore_text_mods = !check_working_copy;
eb->wb.check_working_copy = check_working_copy;
eb->wb.repos_locks = NULL;
eb->wb.repos_root = NULL;
SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals,
wc_ctx->db, eb->target_abspath,
result_pool, scratch_pool));
/* Use the caller-provided ignore patterns if provided; the build-time
configured defaults otherwise. */
if (ignore_patterns)
{
eb->ignores = ignore_patterns;
}
else
{
apr_array_header_t *ignores;
SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool));
eb->ignores = ignores;
}
/* The edit baton's status structure maps to PATH, and the editor
have to be aware of whether that is the anchor or the target. */
SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath,
check_working_copy, result_pool, scratch_pool));
/* Construct an editor. */
tree_editor->set_target_revision = set_target_revision;
tree_editor->open_root = open_root;
tree_editor->delete_entry = delete_entry;
tree_editor->add_directory = add_directory;
tree_editor->open_directory = open_directory;
tree_editor->change_dir_prop = change_dir_prop;
tree_editor->close_directory = close_directory;
tree_editor->add_file = add_file;
tree_editor->open_file = open_file;
tree_editor->apply_textdelta = apply_textdelta;
tree_editor->change_file_prop = change_file_prop;
tree_editor->close_file = close_file;
tree_editor->close_edit = close_edit;
inner_editor = tree_editor;
inner_baton = eb;
if (!server_performs_filtering
&& !depth_as_sticky)
SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor,
&inner_baton,
wc_ctx->db,
anchor_abspath,
target_basename,
inner_editor,
inner_baton,
result_pool));
/* Conjoin a cancellation editor with our status editor. */
SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
inner_editor, inner_baton,
editor, edit_baton,
result_pool));
if (set_locks_baton)
*set_locks_baton = eb;
sfb = apr_palloc(result_pool, sizeof(*sfb));
sfb->db = wc_ctx->db;
sfb->base_abspath = eb->anchor_abspath;
sfb->fetch_base = FALSE;
shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func;
shim_callbacks->fetch_props_func = svn_wc__fetch_props_func;
shim_callbacks->fetch_base_func = svn_wc__fetch_base_func;
shim_callbacks->fetch_baton = sfb;
SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
NULL, NULL, shim_callbacks,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
/* Like svn_io_stat_dirent, but works case sensitive inside working
copies. Before 1.8 we handled this with a selection filter inside
a directory */
static svn_error_t *
stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent,
svn_wc__db_t *db,
const char *local_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_boolean_t is_wcroot;
/* The wcroot is "" inside the wc; handle it as not in the wc, as
the case of the root is indifferent to us. */
/* Note that for performance this is really just a few hashtable lookups,
as we just used local_abspath for a db call in both our callers */
SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
scratch_pool));
return svn_error_trace(
svn_io_stat_dirent2(dirent, local_abspath,
! is_wcroot /* verify_truename */,
TRUE /* ignore_enoent */,
result_pool, scratch_pool));
}
svn_error_t *
svn_wc__internal_walk_status(svn_wc__db_t *db,
const char *local_abspath,
svn_depth_t depth,
svn_boolean_t get_all,
svn_boolean_t no_ignore,
svn_boolean_t ignore_text_mods,
const apr_array_header_t *ignore_patterns,
svn_wc_status_func4_t status_func,
void *status_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
struct walk_status_baton wb;
const svn_io_dirent2_t *dirent;
const struct svn_wc__db_info_t *info;
svn_error_t *err;
wb.db = db;
wb.target_abspath = local_abspath;
wb.ignore_text_mods = ignore_text_mods;
wb.check_working_copy = TRUE;
wb.repos_root = NULL;
wb.repos_locks = NULL;
/* Use the caller-provided ignore patterns if provided; the build-time
configured defaults otherwise. */
if (!ignore_patterns)
{
apr_array_header_t *ignores;
SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool));
ignore_patterns = ignores;
}
err = svn_wc__db_read_single_info(&info, db, local_abspath,
FALSE /* base_tree_only */,
scratch_pool, scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
svn_error_clear(err);
info = NULL;
}
else
return svn_error_trace(err);
wb.externals = apr_hash_make(scratch_pool);
SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
scratch_pool, scratch_pool));
}
else
{
SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals,
db, local_abspath,
scratch_pool, scratch_pool));
SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
scratch_pool, scratch_pool));
}
if (info
&& info->has_descendants /* is dir, or was dir and has tc descendants */
&& info->status != svn_wc__db_status_not_present
&& info->status != svn_wc__db_status_excluded
&& info->status != svn_wc__db_status_server_excluded)
{
SVN_ERR(get_dir_status(&wb,
local_abspath,
FALSE /* skip_root */,
NULL, NULL, NULL,
info,
dirent,
ignore_patterns,
depth,
get_all,
no_ignore,
status_func, status_baton,
cancel_func, cancel_baton,
scratch_pool));
}
else
{
/* It may be a file or an unversioned item. And this is an explicit
* target, so no ignoring. An unversioned item (file or dir) shows a
* status like '?', and can yield a tree conflicted path. */
err = get_child_status(&wb,
local_abspath,
info,
dirent,
ignore_patterns,
get_all,
status_func, status_baton,
cancel_func, cancel_baton,
scratch_pool);
if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
{
/* The parent is also not versioned, but it is not nice to show
an error about a path a user didn't intend to touch. */
svn_error_clear(err);
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("The node '%s' was not found."),
svn_dirent_local_style(local_abspath,
scratch_pool));
}
SVN_ERR(err);
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_walk_status(svn_wc_context_t *wc_ctx,
const char *local_abspath,
svn_depth_t depth,
svn_boolean_t get_all,
svn_boolean_t no_ignore,
svn_boolean_t ignore_text_mods,
const apr_array_header_t *ignore_patterns,
svn_wc_status_func4_t status_func,
void *status_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
return svn_error_trace(
svn_wc__internal_walk_status(wc_ctx->db,
local_abspath,
depth,
get_all,
no_ignore,
ignore_text_mods,
ignore_patterns,
status_func,
status_baton,
cancel_func,
cancel_baton,
scratch_pool));
}
svn_error_t *
svn_wc_status_set_repos_locks(void *edit_baton,
apr_hash_t *locks,
const char *repos_root,
apr_pool_t *pool)
{
struct edit_baton *eb = edit_baton;
eb->wb.repos_locks = locks;
eb->wb.repos_root = apr_pstrdup(pool, repos_root);
return SVN_NO_ERROR;
}
svn_error_t *
svn_wc_get_default_ignores(apr_array_header_t **patterns,
apr_hash_t *config,
apr_pool_t *pool)
{
svn_config_t *cfg = config
? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG)
: NULL;
const char *val;
/* Check the Subversion run-time configuration for global ignores.
If no configuration value exists, we fall back to our defaults. */
svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY,
SVN_CONFIG_OPTION_GLOBAL_IGNORES,
SVN_CONFIG_DEFAULT_GLOBAL_IGNORES);
*patterns = apr_array_make(pool, 16, sizeof(const char *));
/* Split the patterns on whitespace, and stuff them into *PATTERNS. */
svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool);
return SVN_NO_ERROR;
}
/* */
static svn_error_t *
internal_status(svn_wc__internal_status_t **status,
svn_wc__db_t *db,
const char *local_abspath,
svn_boolean_t check_working_copy,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const svn_io_dirent2_t *dirent = NULL;
const char *parent_repos_relpath;
const char *parent_repos_root_url;
const char *parent_repos_uuid;
const struct svn_wc__db_info_t *info;
svn_boolean_t is_root = FALSE;
svn_error_t *err;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
err = svn_wc__db_read_single_info(&info, db, local_abspath,
!check_working_copy,
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);
info = NULL;
if (check_working_copy)
SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
scratch_pool, scratch_pool));
}
else if (check_working_copy)
SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath,
scratch_pool, scratch_pool));
if (!info
|| info->kind == svn_node_unknown
|| info->status == svn_wc__db_status_not_present
|| info->status == svn_wc__db_status_server_excluded
|| info->status == svn_wc__db_status_excluded)
return svn_error_trace(assemble_unversioned(status,
db, local_abspath,
dirent,
info ? info->conflicted : FALSE,
FALSE /* is_ignored */,
result_pool, scratch_pool));
if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
is_root = TRUE;
else
SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
/* Even though passing parent_repos_* is not required, assemble_status needs
these values to determine if a node is switched */
if (!is_root)
{
const char *const parent_abspath = svn_dirent_dirname(local_abspath,
scratch_pool);
if (check_working_copy)
SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL,
&parent_repos_relpath,
&parent_repos_root_url,
&parent_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));
else
SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL,
&parent_repos_relpath,
&parent_repos_root_url,
&parent_repos_uuid,
NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
db, parent_abspath,
result_pool, scratch_pool));
}
else
{
parent_repos_root_url = NULL;
parent_repos_relpath = NULL;
parent_repos_uuid = NULL;
}
return svn_error_trace(assemble_status(status, db, local_abspath,
parent_repos_root_url,
parent_repos_relpath,
parent_repos_uuid,
info,
dirent,
TRUE /* get_all */,
FALSE, check_working_copy,
NULL /* repos_lock */,
result_pool, scratch_pool));
}
svn_error_t *
svn_wc_status3(svn_wc_status3_t **status,
svn_wc_context_t *wc_ctx,
const char *local_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_wc__internal_status_t *stat;
SVN_ERR(internal_status(&stat, wc_ctx->db, local_abspath,
TRUE /* check_working_copy */,
result_pool, scratch_pool));
*status = &stat->s;
return SVN_NO_ERROR;
}
svn_wc_status3_t *
svn_wc_dup_status3(const svn_wc_status3_t *orig_stat,
apr_pool_t *pool)
{
/* Allocate slightly more room */
svn_wc__internal_status_t *new_istat = apr_palloc(pool, sizeof(*new_istat));
svn_wc_status3_t *new_stat = &new_istat->s;
/* Shallow copy all members. */
*new_stat = *orig_stat;
/* Now go back and dup the deep items into this pool. */
if (orig_stat->repos_lock)
new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool);
if (orig_stat->changed_author)
new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author);
if (orig_stat->ood_changed_author)
new_stat->ood_changed_author
= apr_pstrdup(pool, orig_stat->ood_changed_author);
if (orig_stat->lock)
new_stat->lock = svn_lock_dup(orig_stat->lock, pool);
if (orig_stat->changelist)
new_stat->changelist
= apr_pstrdup(pool, orig_stat->changelist);
if (orig_stat->repos_root_url)
new_stat->repos_root_url
= apr_pstrdup(pool, orig_stat->repos_root_url);
if (orig_stat->repos_relpath)
new_stat->repos_relpath
= apr_pstrdup(pool, orig_stat->repos_relpath);
if (orig_stat->repos_uuid)
new_stat->repos_uuid
= apr_pstrdup(pool, orig_stat->repos_uuid);
if (orig_stat->moved_from_abspath)
new_stat->moved_from_abspath
= apr_pstrdup(pool, orig_stat->moved_from_abspath);
if (orig_stat->moved_to_abspath)
new_stat->moved_to_abspath
= apr_pstrdup(pool, orig_stat->moved_to_abspath);
/* Return the new hotness. */
return new_stat;
}
svn_error_t *
svn_wc_get_ignores2(apr_array_header_t **patterns,
svn_wc_context_t *wc_ctx,
const char *local_abspath,
apr_hash_t *config,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
apr_array_header_t *default_ignores;
SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool));
return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db,
local_abspath,
default_ignores,
result_pool, scratch_pool));
}