diff options
Diffstat (limited to 'subversion/libsvn_wc/status.c')
-rw-r--r-- | subversion/libsvn_wc/status.c | 1244 |
1 files changed, 756 insertions, 488 deletions
diff --git a/subversion/libsvn_wc/status.c b/subversion/libsvn_wc/status.c index 51471d3..fa57b0a 100644 --- a/subversion/libsvn_wc/status.c +++ b/subversion/libsvn_wc/status.c @@ -53,6 +53,7 @@ #include "private/svn_wc_private.h" #include "private/svn_fspath.h" +#include "private/svn_editor.h" @@ -241,70 +242,7 @@ struct file_baton /** 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; - - 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, NULL, NULL, NULL, - NULL, &mtb->lock, &mtb->recorded_size, - &mtb->recorded_mod_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)) - { - SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - &mtb->lock, NULL, NULL, - db, local_abspath, - result_pool, scratch_pool)); - } - - mtb->has_checksum = (checksum != NULL); - -#ifdef HAVE_SYMLINK - if (mtb->kind == svn_wc__db_kind_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 != apr_hash_get(properties, SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING)); - } -#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 @@ -325,9 +263,9 @@ get_repos_root_url_relpath(const char **repos_relpath, { if (info->repos_relpath && info->repos_root_url) { - *repos_relpath = info->repos_relpath; - *repos_root_url = info->repos_root_url; - *repos_uuid = info->repos_uuid; + *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) { @@ -335,8 +273,8 @@ get_repos_root_url_relpath(const char **repos_relpath, svn_dirent_basename(local_abspath, NULL), result_pool); - *repos_root_url = parent_repos_root_url; - *repos_uuid = parent_repos_uuid; + *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) { @@ -346,13 +284,42 @@ get_repos_root_url_relpath(const char **repos_relpath, db, local_abspath, result_pool, scratch_pool)); } - else if (info->have_base) + else if (info->status == svn_wc__db_status_deleted + && !info->have_more_work + && 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 if (info->status == svn_wc__db_status_deleted) + { + const char *work_del_abspath; + const char *add_abspath; + + /* Handles working DELETE and the special case where there is just + svn_wc__db_status_not_present in WORKING */ + + SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, &work_del_abspath, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* The parent of what has been deleted must be added */ + add_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, repos_relpath, + repos_root_url, repos_uuid, NULL, + NULL, NULL, NULL, + db, add_abspath, + result_pool, scratch_pool)); + + *repos_relpath = svn_relpath_join(*repos_relpath, + svn_dirent_skip_ancestor( + add_abspath, + local_abspath), + result_pool); + } else { *repos_relpath = NULL; @@ -372,7 +339,7 @@ internal_status(svn_wc_status3_t **status, /* 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 the repository root + 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. @@ -406,10 +373,7 @@ assemble_status(svn_wc_status3_t **status, svn_boolean_t switched_p = FALSE; svn_boolean_t copied = FALSE; svn_boolean_t conflicted; - svn_error_t *err; - const char *repos_relpath; - const char *repos_root_url; - const char *repos_uuid; + const char *moved_from_abspath = NULL; svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file)) ? dirent->filesize : SVN_INVALID_FILESIZE; @@ -421,7 +385,8 @@ assemble_status(svn_wc_status3_t **status, if (!info) - SVN_ERR(read_info(&info, local_abspath, db, result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_read_single_info(&info, db, local_abspath, + result_pool, scratch_pool)); if (!info->repos_relpath || !parent_repos_relpath) switched_p = FALSE; @@ -430,58 +395,48 @@ assemble_status(svn_wc_status3_t **status, /* 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); + switched_p = !name || (strcmp(name, + svn_dirent_basename(local_abspath, NULL)) + != 0); } - /* Examine whether our target is missing or obstructed or missing. - - While we are not completely in single-db mode yet, data about - obstructed or missing nodes might be incomplete here. This is - reported by svn_wc_db_status_obstructed_XXXX. In single-db - mode these obstructions are no longer reported and we have - to detect obstructions by looking at the on disk status in DIRENT. - */ - if (info->kind == svn_wc__db_kind_dir) + if (info->status == svn_wc__db_status_incomplete || info->incomplete) { - 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; + /* 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) - copied = TRUE; - else - SVN_ERR(svn_wc__internal_node_get_schedule(NULL, &copied, - db, local_abspath, - scratch_pool)); - } - else if (!dirent || dirent->kind != svn_node_dir) + if (!info->have_base || info->have_more_work || info->copied) + copied = TRUE; + else if (!info->have_more_work && info->have_base) + copied = FALSE; + else { - /* A present or added directory 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; + 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 (info->status == svn_wc__db_status_deleted) - { - node_status = svn_wc_status_deleted; + /* 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; - SVN_ERR(svn_wc__internal_node_get_schedule(NULL, &copied, - db, local_abspath, - scratch_pool)); - } - else if (!dirent || dirent->kind != svn_node_file) + if (!dirent || dirent->kind != expected_kind) { - /* A present or added file should be on disk, so it is + /* 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; @@ -504,7 +459,7 @@ assemble_status(svn_wc_status3_t **status, If it was changed, then the subdir is incomplete or missing/obstructed. */ - if (info->kind != svn_wc__db_kind_dir + if (info->kind != svn_node_dir && node_status == svn_wc_status_normal) { svn_boolean_t text_modified_p = FALSE; @@ -516,8 +471,8 @@ assemble_status(svn_wc_status3_t **status, precedence over M. */ /* If the entry is a file, check for textual modifications */ - if ((info->kind == svn_wc__db_kind_file - || info->kind == svn_wc__db_kind_symlink) + if ((info->kind == svn_node_file + || info->kind == svn_node_symlink) #ifdef HAVE_SYMLINK && (info->special == (dirent && dirent->special)) #endif /* HAVE_SYMLINK */ @@ -533,12 +488,13 @@ assemble_status(svn_wc_status3_t **status, else if (ignore_text_mods ||(dirent && info->recorded_size != SVN_INVALID_FILESIZE - && info->recorded_mod_time != 0 + && info->recorded_time != 0 && info->recorded_size == dirent->filesize - && info->recorded_mod_time == dirent->mtime)) + && 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); @@ -590,31 +546,59 @@ assemble_status(svn_wc_status3_t **status, override a C text status.*/ if (info->status == svn_wc__db_status_added) { + copied = info->copied; if (!info->op_root) - copied = TRUE; /* And keep status normal */ - else if (info->kind == svn_wc__db_kind_file - && !info->have_base && !info->have_more_work) + { /* Keep status normal */ } + else if (!info->have_base && !info->have_more_work) { /* Simple addition or copy, no replacement */ node_status = svn_wc_status_added; - /* If an added node has a pristine file, it was copied */ - copied = info->has_checksum; } else { - svn_wc_schedule_t schedule; - SVN_ERR(svn_wc__internal_node_get_schedule(&schedule, &copied, - db, local_abspath, - scratch_pool)); - - if (schedule == svn_wc_schedule_add) + 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; - else if (schedule == svn_wc_schedule_replace) - node_status = svn_wc_status_replaced; + } + + /* 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; @@ -640,28 +624,20 @@ assemble_status(svn_wc_status3_t **status, return SVN_NO_ERROR; } - 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, - db, local_abspath, - scratch_pool, scratch_pool)); - /* 6. Build and return a status structure. */ stat = apr_pcalloc(result_pool, sizeof(**status)); switch (info->kind) { - case svn_wc__db_kind_dir: + case svn_node_dir: stat->kind = svn_node_dir; break; - case svn_wc__db_kind_file: - case svn_wc__db_kind_symlink: + case svn_node_file: + case svn_node_symlink: stat->kind = svn_node_file; break; - case svn_wc__db_kind_unknown: + case svn_node_unknown: default: stat->kind = svn_node_unknown; } @@ -678,7 +654,8 @@ assemble_status(svn_wc_status3_t **status, stat->repos_lock = repos_lock; stat->revision = info->revnum; stat->changed_rev = info->changed_rev; - stat->changed_author = info->changed_author; + 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; @@ -686,10 +663,19 @@ assemble_status(svn_wc_status3_t **status, 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 = apr_pcalloc(result_pool, sizeof(*lck)); - lck->path = repos_relpath; + 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; @@ -702,10 +688,17 @@ assemble_status(svn_wc_status3_t **status, stat->locked = info->locked; stat->conflicted = conflicted; stat->versioned = TRUE; - stat->changelist = info->changelist; - stat->repos_root_url = repos_root_url; - stat->repos_relpath = repos_relpath; - stat->repos_uuid = repos_uuid; + 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 = stat; @@ -817,10 +810,9 @@ send_status_structure(const struct walk_status_baton *wb, { /* repos_lock still uses the deprecated filesystem absolute path format */ - repos_lock = apr_hash_get(wb->repos_locks, - svn_fspath__join("/", repos_relpath, - scratch_pool), - APR_HASH_KEY_STRING); + repos_lock = svn_hash_gets(wb->repos_locks, + svn_fspath__join("/", repos_relpath, + scratch_pool)); } } @@ -838,9 +830,10 @@ send_status_structure(const struct walk_status_baton *wb, } -/* Store in PATTERNS a list of all svn:ignore properties from - the working copy directory, including the default ignores - passed in as IGNORES. +/* 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 @@ -849,7 +842,7 @@ send_status_structure(const struct walk_status_baton *wb, IGNORES is a list of patterns to include; typically this will be the default ignores as, for example, specified in a config file. - LOCAL_ABSPATH and DB control how to access the ignore information. + DB, LOCAL_ABSPATH is used to access the working copy. Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL. @@ -864,7 +857,9 @@ collect_ignore_patterns(apr_array_header_t **patterns, apr_pool_t *scratch_pool) { int i; - const svn_string_t *value; + apr_hash_t *props; + apr_array_header_t *inherited_props; + svn_error_t *err; /* ### assert we are passed a directory? */ @@ -878,12 +873,47 @@ collect_ignore_patterns(apr_array_header_t **patterns, ignore); } - /* Then add any svn:ignore globs to the PATTERNS array. */ - SVN_ERR(svn_wc__internal_propget(&value, db, local_abspath, SVN_PROP_IGNORE, - scratch_pool, scratch_pool)); - if (value != NULL) - svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, - result_pool); + 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; } @@ -901,7 +931,7 @@ is_external_path(apr_hash_t *externals, apr_hash_index_t *hi; /* First try: does the path exist as a key in the hash? */ - if (apr_hash_get(externals, local_abspath, APR_HASH_KEY_STRING)) + if (svn_hash_gets(externals, local_abspath)) return TRUE; /* Failing that, we need to check if any external is a child of @@ -928,13 +958,12 @@ is_external_path(apr_hash_t *externals, 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. EXTERNALS is a hash - of known externals definitions for this status run. + PATTERNS points to a list of filename patterns which are marked as ignored. + None of these parameter may be NULL. - If NO_IGNORE is non-zero, the item will be added regardless of + 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 PATTERNS. + does not match any of the patterns in PATTERN or INHERITED_IGNORES. Allocate everything in POOL. */ @@ -952,14 +981,13 @@ send_unversioned_item(const struct walk_status_baton *wb, 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( - svn_dirent_basename(local_abspath, NULL), - patterns, scratch_pool); - + 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, + dirent, tree_conflicted, + is_ignored, scratch_pool, scratch_pool)); is_external = is_external_path(wb->externals, local_abspath, scratch_pool); @@ -974,36 +1002,198 @@ send_unversioned_item(const struct walk_status_baton *wb, /* 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) + if (no_ignore + || !is_ignored + || is_external) return svn_error_trace((*status_func)(status_baton, local_abspath, status, scratch_pool)); return SVN_NO_ERROR; } -/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and - for all its entries through STATUS_FUNC/STATUS_BATON, or, if SELECTED - is non-NULL, only for that directory entry. +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); - PARENT_ENTRY is the entry for the parent of the directory or NULL - if LOCAL_ABSPATH is a working copy root. +/* 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. - However, upon recursing, all subdirs *will* be reported, regardless of this + All subdirs reached by recursion will be reported regardless of this parameter's value. - DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported, - so if SKIP_THIS_DIR or SELECTED is not-NULL DIRENT can be left NULL. + 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. + 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, - const char *selected, svn_boolean_t skip_this_dir, const char *parent_repos_root_url, const char *parent_repos_relpath, @@ -1024,9 +1214,9 @@ get_dir_status(const struct walk_status_baton *wb, const char *dir_repos_relpath; const char *dir_repos_uuid; apr_hash_t *dirents, *nodes, *conflicts, *all_children; - apr_array_header_t *patterns = NULL; apr_array_header_t *sorted_children; - apr_pool_t *iterpool, *subpool = svn_pool_create(scratch_pool); + apr_array_header_t *collected_ignore_patterns = NULL; + apr_pool_t *iterpool; svn_error_t *err; int i; @@ -1036,83 +1226,78 @@ get_dir_status(const struct walk_status_baton *wb, if (depth == svn_depth_unknown) depth = svn_depth_infinity; - iterpool = svn_pool_create(subpool); + iterpool = svn_pool_create(scratch_pool); - err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, subpool, iterpool); + 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(subpool); + dirents = apr_hash_make(scratch_pool); } else SVN_ERR(err); if (!dir_info) - SVN_ERR(read_info(&dir_info, local_abspath, wb->db, - subpool, iterpool)); + SVN_ERR(svn_wc__db_read_single_info(&dir_info, wb->db, local_abspath, + 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, - subpool, iterpool)); - if (selected == NULL) - { - /* 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, - subpool, iterpool)); + scratch_pool, iterpool)); - all_children = apr_hash_overlay(subpool, nodes, dirents); - if (apr_hash_count(conflicts) > 0) - all_children = apr_hash_overlay(subpool, conflicts, all_children); - } - else - { - const struct svn_wc__db_info_t *info; - const char *selected_abspath = svn_dirent_join(local_abspath, selected, - iterpool); - /* Create a hash containing just selected */ - all_children = apr_hash_make(subpool); - nodes = apr_hash_make(subpool); - conflicts = apr_hash_make(subpool); + /* 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)); - err = read_info(&info, selected_abspath, wb->db, subpool, 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); - if (err) - { - if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) - return svn_error_trace(err); - svn_error_clear(err); - /* The node is neither a tree conflict nor a versioned node */ - } - else + /* 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) { - if (!info->conflicted - || info->status != svn_wc__db_status_normal - || info->kind != svn_wc__db_kind_unknown) - { - /* The node is a normal versioned node */ - apr_hash_set(nodes, selected, APR_HASH_KEY_STRING, info); - } - - /* Drop it in the list of possible conflicts */ - if (info->conflicted) - apr_hash_set(conflicts, selected, APR_HASH_KEY_STRING, info); + 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)); } - - apr_hash_set(all_children, selected, APR_HASH_KEY_STRING, selected); - } - - if (!selected) - { - /* Handle "this-dir" first. */ - if (! skip_this_dir) + else SVN_ERR(send_status_structure(wb, local_abspath, parent_repos_root_url, parent_repos_relpath, @@ -1120,24 +1305,24 @@ get_dir_status(const struct walk_status_baton *wb, 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; } + /* 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, - subpool); + scratch_pool); for (i = 0; i < sorted_children->nelts; i++) { const void *key; apr_ssize_t klen; - const char *node_abspath; - svn_io_dirent2_t *dirent_p; - const struct svn_wc__db_info_t *info; 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); @@ -1145,97 +1330,113 @@ get_dir_status(const struct walk_status_baton *wb, key = item.key; klen = item.klen; - node_abspath = svn_dirent_join(local_abspath, key, iterpool); - dirent_p = apr_hash_get(dirents, key, klen); - - info = apr_hash_get(nodes, key, klen); - if (info) - { - if (info->status != svn_wc__db_status_not_present - && info->status != svn_wc__db_status_excluded - && info->status != svn_wc__db_status_server_excluded) - { - if (depth == svn_depth_files - && info->kind == svn_wc__db_kind_dir) - { - continue; - } - - SVN_ERR(send_status_structure(wb, node_abspath, - dir_repos_root_url, - dir_repos_relpath, - dir_repos_uuid, - info, dirent_p, get_all, - status_func, status_baton, - iterpool)); - - /* Descend in subdirectories. */ - if (depth == svn_depth_infinity - && info->kind == svn_wc__db_kind_dir) - { - SVN_ERR(get_dir_status(wb, node_abspath, NULL, TRUE, - dir_repos_root_url, dir_repos_relpath, - dir_repos_uuid, info, - dirent_p, ignore_patterns, - svn_depth_infinity, get_all, - no_ignore, - status_func, status_baton, - cancel_func, cancel_baton, - iterpool)); - } - - continue; - } - } - - if (apr_hash_get(conflicts, key, klen)) - { - /* Tree conflict */ - - if (ignore_patterns && ! patterns) - SVN_ERR(collect_ignore_patterns(&patterns, wb->db, local_abspath, - ignore_patterns, subpool, - iterpool)); - - SVN_ERR(send_unversioned_item(wb, - node_abspath, - dirent_p, TRUE, - patterns, - no_ignore, - status_func, - status_baton, - iterpool)); + 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)); + } - continue; - } + /* Destroy our subpools. */ + svn_pool_destroy(iterpool); - /* Unversioned node */ - if (dirent_p == NULL) - continue; /* Selected node, but not found */ + return SVN_NO_ERROR; +} - if (depth == svn_depth_files && dirent_p->kind == svn_node_dir) - continue; +/* 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 (svn_wc_is_adm_dir(key, iterpool)) - continue; + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); - if (ignore_patterns && ! patterns) - SVN_ERR(collect_ignore_patterns(&patterns, wb->db, local_abspath, - ignore_patterns, subpool, - iterpool)); + if (dirent->kind == svn_node_none) + dirent = NULL; - SVN_ERR(send_unversioned_item(wb, - node_abspath, - dirent_p, FALSE, - patterns, - no_ignore || selected, - status_func, status_baton, - iterpool)); - } + SVN_ERR(svn_wc__db_read_single_info(&dir_info, + wb->db, parent_abspath, + scratch_pool, scratch_pool)); - /* Destroy our subpools. */ - svn_pool_destroy(subpool); + 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; } @@ -1254,9 +1455,9 @@ hash_stash(void *baton, { apr_hash_t *stat_hash = baton; apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash); - assert(! apr_hash_get(stat_hash, path, APR_HASH_KEY_STRING)); - apr_hash_set(stat_hash, apr_pstrdup(hash_pool, path), - APR_HASH_KEY_STRING, svn_wc_dup_status3(status, hash_pool)); + 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; } @@ -1297,7 +1498,6 @@ tweak_statushash(void *baton, svn_boolean_t is_dir_baton, svn_wc__db_t *db, const char *local_abspath, - svn_boolean_t is_dir, enum svn_wc_status_kind repos_node_status, enum svn_wc_status_kind repos_text_status, enum svn_wc_status_kind repos_prop_status, @@ -1316,7 +1516,7 @@ tweak_statushash(void *baton, pool = apr_hash_pool_get(statushash); /* Is PATH already a hash-key? */ - statstruct = apr_hash_get(statushash, local_abspath, APR_HASH_KEY_STRING); + statstruct = svn_hash_gets(statushash, local_abspath); /* If not, make it so. */ if (! statstruct) @@ -1339,8 +1539,7 @@ tweak_statushash(void *baton, SVN_ERR(internal_status(&statstruct, db, local_abspath, pool, scratch_pool)); statstruct->repos_lock = repos_lock; - apr_hash_set(statushash, apr_pstrdup(pool, local_abspath), - APR_HASH_KEY_STRING, statstruct); + svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct); } /* Merge a repos "delete" + "add" into a single "replace". */ @@ -1376,9 +1575,9 @@ tweak_statushash(void *baton, else statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); - statstruct->repos_root_url = + statstruct->repos_root_url = b->edit_baton->anchor_status->repos_root_url; - statstruct->repos_uuid = + statstruct->repos_uuid = b->edit_baton->anchor_status->repos_uuid; } @@ -1386,7 +1585,7 @@ tweak_statushash(void *baton, isn't available. */ if (statstruct->repos_node_status == svn_wc_status_deleted) { - statstruct->ood_kind = is_dir ? svn_node_dir : svn_node_file; + 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 @@ -1442,9 +1641,8 @@ find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool) { const char *repos_relpath; struct dir_baton *pb = db->parent_baton; - const svn_wc_status3_t *status = apr_hash_get(pb->statii, - db->local_abspath, - APR_HASH_KEY_STRING); + 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. */ @@ -1464,32 +1662,40 @@ make_dir_baton(void **dir_baton, const char *path, struct edit_baton *edit_baton, struct dir_baton *parent_baton, - apr_pool_t *pool) + apr_pool_t *result_pool) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = edit_baton; - struct dir_baton *d = apr_pcalloc(pool, sizeof(*d)); + 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, pool); + 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, pool) : NULL; + d->name = path ? svn_dirent_basename(path, dir_pool) : NULL; d->edit_baton = edit_baton; d->parent_baton = parent_baton; - d->pool = pool; - d->statii = apr_hash_make(pool); + d->statii = apr_hash_make(dir_pool); d->ood_changed_rev = SVN_INVALID_REVNUM; d->ood_changed_date = 0; - d->repos_relpath = apr_pstrdup(pool, find_dir_repos_relpath(d, pool)); + d->repos_relpath = find_dir_repos_relpath(d, dir_pool); d->ood_kind = svn_node_dir; d->ood_changed_author = NULL; @@ -1516,8 +1722,7 @@ make_dir_baton(void **dir_baton, /* 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 = apr_hash_get(pb->statii, d->local_abspath, - APR_HASH_KEY_STRING); + status_in_parent = svn_hash_gets(pb->statii, d->local_abspath); else status_in_parent = eb->anchor_status; @@ -1534,7 +1739,7 @@ make_dir_baton(void **dir_baton, 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, NULL, TRUE, + 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, @@ -1546,11 +1751,10 @@ make_dir_baton(void **dir_baton, TRUE, TRUE, hash_stash, d->statii, eb->cancel_func, eb->cancel_baton, - pool)); + dir_pool)); /* If we found a depth here, it should govern. */ - this_dir_status = apr_hash_get(d->statii, d->local_abspath, - APR_HASH_KEY_STRING); + 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)) @@ -1719,7 +1923,7 @@ handle_statii(struct edit_baton *eb, || depth == svn_depth_infinity)) { SVN_ERR(get_dir_status(&eb->wb, - local_abspath, NULL, TRUE, + local_abspath, TRUE, dir_repos_root_url, dir_repos_relpath, dir_repos_uuid, NULL, @@ -1747,7 +1951,7 @@ handle_statii(struct edit_baton *eb, /*** 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, @@ -1759,7 +1963,7 @@ set_target_revision(void *edit_baton, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, @@ -1772,7 +1976,7 @@ open_root(void *edit_baton, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * delete_entry(const char *path, svn_revnum_t revision, @@ -1782,16 +1986,13 @@ delete_entry(const char *path, 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); - svn_wc__db_kind_t kind; /* 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(svn_wc__db_read_kind(&kind, eb->db, local_abspath, FALSE, pool)); SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, - local_abspath, kind == svn_wc__db_kind_dir, + local_abspath, svn_wc_status_deleted, 0, 0, revision, NULL, pool)); /* Mark the parent dir -- it lost an entry (unless that parent dir @@ -1800,7 +2001,6 @@ delete_entry(const char *path, if (db->parent_baton && (! *eb->target_basename)) SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db, db->local_abspath, - kind == svn_wc__db_kind_dir, svn_wc_status_modified, svn_wc_status_modified, 0, SVN_INVALID_REVNUM, NULL, pool)); @@ -1808,7 +2008,7 @@ delete_entry(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * add_directory(const char *path, void *parent_baton, @@ -1834,7 +2034,7 @@ add_directory(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_directory(const char *path, void *parent_baton, @@ -1847,7 +2047,7 @@ open_directory(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_dir_prop(void *dir_baton, const char *name, @@ -1878,7 +2078,7 @@ change_dir_prop(void *dir_baton, -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * close_directory(void *dir_baton, apr_pool_t *pool) @@ -1886,6 +2086,7 @@ close_directory(void *dir_baton, 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. */ @@ -1900,7 +2101,7 @@ close_directory(void *dir_baton, if (db->added) { repos_node_status = svn_wc_status_added; - repos_text_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; } @@ -1923,9 +2124,9 @@ close_directory(void *dir_baton, /* ### 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, - TRUE, repos_node_status, repos_text_status, + repos_node_status, repos_text_status, repos_prop_status, SVN_INVALID_REVNUM, NULL, - pool)); + scratch_pool)); } else { @@ -1956,8 +2157,7 @@ close_directory(void *dir_baton, const svn_wc_status3_t *dir_status; /* See if the directory was deleted or replaced. */ - dir_status = apr_hash_get(pb->statii, db->local_abspath, - APR_HASH_KEY_STRING); + 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))) @@ -1968,12 +2168,12 @@ close_directory(void *dir_baton, 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, pool)); + 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, pool)); - apr_hash_set(pb->statii, db->local_abspath, APR_HASH_KEY_STRING, NULL); + dir_status, scratch_pool)); + svn_hash_sets(pb->statii, db->local_abspath, NULL); } else if (! pb) { @@ -1983,15 +2183,14 @@ close_directory(void *dir_baton, { const svn_wc_status3_t *tgt_status; - tgt_status = apr_hash_get(db->statii, eb->target_abspath, - APR_HASH_KEY_STRING); + 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, NULL, TRUE, + eb->target_abspath, TRUE, NULL, NULL, NULL, NULL, NULL /* dirent */, eb->ignores, @@ -1999,11 +2198,11 @@ close_directory(void *dir_baton, eb->get_all, eb->no_ignore, eb->status_func, eb->status_baton, eb->cancel_func, eb->cancel_baton, - pool)); + 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, pool)); + tgt_status, scratch_pool)); } } else @@ -2015,20 +2214,24 @@ close_directory(void *dir_baton, eb->anchor_status->repos_root_url, eb->anchor_status->repos_relpath, eb->anchor_status->repos_uuid, - db->statii, FALSE, eb->default_depth, pool)); + 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, pool)); + 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, @@ -2051,7 +2254,7 @@ add_file(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * open_file(const char *path, void *parent_baton, @@ -2067,7 +2270,7 @@ open_file(const char *path, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * apply_textdelta(void *file_baton, const char *base_checksum, @@ -2088,7 +2291,7 @@ apply_textdelta(void *file_baton, } -/* */ +/* An svn_delta_editor_t function. */ static svn_error_t * change_file_prop(void *file_baton, const char *name, @@ -2120,7 +2323,7 @@ change_file_prop(void *file_baton, } -/* */ +/* 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 */ @@ -2140,8 +2343,10 @@ close_file(void *file_baton, if (fb->added) { repos_node_status = svn_wc_status_added; - repos_text_status = svn_wc_status_added; - repos_prop_status = fb->prop_changed ? svn_wc_status_added : 0; + 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) { @@ -2153,27 +2358,29 @@ close_file(void *file_baton, const char *repos_relpath = svn_relpath_join(dir_repos_relpath, fb->name, pool); - repos_lock = apr_hash_get(fb->edit_baton->wb.repos_locks, - svn_fspath__join("/", repos_relpath, - pool), - APR_HASH_KEY_STRING); + 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; - repos_text_status = fb->text_changed ? svn_wc_status_modified : 0; - repos_prop_status = fb->prop_changed ? svn_wc_status_modified : 0; + ? 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, FALSE, repos_node_status, + 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) @@ -2207,7 +2414,7 @@ close_edit(void *edit_baton, /*** Public API ***/ svn_error_t * -svn_wc_get_status_editor5(const svn_delta_editor_t **editor, +svn_wc__get_status_editor(const svn_delta_editor_t **editor, void **edit_baton, void **set_locks_baton, svn_revnum_t *edit_revision, @@ -2230,7 +2437,10 @@ svn_wc_get_status_editor5(const svn_delta_editor_t **editor, 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)); @@ -2318,9 +2528,50 @@ svn_wc_get_status_editor5(const svn_delta_editor_t **editor, 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, @@ -2337,9 +2588,7 @@ svn_wc__internal_walk_status(svn_wc__db_t *db, { struct walk_status_baton wb; const svn_io_dirent2_t *dirent; - const char *anchor_abspath, *target_name; - svn_boolean_t skip_root; - const struct svn_wc__db_info_t *dir_info; + const struct svn_wc__db_info_t *info; svn_error_t *err; wb.db = db; @@ -2348,9 +2597,6 @@ svn_wc__internal_walk_status(svn_wc__db_t *db, wb.repos_root = NULL; wb.repos_locks = NULL; - SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals, db, local_abspath, - scratch_pool, scratch_pool)); - /* Use the caller-provided ignore patterns if provided; the build-time configured defaults otherwise. */ if (!ignore_patterns) @@ -2361,71 +2607,81 @@ svn_wc__internal_walk_status(svn_wc__db_t *db, ignore_patterns = ignores; } - err = read_info(&dir_info, local_abspath, db, scratch_pool, scratch_pool); + err = svn_wc__db_read_single_info(&info, db, local_abspath, + scratch_pool, scratch_pool); - if (!err - && dir_info->kind == svn_wc__db_kind_dir - && dir_info->status != svn_wc__db_status_not_present - && dir_info->status != svn_wc__db_status_excluded - && dir_info->status != svn_wc__db_status_server_excluded) + if (err) { - anchor_abspath = local_abspath; - target_name = NULL; - skip_root = FALSE; + 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 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) - return svn_error_trace(err); else { - svn_error_clear(err); - dir_info = NULL; /* Don't pass information of the child */ + SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals, + db, local_abspath, + scratch_pool, scratch_pool)); - /* Walk the status of the parent of LOCAL_ABSPATH, but only report - status on its child LOCAL_ABSPATH. */ - anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); - target_name = svn_dirent_basename(local_abspath, NULL); - skip_root = TRUE; + SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, + scratch_pool, scratch_pool)); } - SVN_ERR(svn_io_stat_dirent(&dirent, local_abspath, TRUE, - scratch_pool, scratch_pool)); - -#ifdef HAVE_SYMLINK - if (dirent->special && !skip_root) + 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_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, - scratch_pool); - - /* We're being pointed to the status root via a symlink. - * Get the real node kind and pretend the path is not a symlink. - * This prevents send_status_structure() from treating the root - * as a directory obstructed by a file. */ - SVN_ERR(svn_io_check_resolved_path(local_abspath, - &this_dirent->kind, scratch_pool)); - this_dirent->special = FALSE; - SVN_ERR(send_status_structure(&wb, local_abspath, - NULL, NULL, NULL, - dir_info, this_dirent, get_all, - status_func, status_baton, - scratch_pool)); - skip_root = TRUE; + 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); } -#endif - - SVN_ERR(get_dir_status(&wb, - anchor_abspath, - target_name, - skip_root, - NULL, NULL, NULL, - dir_info, - dirent, - ignore_patterns, - depth, - get_all, - no_ignore, - status_func, status_baton, - cancel_func, cancel_baton, - scratch_pool)); return SVN_NO_ERROR; } @@ -2480,9 +2736,9 @@ svn_wc_get_default_ignores(apr_array_header_t **patterns, apr_hash_t *config, apr_pool_t *pool) { - svn_config_t *cfg = config ? apr_hash_get(config, - SVN_CONFIG_CATEGORY_CONFIG, - APR_HASH_KEY_STRING) : NULL; + 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. @@ -2507,7 +2763,7 @@ internal_status(svn_wc_status3_t **status, apr_pool_t *scratch_pool) { const svn_io_dirent2_t *dirent; - svn_wc__db_kind_t node_kind; + svn_node_kind_t node_kind; const char *parent_repos_relpath; const char *parent_repos_root_url; const char *parent_repos_uuid; @@ -2518,9 +2774,6 @@ internal_status(svn_wc_status3_t **status, SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); - SVN_ERR(svn_io_stat_dirent(&dirent, local_abspath, TRUE, - scratch_pool, scratch_pool)); - 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, @@ -2528,26 +2781,33 @@ internal_status(svn_wc_status3_t **status, db, local_abspath, scratch_pool, scratch_pool); - if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + if (err) { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + svn_error_clear(err); - node_kind = svn_wc__db_kind_unknown; + 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 if (err) - { - return svn_error_trace(err); - } - else if (node_status == svn_wc__db_status_not_present - || node_status == svn_wc__db_status_server_excluded - || node_status == svn_wc__db_status_excluded) + 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_wc__db_kind_unknown; + node_kind = svn_node_unknown; } - if (node_kind == svn_wc__db_kind_unknown) + if (node_kind == svn_node_unknown) return svn_error_trace(assemble_unversioned(status, db, local_abspath, dirent, conflicted, @@ -2655,6 +2915,14 @@ svn_wc_dup_status3(const svn_wc_status3_t *orig_stat, 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; } |