diff options
Diffstat (limited to 'subversion/libsvn_wc/status.c')
-rw-r--r-- | subversion/libsvn_wc/status.c | 3047 |
1 files changed, 3047 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/status.c b/subversion/libsvn_wc/status.c new file mode 100644 index 0000000..1440b2e --- /dev/null +++ b/subversion/libsvn_wc/status.c @@ -0,0 +1,3047 @@ +/* + * 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_path.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 "entries.h" +#include "translate.h" +#include "tree_conflicts.h" + +#include "private/svn_wc_private.h" +#include "private/svn_fspath.h" +#include "private/svn_editor.h" + + + +/*** 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; + + /* 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; + svn_wc_context_t *wc_ctx; + + /* 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_status3_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 **/ + +/* Fill in *INFO with the information it would contain if it were + obtained from svn_wc__db_read_children_info. */ +static svn_error_t * +read_info(const struct svn_wc__db_info_t **info, + const char *local_abspath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct svn_wc__db_info_t *mtb = apr_pcalloc(result_pool, sizeof(*mtb)); + const svn_checksum_t *checksum; + const char *original_repos_relpath; + + SVN_ERR(svn_wc__db_read_info(&mtb->status, &mtb->kind, + &mtb->revnum, &mtb->repos_relpath, + &mtb->repos_root_url, &mtb->repos_uuid, + &mtb->changed_rev, &mtb->changed_date, + &mtb->changed_author, &mtb->depth, + &checksum, NULL, &original_repos_relpath, NULL, + NULL, NULL, &mtb->lock, &mtb->recorded_size, + &mtb->recorded_time, &mtb->changelist, + &mtb->conflicted, &mtb->op_root, + &mtb->had_props, &mtb->props_mod, + &mtb->have_base, &mtb->have_more_work, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_wclocked(&mtb->locked, db, local_abspath, scratch_pool)); + + /* Maybe we have to get some shadowed lock from BASE to make our test suite + happy... (It might be completely unrelated, but...) */ + if (mtb->have_base + && (mtb->status == svn_wc__db_status_added + || mtb->status == svn_wc__db_status_deleted + || mtb->kind == svn_node_file)) + { + svn_boolean_t update_root; + svn_wc__db_lock_t **lock_arg = NULL; + + if (mtb->status == svn_wc__db_status_added + || mtb->status == svn_wc__db_status_deleted) + lock_arg = &mtb->lock; + + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + lock_arg, NULL, NULL, &update_root, + db, local_abspath, + result_pool, scratch_pool)); + + mtb->file_external = (update_root && mtb->kind == svn_node_file); + + if (mtb->status == svn_wc__db_status_deleted) + { + const char *moved_to_abspath; + const char *moved_to_op_root_abspath; + + /* NOTE: we can't use op-root-ness as a condition here since a base + * node can be the root of a move and still not be an explicit + * op-root (having a working node with op_depth == pathelements). + * + * Both these (almost identical) situations showcase this: + * svn mv a/b bb + * svn del a + * and + * svn mv a aa + * svn mv aa/b bb + * In both, 'bb' is moved from 'a/b', but 'a/b' has no op_depth>0 + * node at all, as its parent 'a' is locally deleted. */ + + SVN_ERR(svn_wc__db_scan_deletion(NULL, + &moved_to_abspath, + NULL, + &moved_to_op_root_abspath, + db, local_abspath, + scratch_pool, scratch_pool)); + if (moved_to_abspath != NULL + && moved_to_op_root_abspath != NULL + && strcmp(moved_to_abspath, moved_to_op_root_abspath) == 0) + { + mtb->moved_to_abspath = apr_pstrdup(result_pool, + moved_to_abspath); + } + /* ### ^^^ THIS SUCKS. For at least two reasons: + * 1) We scan the node deletion and that's technically not necessary. + * We'd be fine to know if this is an actual root of a move. + * 2) From the elaborately calculated results, we backwards-guess + * whether this is a root. + * It works ok, and this code only gets called when a node is an + * explicit target of a 'status'. But it would be better to do this + * differently. + * We could return moved-to via svn_wc__db_base_get_info() (called + * just above), but as moved-to is only intended to be returned for + * roots of a move, that doesn't fit too well. */ + } + } + + /* ### svn_wc__db_read_info() could easily return the moved-here flag. But + * for now... (The per-dir query for recursive status is far more optimal.) + * Note that this actually scans around to get the full path, for a bool. + * This bool then gets returned, later is evaluated, and if true leads to + * the same paths being scanned again. We'd want to obtain this bool here as + * cheaply as svn_wc__db_read_children_info() does. */ + if (mtb->status == svn_wc__db_status_added) + { + svn_wc__db_status_t status; + + SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + mtb->moved_here = (status == svn_wc__db_status_moved_here); + mtb->incomplete = (status == svn_wc__db_status_incomplete); + } + + mtb->has_checksum = (checksum != NULL); + mtb->copied = (original_repos_relpath != NULL); + +#ifdef HAVE_SYMLINK + if (mtb->kind == svn_node_file + && (mtb->had_props || mtb->props_mod)) + { + apr_hash_t *properties; + + if (mtb->props_mod) + SVN_ERR(svn_wc__db_read_props(&properties, db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_pristine_props(&properties, db, local_abspath, + scratch_pool, scratch_pool)); + + mtb->special = (NULL != svn_hash_gets(properties, SVN_PROP_SPECIAL)); + } +#endif + *info = mtb; + + return SVN_NO_ERROR; +} + +/* 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 if (info->status == svn_wc__db_status_added) + { + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, + repos_relpath, repos_root_url, + repos_uuid, NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + } + else if (info->have_base) + { + SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url, + repos_uuid, + db, local_abspath, + result_pool, scratch_pool)); + } + else + { + *repos_relpath = NULL; + *repos_root_url = NULL; + *repos_uuid = NULL; + } + return SVN_NO_ERROR; +} + +static svn_error_t * +internal_status(svn_wc_status3_t **status, + svn_wc__db_t *db, + const char *local_abspath, + 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. + + The status struct's repos_lock field will be set to REPOS_LOCK. +*/ +static svn_error_t * +assemble_status(svn_wc_status3_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, + const svn_lock_t *repos_lock, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + 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; + svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file)) + ? dirent->filesize + : SVN_INVALID_FILESIZE; + + /* 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) + SVN_ERR(read_info(&info, local_abspath, db, result_pool, scratch_pool)); + + 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 + { + /* 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- entry, don't bother + to allocate a struct for an uninteresting entry. */ + + if (! get_all) + if (((node_status == svn_wc_status_none) + || (node_status == svn_wc_status_normal)) + + && (! switched_p) + && (! info->locked ) + && (! info->lock) + && (! repos_lock) + && (! info->changelist) + && (! conflicted)) + { + *status = NULL; + return SVN_NO_ERROR; + } + + /* 6. Build and return a status structure. */ + + stat = apr_pcalloc(result_pool, sizeof(**status)); + + 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; + stat->filesize = filesize; + 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; + if (info->moved_to_abspath) + stat->moved_to_abspath = apr_pstrdup(result_pool, info->moved_to_abspath); + + stat->file_external = info->file_external; + + *status = 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_status3_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_status3_t *stat; + + /* return a fairly blank structure. */ + stat = apr_pcalloc(result_pool, sizeof(*stat)); + + /*stat->versioned = FALSE;*/ + stat->kind = svn_node_unknown; /* not versioned */ + stat->depth = svn_depth_unknown; + stat->filesize = (dirent && dirent->kind == svn_node_file) + ? dirent->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 = stat; + 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_status3_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, + repos_lock, scratch_pool, scratch_pool)); + + if (statstruct && status_func) + return svn_error_trace((*status_func)(status_baton, local_abspath, + statstruct, 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 = svn__apr_hash_index_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_status3_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->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->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, 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->kind == svn_node_dir) + { + 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) + { + /* Selected node, but not found */ + if (dirent == NULL) + return SVN_NO_ERROR; + + if (depth == svn_depth_files && 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); + + err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, 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); + + if (!dir_info) + SVN_ERR(read_info(&dir_info, local_abspath, wb->db, + 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, + 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(read_info(&dir_info, parent_abspath, wb->db, + 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); + assert(! svn_hash_gets(stat_hash, path)); + svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), + svn_wc_dup_status3(status, hash_pool)); + + 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, + 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) + { + /* 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(&statstruct, db, local_abspath, pool, + scratch_pool)); + 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->repos_root_url; + statstruct->repos_uuid = + b->edit_baton->anchor_status->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->repos_root_url; + statstruct->repos_uuid = + b->edit_baton->anchor_status->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->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_status3_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->versioned + && (status_in_parent->kind == svn_node_dir) + && (! 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->repos_root_url, + NULL /*parent_repos_relpath*/, + status_in_parent->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->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(). + */ +static svn_boolean_t +is_sendable_status(const svn_wc_status3_t *status, + svn_boolean_t no_ignore, + svn_boolean_t get_all) +{ + /* 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))) + 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; + + /* 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 = svn__apr_hash_index_key(hi); + svn_wc_status3_t *status = svn__apr_hash_index_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->versioned && status->kind == svn_node_dir + && (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->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, + 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, + 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, + 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, 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->repos_node_status = repos_node_status; + eb->anchor_status->repos_prop_status = repos_prop_status; + eb->anchor_status->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->revision) + { + eb->anchor_status->ood_changed_rev = db->ood_changed_rev; + eb->anchor_status->ood_changed_date = db->ood_changed_date; + eb->anchor_status->ood_kind = db->ood_kind; + eb->anchor_status->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; + const svn_wc_status3_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->repos_node_status == svn_wc_status_deleted) + || (dir_status->repos_node_status == svn_wc_status_replaced))) + was_deleted = TRUE; + + /* Now do the status reporting. */ + SVN_ERR(handle_statii(eb, + dir_status ? dir_status->repos_root_url : NULL, + dir_status ? dir_status->repos_relpath : NULL, + dir_status ? dir_status->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, 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_status3_t *tgt_status; + + tgt_status = svn_hash_gets(db->statii, eb->target_abspath); + if (tgt_status) + { + if (tgt_status->versioned + && tgt_status->kind == svn_node_dir) + { + 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, 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->repos_root_url, + eb->anchor_status->repos_relpath, + eb->anchor_status->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, 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->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_walk_status(eb->wc_ctx, + 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 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->wc_ctx = wc_ctx; + 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 = FALSE; + 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, + 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.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 = read_info(&info, local_abspath, db, 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->kind == svn_node_dir + && 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_status3_t **status, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_io_dirent2_t *dirent; + svn_node_kind_t node_kind; + const char *parent_repos_relpath; + const char *parent_repos_root_url; + const char *parent_repos_uuid; + svn_wc__db_status_t node_status; + svn_boolean_t conflicted; + svn_boolean_t is_root = FALSE; + svn_error_t *err; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + node_kind = svn_node_unknown; + /* Ensure conflicted is always set, but don't hide tree conflicts + on 'hidden' nodes. */ + conflicted = FALSE; + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, + scratch_pool, scratch_pool)); + } + else + SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, + scratch_pool, scratch_pool)); + + if (node_kind != svn_node_unknown + && (node_status == svn_wc__db_status_not_present + || node_status == svn_wc__db_status_server_excluded + || node_status == svn_wc__db_status_excluded)) + { + node_kind = svn_node_unknown; + } + + if (node_kind == svn_node_unknown) + return svn_error_trace(assemble_unversioned(status, + db, local_abspath, + dirent, conflicted, + 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)); + + if (!is_root) + { + svn_wc__db_status_t parent_status; + const char *parent_abspath = svn_dirent_dirname(local_abspath, + scratch_pool); + + err = svn_wc__db_read_info(&parent_status, 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); + + if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND + || SVN_WC__ERR_IS_NOT_CURRENT_WC(err))) + { + svn_error_clear(err); + parent_repos_root_url = NULL; + parent_repos_relpath = NULL; + parent_repos_uuid = NULL; + } + else SVN_ERR(err); + } + 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, + NULL, + dirent, + TRUE /* get_all */, + FALSE, + 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) +{ + return svn_error_trace( + internal_status(status, wc_ctx->db, local_abspath, result_pool, + scratch_pool)); +} + +svn_wc_status3_t * +svn_wc_dup_status3(const svn_wc_status3_t *orig_stat, + apr_pool_t *pool) +{ + svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat)); + + /* 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)); +} |