/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself, but create it marked with skip+skip_children. */ static svn_error_t * ensure_state(struct diff_baton *eb, const char *local_abspath, svn_boolean_t recursive_skip, apr_pool_t *scratch_pool) { struct node_state_t *ns; apr_pool_t *ns_pool; if (!eb->cur) { if (!svn_dirent_is_ancestor(eb->anchor_abspath, local_abspath)) return SVN_NO_ERROR; SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), FALSE, scratch_pool)); } else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL)) SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), FALSE, scratch_pool)); else return SVN_NO_ERROR; if (eb->cur && eb->cur->skip_children) return SVN_NO_ERROR; ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); ns = apr_pcalloc(ns_pool, sizeof(*ns)); ns->pool = ns_pool; ns->local_abspath = apr_pstrdup(ns_pool, local_abspath); ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath); ns->parent = eb->cur; eb->cur = ns; if (recursive_skip) { ns->skip = TRUE; ns->skip_children = TRUE; return SVN_NO_ERROR; } { svn_revnum_t revision; svn_error_t *err; err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->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); revision = 0; /* Use original revision? */ } ns->left_src = svn_diff__source_create(revision, ns->pool); ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, &ns->skip_children, ns->relpath, ns->left_src, ns->right_src, NULL /* copyfrom_source */, ns->parent ? ns->parent->baton : NULL, eb->processor, ns->pool, scratch_pool)); } return SVN_NO_ERROR; }
/* Implements svn_wc_status_func3_t */ static svn_error_t * diff_status_callback(void *baton, const char *local_abspath, const svn_wc_status3_t *status, apr_pool_t *scratch_pool) { struct diff_baton *eb = baton; svn_wc__db_t *db = eb->db; if (! status->versioned) return SVN_NO_ERROR; /* unversioned (includes dir externals) */ if (status->node_status == svn_wc_status_conflicted && status->text_status == svn_wc_status_none && status->prop_status == svn_wc_status_none) { /* Node is an actual only node describing a tree conflict */ return SVN_NO_ERROR; } /* Not text/prop modified, not copied. Easy out */ if (status->node_status == svn_wc_status_normal && !status->copied) return SVN_NO_ERROR; /* Mark all directories where we are no longer inside as closed */ while (eb->cur && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) { struct node_state_t *ns = eb->cur; if (!ns->skip) { if (ns->propchanges) SVN_ERR(eb->processor->dir_changed(ns->relpath, ns->left_src, ns->right_src, ns->left_props, ns->right_props, ns->propchanges, ns->baton, eb->processor, ns->pool)); else SVN_ERR(eb->processor->dir_closed(ns->relpath, ns->left_src, ns->right_src, ns->baton, eb->processor, ns->pool)); } eb->cur = ns->parent; svn_pool_clear(ns->pool); } SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), FALSE, scratch_pool)); if (eb->cur && eb->cur->skip_children) return SVN_NO_ERROR; if (eb->changelist_hash != NULL && (!status->changelist || ! svn_hash_gets(eb->changelist_hash, status->changelist))) return SVN_NO_ERROR; /* Filtered via changelist */ /* This code does about the same thing as the inner body of walk_local_nodes_diff() in diff_editor.c, except that it is already filtered by the status walker, doesn't have to account for remote changes (and many tiny other details) */ { svn_boolean_t repos_only; svn_boolean_t local_only; svn_wc__db_status_t db_status; svn_boolean_t have_base; svn_node_kind_t base_kind; svn_node_kind_t db_kind = status->kind; svn_depth_t depth_below_here = svn_depth_unknown; const char *child_abspath = local_abspath; const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); repos_only = FALSE; local_only = FALSE; /* ### optimize away this call using status info. Should be possible in almost every case (except conflict, missing, obst.)*/ SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &have_base, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (!have_base) { local_only = TRUE; /* Only report additions */ } else if (db_status == svn_wc__db_status_normal) { /* Simple diff */ base_kind = db_kind; } else if (db_status == svn_wc__db_status_deleted) { svn_wc__db_status_t base_status; repos_only = TRUE; SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (base_status != svn_wc__db_status_normal) return SVN_NO_ERROR; } else { /* working status is either added or deleted */ svn_wc__db_status_t base_status; SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (base_status != svn_wc__db_status_normal) local_only = TRUE; else if (base_kind != db_kind || !eb->ignore_ancestry) { repos_only = TRUE; local_only = TRUE; } } if (repos_only) { /* Report repository form deleted */ if (base_kind == svn_node_file) SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, eb->processor, eb->cur ? eb->cur->baton : NULL, scratch_pool)); else if (base_kind == svn_node_dir) SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, depth_below_here, eb->processor, eb->cur ? eb->cur->baton : NULL, eb->cancel_func, eb->cancel_baton, scratch_pool)); } else if (!local_only) { /* Diff base against actual */ if (db_kind == svn_node_file) { SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, eb->changelist_hash, eb->processor, eb->cur ? eb->cur->baton : NULL, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); } else if (db_kind == svn_node_dir) { SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); if (status->prop_status != svn_wc_status_none && status->prop_status != svn_wc_status_normal) { apr_array_header_t *propchanges; SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, eb->db, local_abspath, eb->cur->pool, scratch_pool)); SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, eb->db, local_abspath, eb->cur->pool, scratch_pool)); SVN_ERR(svn_prop_diffs(&propchanges, eb->cur->right_props, eb->cur->left_props, eb->cur->pool)); eb->cur->propchanges = propchanges; } } } if (local_only && (db_status != svn_wc__db_status_deleted)) { if (db_kind == svn_node_file) SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, child_relpath, eb->processor, eb->cur ? eb->cur->baton : NULL, eb->changelist_hash, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); else if (db_kind == svn_node_dir) SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, child_relpath, depth_below_here, eb->processor, eb->cur ? eb->cur->baton : NULL, eb->changelist_hash, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); } if (db_kind == svn_node_dir && (local_only || repos_only)) SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); } return SVN_NO_ERROR; }
/* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a access baton that has a write lock. Use SCRATCH_POOL for temporary allocations, and use the client context CTX. */ static svn_error_t * switch_file_external(const char *local_abspath, const char *url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, const char *def_dir_abspath, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; svn_boolean_t use_commit_times; const char *diff3_cmd; const char *preserved_exts_str; const apr_array_header_t *preserved_exts; svn_node_kind_t kind, external_kind; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool) : NULL; { const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, scratch_pool, scratch_pool)); /* File externals can only be installed inside the current working copy. So verify if the working copy that contains/will contain the target is the defining abspath, or one of its ancestors */ if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath)) return svn_error_createf( SVN_ERR_WC_BAD_PATH, NULL, _("Cannot insert a file external defined on '%s' " "into the working copy '%s'."), svn_dirent_local_style(def_dir_abspath, scratch_pool), svn_dirent_local_style(wcroot_abspath, scratch_pool)); } SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, scratch_pool)); SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, local_abspath, local_abspath, TRUE, scratch_pool, scratch_pool)); /* If there is a versioned item with this name, ensure it's a file external before working with it. If there is no entry in the working copy, then create an empty file and add it to the working copy. */ if (kind != svn_node_none && kind != svn_node_unknown) { if (external_kind != svn_node_file) { return svn_error_createf( SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, _("The file external from '%s' cannot overwrite the existing " "versioned item at '%s'"), url, svn_dirent_local_style(local_abspath, scratch_pool)); } } else { svn_node_kind_t disk_kind; SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); if (kind == svn_node_file || kind == svn_node_dir) return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, _("The file external '%s' can not be " "created because the node exists."), svn_dirent_local_style(local_abspath, scratch_pool)); } { const svn_ra_reporter3_t *reporter; void *report_baton; const svn_delta_editor_t *switch_editor; void *switch_baton; svn_client__pathrev_t *switch_loc; svn_revnum_t revnum; apr_array_header_t *inherited_props; const char *dir_abspath; const char *target; svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool); /* ### Why do we open a new session? RA_SESSION is a valid ### session -- the caller used it to call svn_ra_check_path on ### this very URL, the caller also did the resolving and ### reparenting that is repeated here. */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc, url, dir_abspath, peg_revision, revision, ctx, scratch_pool)); /* Get the external file's iprops. */ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", switch_loc->rev, scratch_pool, scratch_pool)); SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool), scratch_pool)); SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton, &revnum, ctx->wc_ctx, local_abspath, def_dir_abspath, switch_loc->url, switch_loc->repos_root_url, switch_loc->repos_uuid, inherited_props, use_commit_times, diff3_cmd, preserved_exts, def_dir_abspath, url, peg_revision, revision, ctx->conflict_func2, ctx->conflict_baton2, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool, scratch_pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, switch_loc->rev, target, svn_depth_unknown, switch_loc->url, FALSE /* send_copyfrom */, TRUE /* ignore_ancestry */, switch_editor, switch_baton, scratch_pool, scratch_pool)); SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, scratch_pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } } return SVN_NO_ERROR; }