/* Go up the directory tree from LOCAL_ABSPATH, looking for a versioned * directory. If found, return its path in *EXISTING_PARENT_ABSPATH. * Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */ static svn_error_t * find_existing_parent(const char **existing_parent_abspath, svn_client_ctx_t *ctx, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_node_kind_t kind; const char *parent_abspath; svn_wc_context_t *wc_ctx = ctx->wc_ctx; SVN_ERR(svn_wc_read_kind(&kind, wc_ctx, local_abspath, FALSE, scratch_pool)); if (kind == svn_node_dir) { svn_boolean_t is_deleted; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, local_abspath, scratch_pool)); if (!is_deleted) { *existing_parent_abspath = apr_pstrdup(result_pool, local_abspath); return SVN_NO_ERROR; } } if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) return svn_error_create(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL); if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, scratch_pool), scratch_pool)) return svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, NULL, _("'%s' ends in a reserved name"), svn_dirent_local_style(local_abspath, scratch_pool)); parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); SVN_ERR(find_existing_parent(existing_parent_abspath, ctx, parent_abspath, result_pool, scratch_pool)); return SVN_NO_ERROR; }
/* Go up the directory tree, looking for a versioned directory. If found, add all the intermediate directories. Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */ static svn_error_t * add_parent_dirs(const char *path, svn_wc_adm_access_t **parent_access, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_wc_adm_access_t *adm_access; svn_error_t *err; err = svn_wc_adm_open3(&adm_access, NULL, path, TRUE, 0, ctx->cancel_func, ctx->cancel_baton, pool); if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY) { if (svn_dirent_is_root(path, strlen(path))) { svn_error_clear(err); return svn_error_create (SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL); } else { const char *parent_path = svn_path_dirname(path, pool); svn_error_clear(err); SVN_ERR(add_parent_dirs(parent_path, &adm_access, ctx, pool)); SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, parent_path, pool)); SVN_ERR(svn_wc_add2(path, adm_access, NULL, SVN_INVALID_REVNUM, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } } else if (err) { return err; } if (parent_access) *parent_access = adm_access; return SVN_NO_ERROR; }
/* Make an unversioned copy of the versioned file or directory tree at the * source path FROM_ABSPATH. Copy it to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * See copy_one_versioned_file() for details of file copying behaviour, * including IGNORE_KEYWORDS and NATIVE_EOL. * * Include externals unless IGNORE_EXTERNALS is true. * * Recurse according to DEPTH. * */ static svn_error_t * copy_versioned_files(const char *from_abspath, const char *to_abspath, const svn_opt_revision_t *revision, svn_boolean_t force, svn_boolean_t ignore_externals, svn_boolean_t ignore_keywords, svn_depth_t depth, const char *native_eol, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_error_t *err; apr_pool_t *iterpool; const apr_array_header_t *children; svn_node_kind_t from_kind; svn_depth_t node_depth; SVN_ERR_ASSERT(svn_dirent_is_absolute(from_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); /* Only export 'added' and 'replaced' files when the revision is WORKING; when the revision is BASE (i.e. != WORKING), only export 'added' and 'replaced' files when they are part of a copy-/move-here. Otherwise, skip them, since they don't have an associated text-base. This condition for added/replaced simply is an optimization. Added and replaced files would be handled similarly by svn_wc_get_pristine_contents2(), which would return NULL if they have no base associated. TODO: We may prefer not to duplicate this condition and rather use svn_wc_get_pristine_contents2() or a dedicated new function instead. Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind != svn_opt_revision_working) { svn_boolean_t is_added; const char *repos_relpath; SVN_ERR(svn_wc__node_get_origin(&is_added, NULL, &repos_relpath, NULL, NULL, NULL, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); if (is_added && !repos_relpath) return SVN_NO_ERROR; /* Local addition */ } else { svn_boolean_t is_deleted; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx, from_abspath, pool)); if (is_deleted) return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&from_kind, ctx->wc_ctx, from_abspath, FALSE, pool)); if (from_kind == svn_node_dir) { apr_fileperms_t perm = APR_OS_DEFAULT; int j; /* Try to make the new directory. If this fails because the directory already exists, check our FORCE flag to see if we care. */ /* Keep the source directory's permissions if applicable. Skip retrieving the umask on windows. Apr does not implement setting filesystem privileges on Windows. Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER is documented to be 'incredibly expensive' */ #ifndef WIN32 if (revision->kind == svn_opt_revision_working) { apr_finfo_t finfo; SVN_ERR(svn_io_stat(&finfo, from_abspath, APR_FINFO_PROT, pool)); perm = finfo.protection; } #endif err = svn_io_dir_make(to_abspath, perm, pool); if (err) { if (! APR_STATUS_IS_EEXIST(err->apr_err)) return svn_error_trace(err); if (! force) SVN_ERR_W(err, _("Destination directory exists, and will not be " "overwritten unless forced")); else svn_error_clear(err); } SVN_ERR(svn_wc__node_get_children(&children, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); iterpool = svn_pool_create(pool); for (j = 0; j < children->nelts; j++) { const char *child_abspath = APR_ARRAY_IDX(children, j, const char *); const char *child_name = svn_dirent_basename(child_abspath, NULL); const char *target_abspath; svn_node_kind_t child_kind; svn_pool_clear(iterpool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); target_abspath = svn_dirent_join(to_abspath, child_name, iterpool); SVN_ERR(svn_wc_read_kind(&child_kind, ctx->wc_ctx, child_abspath, FALSE, iterpool)); if (child_kind == svn_node_dir) { if (depth == svn_depth_infinity || depth == svn_depth_immediates) { if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(target_abspath, svn_wc_notify_update_add, pool); notify->kind = svn_node_dir; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } if (depth == svn_depth_infinity) SVN_ERR(copy_versioned_files(child_abspath, target_abspath, revision, force, ignore_externals, ignore_keywords, depth, native_eol, ctx, iterpool)); else SVN_ERR(svn_io_make_dir_recursively(target_abspath, iterpool)); } } else if (child_kind == svn_node_file && depth >= svn_depth_files) { svn_node_kind_t external_kind; SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, child_abspath, child_abspath, TRUE, pool, pool)); if (external_kind != svn_node_file) SVN_ERR(copy_one_versioned_file(child_abspath, target_abspath, ctx, revision, native_eol, ignore_keywords, iterpool)); } } SVN_ERR(svn_wc__node_get_depth(&node_depth, ctx->wc_ctx, from_abspath, pool)); /* Handle externals. */ if (! ignore_externals && depth == svn_depth_infinity && node_depth == svn_depth_infinity) { apr_array_header_t *ext_items; const svn_string_t *prop_val; SVN_ERR(svn_wc_prop_get2(&prop_val, ctx->wc_ctx, from_abspath, SVN_PROP_EXTERNALS, pool, pool)); if (prop_val != NULL) { int i; SVN_ERR(svn_wc_parse_externals_description3(&ext_items, from_abspath, prop_val->data, FALSE, pool)); for (i = 0; i < ext_items->nelts; ++i) { svn_wc_external_item2_t *ext_item; const char *new_from, *new_to; svn_pool_clear(iterpool); ext_item = APR_ARRAY_IDX(ext_items, i, svn_wc_external_item2_t *); new_from = svn_dirent_join(from_abspath, ext_item->target_dir, iterpool); new_to = svn_dirent_join(to_abspath, ext_item->target_dir, iterpool); /* The target dir might have parents that don't exist. Guarantee the path upto the last component. */ if (!svn_dirent_is_root(ext_item->target_dir, strlen(ext_item->target_dir))) { const char *parent = svn_dirent_dirname(new_to, iterpool); SVN_ERR(svn_io_make_dir_recursively(parent, iterpool)); } SVN_ERR(copy_versioned_files(new_from, new_to, revision, force, FALSE, ignore_keywords, svn_depth_infinity, native_eol, ctx, iterpool)); } }
svn_error_t * svn_client__update_internal(svn_revnum_t *result_rev, const char *local_abspath, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t depth_is_sticky, svn_boolean_t ignore_externals, svn_boolean_t allow_unver_obstructions, svn_boolean_t adds_as_modification, svn_boolean_t make_parents, svn_boolean_t innerupdate, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { const char *anchor_abspath, *lockroot_abspath; svn_error_t *err; svn_opt_revision_t peg_revision = *revision; apr_hash_t *conflicted_paths = ctx->conflict_func2 ? apr_hash_make(pool) : NULL; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR_ASSERT(! (innerupdate && make_parents)); if (make_parents) { int i; const char *parent_abspath = local_abspath; apr_array_header_t *missing_parents = apr_array_make(pool, 4, sizeof(const char *)); while (1) { /* Try to lock. If we can't lock because our target (or its parent) isn't a working copy, we'll try to walk up the tree to find a working copy, remembering this path's parent as one we need to flesh out. */ err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx, parent_abspath, !innerupdate, pool, pool); if (!err) break; if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) || svn_dirent_is_root(parent_abspath, strlen(parent_abspath))) return err; svn_error_clear(err); /* Remember the parent of our update target as a missing parent. */ parent_abspath = svn_dirent_dirname(parent_abspath, pool); APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath; } /* Run 'svn up --depth=empty' (effectively) on the missing parents, if any. */ anchor_abspath = lockroot_abspath; for (i = missing_parents->nelts - 1; i >= 0; i--) { const char *missing_parent = APR_ARRAY_IDX(missing_parents, i, const char *); err = update_internal(result_rev, conflicted_paths, missing_parent, anchor_abspath, &peg_revision, svn_depth_empty, FALSE, ignore_externals, allow_unver_obstructions, adds_as_modification, timestamp_sleep, FALSE, ctx, pool); if (err) goto cleanup; anchor_abspath = missing_parent; /* If we successfully updated a missing parent, let's re-use the returned revision number for future updates for the sake of consistency. */ peg_revision.kind = svn_opt_revision_number; peg_revision.value.number = *result_rev; } } else {
svn_error_t * svn_path_condense_targets(const char **pcommon, apr_array_header_t **pcondensed_targets, const apr_array_header_t *targets, svn_boolean_t remove_redundancies, apr_pool_t *pool) { int i, j, num_condensed = targets->nelts; svn_boolean_t *removed; apr_array_header_t *abs_targets; int basedir_len; /* Early exit when there's no data to work on. */ if (targets->nelts <= 0) { *pcommon = NULL; if (pcondensed_targets) *pcondensed_targets = NULL; return SVN_NO_ERROR; } /* Get the absolute path of the first target. */ SVN_ERR(svn_path_get_absolute(pcommon, APR_ARRAY_IDX(targets, 0, const char *), pool)); /* Early exit when there's only one path to work on. */ if (targets->nelts == 1) { if (pcondensed_targets) *pcondensed_targets = apr_array_make(pool, 0, sizeof(const char *)); return SVN_NO_ERROR; } /* Copy the targets array, but with absolute paths instead of relative. Also, find the pcommon argument by finding what is common in all of the absolute paths. NOTE: This is not as efficient as it could be. The calculation of the basedir could be done in the loop below, which would save some calls to svn_path_get_longest_ancestor. I decided to do it this way because I thought it would be simpler, since this way, we don't even do the loop if we don't need to condense the targets. */ removed = apr_pcalloc(pool, (targets->nelts * sizeof(svn_boolean_t))); abs_targets = apr_array_make(pool, targets->nelts, sizeof(const char *)); APR_ARRAY_PUSH(abs_targets, const char *) = *pcommon; for (i = 1; i < targets->nelts; ++i) { const char *rel = APR_ARRAY_IDX(targets, i, const char *); const char *absolute; SVN_ERR(svn_path_get_absolute(&absolute, rel, pool)); APR_ARRAY_PUSH(abs_targets, const char *) = absolute; *pcommon = svn_path_get_longest_ancestor(*pcommon, absolute, pool); } if (pcondensed_targets != NULL) { if (remove_redundancies) { /* Find the common part of each pair of targets. If common part is equal to one of the paths, the other is a child of it, and can be removed. If a target is equal to *pcommon, it can also be removed. */ /* First pass: when one non-removed target is a child of another non-removed target, remove the child. */ for (i = 0; i < abs_targets->nelts; ++i) { if (removed[i]) continue; for (j = i + 1; j < abs_targets->nelts; ++j) { const char *abs_targets_i; const char *abs_targets_j; const char *ancestor; if (removed[j]) continue; abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *); abs_targets_j = APR_ARRAY_IDX(abs_targets, j, const char *); ancestor = svn_path_get_longest_ancestor (abs_targets_i, abs_targets_j, pool); if (*ancestor == '\0') continue; if (strcmp(ancestor, abs_targets_i) == 0) { removed[j] = TRUE; num_condensed--; } else if (strcmp(ancestor, abs_targets_j) == 0) { removed[i] = TRUE; num_condensed--; } } } /* Second pass: when a target is the same as *pcommon, remove the target. */ for (i = 0; i < abs_targets->nelts; ++i) { const char *abs_targets_i = APR_ARRAY_IDX(abs_targets, i, const char *); if ((strcmp(abs_targets_i, *pcommon) == 0) && (! removed[i])) { removed[i] = TRUE; num_condensed--; } } } /* Now create the return array, and copy the non-removed items */ basedir_len = strlen(*pcommon); *pcondensed_targets = apr_array_make(pool, num_condensed, sizeof(const char *)); for (i = 0; i < abs_targets->nelts; ++i) { const char *rel_item = APR_ARRAY_IDX(abs_targets, i, const char *); /* Skip this if it's been removed. */ if (removed[i]) continue; /* If a common prefix was found, condensed_targets are given relative to that prefix. */ if (basedir_len > 0) { /* Only advance our pointer past a path separator if REL_ITEM isn't the same as *PCOMMON. If *PCOMMON is a root path, basedir_len will already include the closing '/', so never advance the pointer here. */ rel_item += basedir_len; if (rel_item[0] && ! svn_dirent_is_root(*pcommon, basedir_len)) rel_item++; } APR_ARRAY_PUSH(*pcondensed_targets, const char *) = apr_pstrdup(pool, rel_item); } } return SVN_NO_ERROR; }
svn_error_t * svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, const char **local_relpath, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *local_dir_abspath; const char *original_abspath = local_abspath; svn_node_kind_t kind; const char *build_relpath; svn_wc__db_wcroot_t *probe_wcroot; svn_wc__db_wcroot_t *found_wcroot = NULL; const char *scan_abspath; svn_sqlite__db_t *sdb = NULL; svn_boolean_t moved_upwards = FALSE; svn_boolean_t always_check = FALSE; int wc_format = 0; const char *adm_relpath; /* Non-NULL if WCROOT is found through a symlink: */ const char *symlink_wcroot_abspath = NULL; /* ### we need more logic for finding the database (if it is located ### outside of the wcroot) and then managing all of that within DB. ### for now: play quick & dirty. */ probe_wcroot = svn_hash_gets(db->dir_data, local_abspath); if (probe_wcroot != NULL) { *wcroot = probe_wcroot; /* We got lucky. Just return the thing BEFORE performing any I/O. */ /* ### validate SMODE against how we opened wcroot->sdb? and against ### DB->mode? (will we record per-dir mode?) */ /* ### for most callers, we could pass NULL for result_pool. */ *local_relpath = compute_relpath(probe_wcroot, local_abspath, result_pool); return SVN_NO_ERROR; } /* ### at some point in the future, we may need to find a way to get ### rid of this stat() call. it is going to happen for EVERY call ### into wc_db which references a file. calls for directories could ### get an early-exit in the hash lookup just above. */ SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool)); if (kind != svn_node_dir) { /* If the node specified by the path is NOT present, then it cannot possibly be a directory containing ".svn/wc.db". If it is a file, then it cannot contain ".svn/wc.db". For both of these cases, strip the basename off of the path and move up one level. Keep record of what we strip, though, since we'll need it later to construct local_relpath. */ svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath, scratch_pool); /* Is this directory in our hash? */ probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath); if (probe_wcroot != NULL) { const char *dir_relpath; *wcroot = probe_wcroot; /* Stashed directory's local_relpath + basename. */ dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath, NULL); *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool); return SVN_NO_ERROR; } /* If the requested path is not on the disk, then we don't know how many ancestors need to be scanned until we start hitting content on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries rather than bailing out after the first check. */ if (kind == svn_node_none) always_check = TRUE; /* Start the scanning at LOCAL_DIR_ABSPATH. */ local_abspath = local_dir_abspath; } else { /* Start the local_relpath empty. If *this* directory contains the wc.db, then relpath will be the empty string. */ build_relpath = ""; /* Remember the dir containing LOCAL_ABSPATH (they're the same). */ local_dir_abspath = local_abspath; } /* LOCAL_ABSPATH refers to a directory at this point. At this point, we've determined that an associated WCROOT is NOT in the DB's hash table for this directory. Let's find an existing one in the ancestors, or create one when we find the actual wcroot. */ /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite database in the right place. If we find it... great! If not, then peel off some components, and try again. */ adm_relpath = svn_wc_get_adm_dir(scratch_pool); while (TRUE) { svn_error_t *err; svn_node_kind_t adm_subdir_kind; const char *adm_subdir = svn_dirent_join(local_abspath, adm_relpath, scratch_pool); SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, scratch_pool)); if (adm_subdir_kind == svn_node_dir) { /* We always open the database in read/write mode. If the database isn't writable in the filesystem, SQLite will internally open it as read-only, and we'll get an error if we try to do a write operation. We could decide what to do on a per-operation basis, but since we're caching database handles, it make sense to be as permissive as the filesystem allows. */ err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE, svn_sqlite__mode_readwrite, db->exclusive, db->timeout, NULL, db->state_pool, scratch_pool); if (err == NULL) { #ifdef SVN_DEBUG /* Install self-verification trigger statements. */ err = svn_sqlite__exec_statements(sdb, STMT_VERIFICATION_TRIGGERS); if (err && err->apr_err == SVN_ERR_SQLITE_ERROR) { /* Verification triggers can fail to install on old 1.7-dev * formats which didn't have a NODES table yet. Ignore sqlite * errors so such working copies can be upgraded. */ svn_error_clear(err); } else SVN_ERR(err); #endif break; } if (err->apr_err != SVN_ERR_SQLITE_ERROR && !APR_STATUS_IS_ENOENT(err->apr_err)) return svn_error_trace(err); svn_error_clear(err); /* If we have not moved upwards, then check for a wc-1 working copy. Since wc-1 has a .svn in every directory, and we didn't find one in the original directory, then we aren't looking at a wc-1. If the original path is not present, then we have to check on every iteration. The content may be the immediate parent, or possibly five ancetors higher. We don't test for directory presence (just for the presence of subdirs/files), so we don't know when we can stop checking ... so just check always. */ if (!moved_upwards || always_check) { SVN_ERR(get_old_version(&wc_format, local_abspath, scratch_pool)); if (wc_format != 0) break; } } /* We couldn't open the SDB within the specified directory, so move up one more directory. */ if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) { /* Hit the root without finding a wcroot. */ /* The wcroot could be a symlink to a directory. * (Issue #2557, #3987). If so, try again, this time scanning * for a db within the directory the symlink points to, * rather than within the symlink's parent directory. */ if (kind == svn_node_symlink) { svn_node_kind_t resolved_kind; local_abspath = original_abspath; SVN_ERR(svn_io_check_resolved_path(local_abspath, &resolved_kind, scratch_pool)); if (resolved_kind == svn_node_dir) { /* Is this directory recorded in our hash? */ found_wcroot = svn_hash_gets(db->dir_data, local_abspath); if (found_wcroot) break; symlink_wcroot_abspath = local_abspath; SVN_ERR(read_link_target(&local_abspath, local_abspath, scratch_pool)); try_symlink_as_dir: kind = svn_node_dir; moved_upwards = FALSE; local_dir_abspath = local_abspath; build_relpath = ""; continue; } } return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' is not a working copy"), svn_dirent_local_style(original_abspath, scratch_pool)); } local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); moved_upwards = TRUE; symlink_wcroot_abspath = NULL; /* Is the parent directory recorded in our hash? */ found_wcroot = svn_hash_gets(db->dir_data, local_abspath); if (found_wcroot != NULL) break; } if (found_wcroot != NULL) { /* We found a hash table entry for an ancestor, so we stopped scanning since all subdirectories use the same WCROOT. */ *wcroot = found_wcroot; } else if (wc_format == 0) { /* We finally found the database. Construct a wcroot_t for it. */ apr_int64_t wc_id; int format; svn_error_t *err; err = fetch_sdb_info(&wc_id, &format, sdb, scratch_pool); if (err) { if (err->apr_err == SVN_ERR_WC_CORRUPT) return svn_error_quick_wrapf( err, _("Missing a row in WCROOT for '%s'."), svn_dirent_local_style(original_abspath, scratch_pool)); return svn_error_trace(err); } /* WCROOT.local_abspath may be NULL when the database is stored inside the wcroot, but we know the abspath is this directory (ie. where we found it). */ err = svn_wc__db_pdh_create_wcroot(wcroot, apr_pstrdup(db->state_pool, symlink_wcroot_abspath ? symlink_wcroot_abspath : local_abspath), sdb, wc_id, format, db->verify_format, db->state_pool, scratch_pool); if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT || err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) && kind == svn_node_symlink) { /* We found an unsupported WC after traversing upwards from a * symlink. Fall through to code below to check if the symlink * points at a supported WC. */ svn_error_clear(err); *wcroot = NULL; } else if (err) { /* Close handle if we are not going to use it to support upgrading with exclusive wc locking. */ return svn_error_compose_create(err, svn_sqlite__close(sdb)); } } else { /* We found something that looks like a wc-1 working copy directory. However, if the format version is 12 and the .svn/entries file is only 3 bytes long, then it's a breadcrumb in a wc-ng working copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */ if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */) { apr_finfo_t info; /* Check attributes of .svn/entries */ const char *admin_abspath = svn_wc__adm_child( local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool); svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE, scratch_pool); /* If the former does not succeed, something is seriously wrong. */ if (err) return svn_error_createf( SVN_ERR_WC_CORRUPT, err, _("The working copy at '%s' is corrupt."), svn_dirent_local_style(local_abspath, scratch_pool)); svn_error_clear(err); if (3 == info.size) { /* Check existence of .svn/wc.db */ admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE, scratch_pool); err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE, scratch_pool); if (err && APR_STATUS_IS_ENOENT(err->apr_err)) { svn_error_clear(err); return svn_error_createf( SVN_ERR_WC_CORRUPT, NULL, _("The working copy database at '%s' is missing."), svn_dirent_local_style(local_abspath, scratch_pool)); } else /* We should never have reached this point in the code if .svn/wc.db exists; therefore it's best to assume it's corrupt. */ return svn_error_createf( SVN_ERR_WC_CORRUPT, err, _("The working copy database at '%s' is corrupt."), svn_dirent_local_style(local_abspath, scratch_pool)); } } SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot, apr_pstrdup(db->state_pool, symlink_wcroot_abspath ? symlink_wcroot_abspath : local_abspath), NULL, UNKNOWN_WC_ID, wc_format, db->verify_format, db->state_pool, scratch_pool)); } if (*wcroot) { const char *dir_relpath; if (symlink_wcroot_abspath) { /* The WCROOT was found through a symlink pointing at the root of * the WC. Cache the WCROOT under the symlink's path. */ local_dir_abspath = symlink_wcroot_abspath; } /* The subdirectory's relpath is easily computed relative to the wcroot that we just found. */ dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL); /* And the result local_relpath may include a filename. */ *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool); } if (kind == svn_node_symlink) { svn_boolean_t retry_if_dir = FALSE; svn_wc__db_status_t status; svn_boolean_t conflicted; svn_error_t *err; /* Check if the symlink is versioned or obstructs a versioned node * in this DB -- in that case, use this wcroot. Else, if the symlink * points to a directory, try to find a wcroot in that directory * instead. */ if (*wcroot) { err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, NULL, NULL, NULL, NULL, NULL, NULL, *wcroot, *local_relpath, scratch_pool, scratch_pool); if (err) { if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err)) return svn_error_trace(err); svn_error_clear(err); retry_if_dir = TRUE; /* The symlink is unversioned. */ } else { /* The symlink is versioned, or obstructs a versioned node. * Ignore non-conflicted not-present/excluded nodes. * This allows the symlink to redirect the wcroot query to a * directory, regardless of 'invisible' nodes in this WC. */ retry_if_dir = ((status == svn_wc__db_status_not_present || status == svn_wc__db_status_excluded || status == svn_wc__db_status_server_excluded) && !conflicted); } } else retry_if_dir = TRUE; if (retry_if_dir) { svn_node_kind_t resolved_kind; SVN_ERR(svn_io_check_resolved_path(original_abspath, &resolved_kind, scratch_pool)); if (resolved_kind == svn_node_dir) { symlink_wcroot_abspath = original_abspath; SVN_ERR(read_link_target(&local_abspath, original_abspath, scratch_pool)); /* This handle was opened in this function but is not going to be used further so close it. */ if (sdb) SVN_ERR(svn_sqlite__close(sdb)); goto try_symlink_as_dir; } } } /* We've found the appropriate WCROOT for the requested path. Stash it into that path's directory. */ svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, local_dir_abspath), *wcroot); /* Did we traverse up to parent directories? */ if (!moved_upwards) { /* We did NOT move to a parent of the original requested directory. We've constructed and filled in a WCROOT for the request, so we are done. */ return SVN_NO_ERROR; } /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally passed into this function. We stepped *at least* one directory above that. We should now associate the WROOT for each parent directory that does not (yet) have one. */ scan_abspath = local_dir_abspath; do { const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool); svn_wc__db_wcroot_t *parent_wcroot; parent_wcroot = svn_hash_gets(db->dir_data, parent_dir); if (parent_wcroot == NULL) { svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir), *wcroot); } /* Move up a directory, stopping when we reach the directory where we found/built the WCROOT. */ scan_abspath = parent_dir; } while (strcmp(scan_abspath, local_abspath) != 0); return SVN_NO_ERROR; }