/* The main logic of the public svn_client_add4. * * EXISTING_PARENT_ABSPATH is the absolute path to the first existing * parent directory of local_abspath. If not NULL, all missing parents * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */ static svn_error_t * add(const char *local_abspath, svn_depth_t depth, svn_boolean_t force, svn_boolean_t no_ignore, const char *existing_parent_abspath, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_node_kind_t kind; svn_error_t *err; svn_magic__cookie_t *magic_cookie; svn_magic__init(&magic_cookie, scratch_pool); if (existing_parent_abspath) { const char *parent_abspath; const char *child_relpath; apr_array_header_t *components; int i; apr_pool_t *iterpool; parent_abspath = existing_parent_abspath; child_relpath = svn_dirent_is_child(existing_parent_abspath, local_abspath, NULL); components = svn_path_decompose(child_relpath, scratch_pool); iterpool = svn_pool_create(scratch_pool); for (i = 0; i < components->nelts - 1; i++) { const char *component; svn_node_kind_t disk_kind; svn_pool_clear(iterpool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); component = APR_ARRAY_IDX(components, i, const char *); parent_abspath = svn_dirent_join(parent_abspath, component, scratch_pool); SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool)); if (disk_kind != svn_node_none && disk_kind != svn_node_dir) return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, _("'%s' prevents creating parent of '%s'"), parent_abspath, local_abspath); SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool)); SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, parent_abspath, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); } svn_pool_destroy(iterpool); } SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); if (kind == svn_node_dir) { /* We use add_dir_recursive for all directory targets and pass depth along no matter what it is, so that the target's depth will be set correctly. */ err = add_dir_recursive(local_abspath, depth, force, no_ignore, magic_cookie, ctx, scratch_pool); } else if (kind == svn_node_file) err = add_file(local_abspath, magic_cookie, ctx, scratch_pool); else if (kind == svn_node_none) { svn_boolean_t tree_conflicted; /* Provide a meaningful error message if the node does not exist * on disk but is a tree conflict victim. */ err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, ctx->wc_ctx, local_abspath, scratch_pool); if (err) svn_error_clear(err); else if (tree_conflicted) return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, _("'%s' is an existing item in conflict; " "please mark the conflict as resolved " "before adding a new item here"), svn_dirent_local_style(local_abspath, scratch_pool)); return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("'%s' not found"), svn_dirent_local_style(local_abspath, scratch_pool)); } else return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Unsupported node kind for path '%s'"), svn_dirent_local_style(local_abspath, scratch_pool)); /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set. */ if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force) { svn_error_clear(err); err = SVN_NO_ERROR; } return svn_error_trace(err); }
/* This is a helper for svn_client__update_internal(), which see for an explanation of most of these parameters. Some stuff that's unique is as follows: ANCHOR_ABSPATH is the local absolute path of the update anchor. This is typically either the same as LOCAL_ABSPATH, or the immediate parent of LOCAL_ABSPATH. If NOTIFY_SUMMARY is set (and there's a notification handler in CTX), transmit the final update summary upon successful completion of the update. Add the paths of any conflict victims to CONFLICTED_PATHS, if that is not null. */ static svn_error_t * update_internal(svn_revnum_t *result_rev, apr_hash_t *conflicted_paths, const char *local_abspath, const char *anchor_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 *timestamp_sleep, svn_boolean_t notify_summary, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_delta_editor_t *update_editor; void *update_edit_baton; const svn_ra_reporter3_t *reporter; void *report_baton; const char *corrected_url; const char *target; const char *repos_root_url; const char *repos_relpath; const char *repos_uuid; const char *anchor_url; svn_revnum_t revnum; svn_boolean_t use_commit_times; svn_boolean_t clean_checkout = FALSE; const char *diff3_cmd; apr_hash_t *wcroot_iprops; svn_opt_revision_t opt_rev; svn_ra_session_t *ra_session; const char *preserved_exts_str; apr_array_header_t *preserved_exts; struct svn_client__dirent_fetcher_baton_t dfb; svn_boolean_t server_supports_depth; svn_boolean_t cropping_target; svn_boolean_t target_conflicted = FALSE; svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; if (result_rev) *result_rev = SVN_INVALID_REVNUM; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; /* Check if our anchor exists in BASE. If it doesn't we can't update. */ SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url, &repos_uuid, NULL, ctx->wc_ctx, anchor_abspath, TRUE, FALSE, pool, pool)); /* It does not make sense to update conflict victims. */ if (repos_relpath) { svn_error_t *err; svn_boolean_t text_conflicted, prop_conflicted; anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath, pool); err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, NULL, ctx->wc_ctx, local_abspath, pool); if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) return svn_error_trace(err); svn_error_clear(err); /* tree-conflicts are handled by the update editor */ if (!err && (text_conflicted || prop_conflicted)) target_conflicted = TRUE; } else anchor_url = NULL; if (! anchor_url || target_conflicted) { if (ctx->notify_func2) { svn_wc_notify_t *nt; nt = svn_wc_create_notify(local_abspath, target_conflicted ? svn_wc_notify_skip_conflicted : svn_wc_notify_update_skip_working_only, pool); ctx->notify_func2(ctx->notify_baton2, nt, pool); } return SVN_NO_ERROR; } /* We may need to crop the tree if the depth is sticky */ cropping_target = (depth_is_sticky && depth < svn_depth_infinity); if (cropping_target) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath, TRUE, TRUE, pool)); if (target_kind == svn_node_dir) { SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } } /* check whether the "clean c/o" optimization is applicable */ SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool)); /* 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, pool)); /* 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)); /* 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, pool) : NULL; /* Let everyone know we're starting a real update (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started, 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; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* Open an RA session for the URL */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, anchor_url, anchor_abspath, NULL, TRUE, TRUE, ctx, pool, pool)); /* If we got a corrected URL from the RA subsystem, we'll need to relocate our working copy first. */ if (corrected_url) { const char *new_repos_root_url; /* To relocate everything inside our repository we need the old and new repos root. */ SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool)); /* svn_client_relocate2() will check the uuid */ SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url, new_repos_root_url, ignore_externals, ctx, pool)); /* Store updated repository root for externals */ repos_root_url = new_repos_root_url; /* ### We should update anchor_loc->repos_uuid too, although currently * we don't use it. */ anchor_url = corrected_url; } /* Resolve unspecified REVISION now, because we need to retrieve the correct inherited props prior to the editor drive and we need to use the same value of HEAD for both. */ opt_rev.kind = revision->kind; opt_rev.value = revision->value; if (opt_rev.kind == svn_opt_revision_unspecified) opt_rev.kind = svn_opt_revision_head; /* ### todo: shouldn't svn_client__get_revision_number be able to take a URL as easily as a local path? */ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, local_abspath, ra_session, &opt_rev, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; dfb.target_revision = revnum; dfb.anchor_url = anchor_url; SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath, revnum, depth, ra_session, ctx, pool, pool)); /* Fetch the update editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision later on. */ SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, wcroot_iprops, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, adds_as_modification, server_supports_depth, clean_checkout, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, conflicted_paths ? record_conflict : NULL, conflicted_paths, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, 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_update3(ra_session, &reporter, &report_baton, revnum, target, (!server_supports_depth || depth_is_sticky ? depth : svn_depth_unknown), FALSE /* send_copyfrom_args */, FALSE /* ignore_ancestry */, update_editor, update_edit_baton, pool, pool)); /* Past this point, we assume the WC is going to be modified so we will * need to sleep for timestamps. */ *timestamp_sleep = TRUE; /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. */ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* We handle externals after the update is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, repos_root_url, local_abspath, depth, timestamp_sleep, ctx, pool)); } /* Let everyone know we're finished here (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, 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, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
svn_error_t * svn_client__create_status(svn_client_status_t **cst, svn_wc_context_t *wc_ctx, const char *local_abspath, const svn_wc_status3_t *status, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { *cst = apr_pcalloc(result_pool, sizeof(**cst)); (*cst)->kind = status->kind; (*cst)->local_abspath = local_abspath; (*cst)->filesize = status->filesize; (*cst)->versioned = status->versioned; (*cst)->conflicted = status->conflicted; (*cst)->node_status = status->node_status; (*cst)->text_status = status->text_status; (*cst)->prop_status = status->prop_status; if (status->kind == svn_node_dir) (*cst)->wc_is_locked = status->locked; (*cst)->copied = status->copied; (*cst)->revision = status->revision; (*cst)->changed_rev = status->changed_rev; (*cst)->changed_date = status->changed_date; (*cst)->changed_author = status->changed_author; (*cst)->repos_root_url = status->repos_root_url; (*cst)->repos_uuid = status->repos_uuid; (*cst)->repos_relpath = status->repos_relpath; (*cst)->switched = status->switched; (*cst)->file_external = status->file_external; if (status->file_external) { (*cst)->switched = FALSE; } (*cst)->lock = status->lock; (*cst)->changelist = status->changelist; (*cst)->depth = status->depth; /* Out of date information */ (*cst)->ood_kind = status->ood_kind; (*cst)->repos_node_status = status->repos_node_status; (*cst)->repos_text_status = status->repos_text_status; (*cst)->repos_prop_status = status->repos_prop_status; (*cst)->repos_lock = status->repos_lock; (*cst)->ood_changed_rev = status->ood_changed_rev; (*cst)->ood_changed_date = status->ood_changed_date; (*cst)->ood_changed_author = status->ood_changed_author; /* When changing the value of backwards_compatibility_baton, also change its use in status4_wrapper_func in deprecated.c */ (*cst)->backwards_compatibility_baton = status; if (status->versioned && status->conflicted) { svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; /* Note: This checks the on disk markers to automatically hide text/property conflicts that are hidden by removing their markers */ SVN_ERR(svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, &tree_conflicted, wc_ctx, local_abspath, scratch_pool)); if (text_conflicted) (*cst)->text_status = svn_wc_status_conflicted; if (prop_conflicted) (*cst)->prop_status = svn_wc_status_conflicted; /* ### Also set this for tree_conflicts? */ if (text_conflicted || prop_conflicted) (*cst)->node_status = svn_wc_status_conflicted; } (*cst)->moved_from_abspath = status->moved_from_abspath; (*cst)->moved_to_abspath = status->moved_to_abspath; return SVN_NO_ERROR; }
svn_error_t * svn_cl__print_status_xml(const char *cwd_abspath, const char *path, const svn_client_status_t *status, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); apr_hash_t *att_hash; const char *local_abspath = status->local_abspath; svn_boolean_t tree_conflicted = FALSE; if (status->node_status == svn_wc_status_none && status->repos_node_status == svn_wc_status_none) return SVN_NO_ERROR; if (status->conflicted) SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, ctx->wc_ctx, local_abspath, pool)); path = make_relpath(cwd_abspath, path, pool, pool); svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", "path", svn_dirent_local_style(path, pool), NULL); att_hash = apr_hash_make(pool); svn_hash_sets(att_hash, "item", generate_status_desc(combined_status(status))); svn_hash_sets(att_hash, "props", generate_status_desc( (status->node_status != svn_wc_status_deleted) ? status->prop_status : svn_wc_status_none)); if (status->wc_is_locked) svn_hash_sets(att_hash, "wc-locked", "true"); if (status->copied) svn_hash_sets(att_hash, "copied", "true"); if (status->switched) svn_hash_sets(att_hash, "switched", "true"); if (status->file_external) svn_hash_sets(att_hash, "file-external", "true"); if (status->versioned && ! status->copied) svn_hash_sets(att_hash, "revision", apr_psprintf(pool, "%ld", status->revision)); if (tree_conflicted) svn_hash_sets(att_hash, "tree-conflicted", "true"); if (status->moved_from_abspath || status->moved_to_abspath) { const char *relpath; if (status->moved_from_abspath) { relpath = make_relpath(cwd_abspath, status->moved_from_abspath, pool, pool); relpath = svn_dirent_local_style(relpath, pool); svn_hash_sets(att_hash, "moved-from", relpath); } if (status->moved_to_abspath) { relpath = make_relpath(cwd_abspath, status->moved_to_abspath, pool, pool); relpath = svn_dirent_local_style(relpath, pool); svn_hash_sets(att_hash, "moved-to", relpath); } } svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status", att_hash); if (SVN_IS_VALID_REVNUM(status->changed_rev)) { svn_cl__print_xml_commit(&sb, status->changed_rev, status->changed_author, svn_time_to_cstring(status->changed_date, pool), pool); } if (status->lock) svn_cl__print_xml_lock(&sb, status->lock, pool); svn_xml_make_close_tag(&sb, pool, "wc-status"); if (status->repos_node_status != svn_wc_status_none || status->repos_lock) { svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status", "item", generate_status_desc(combined_repos_status(status)), "props", generate_status_desc(status->repos_prop_status), NULL); if (status->repos_lock) svn_cl__print_xml_lock(&sb, status->repos_lock, pool); svn_xml_make_close_tag(&sb, pool, "repos-status"); } svn_xml_make_close_tag(&sb, pool, "entry"); return svn_cl__error_checked_fputs(sb->data, stdout); }
/* Print STATUS and PATH in a format determined by DETAILED and SHOW_LAST_COMMITTED. */ static svn_error_t * print_status(const char *cwd_abspath, const char *path, svn_boolean_t detailed, svn_boolean_t show_last_committed, svn_boolean_t repos_locks, const svn_client_status_t *status, unsigned int *text_conflicts, unsigned int *prop_conflicts, unsigned int *tree_conflicts, svn_client_ctx_t *ctx, apr_pool_t *pool) { enum svn_wc_status_kind node_status = status->node_status; enum svn_wc_status_kind prop_status = status->prop_status; char tree_status_code = ' '; const char *tree_desc_line = ""; const char *moved_from_line = ""; const char *moved_to_line = ""; path = make_relpath(cwd_abspath, path, pool, pool); /* For historic reasons svn ignores the property status for added nodes, even if these nodes were copied and have local property changes. Note that it doesn't do this on replacements, or children of copies. ### Our test suite would catch more errors if we reported property changes on copies. */ if (node_status == svn_wc_status_added) prop_status = svn_wc_status_none; /* To indicate this node is the victim of a tree conflict, we show 'C' in the tree-conflict column, overriding any other status. We also print a separate line describing the nature of the tree conflict. */ if (status->conflicted) { const char *desc; const char *local_abspath = status->local_abspath; svn_boolean_t text_conflicted; svn_boolean_t prop_conflicted; svn_boolean_t tree_conflicted; if (status->versioned) { svn_error_t *err; err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, &tree_conflicted, ctx->wc_ctx, local_abspath, pool); if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) { svn_error_clear(err); text_conflicted = FALSE; prop_conflicted = FALSE; tree_conflicted = FALSE; } else SVN_ERR(err); } else { text_conflicted = FALSE; prop_conflicted = FALSE; tree_conflicted = TRUE; } if (tree_conflicted) { const svn_wc_conflict_description2_t *tree_conflict; SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx, local_abspath, pool, pool)); SVN_ERR_ASSERT(tree_conflict != NULL); tree_status_code = 'C'; SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( &desc, tree_conflict, pool)); tree_desc_line = apr_psprintf(pool, "\n > %s", desc); (*tree_conflicts)++; } else if (text_conflicted) (*text_conflicts)++; else if (prop_conflicted) (*prop_conflicts)++; } /* Note that moved-from and moved-to information is only available in STATUS * for (op-)roots of a move. Those are exactly the nodes we want to show * move info for in 'svn status'. See also comments in svn_wc_status3_t. */ if (status->moved_from_abspath && status->moved_to_abspath && strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0) { const char *relpath; relpath = make_relpath(cwd_abspath, status->moved_from_abspath, pool, pool); relpath = svn_dirent_local_style(relpath, pool); moved_from_line = apr_pstrcat(pool, "\n > ", apr_psprintf(pool, _("swapped places with %s"), relpath), (char *)NULL); } else if (status->moved_from_abspath || status->moved_to_abspath) { const char *relpath; if (status->moved_from_abspath) { relpath = make_relpath(cwd_abspath, status->moved_from_abspath, pool, pool); relpath = svn_dirent_local_style(relpath, pool); moved_from_line = apr_pstrcat(pool, "\n > ", apr_psprintf(pool, _("moved from %s"), relpath), (char *)NULL); } if (status->moved_to_abspath) { relpath = make_relpath(cwd_abspath, status->moved_to_abspath, pool, pool); relpath = svn_dirent_local_style(relpath, pool); moved_to_line = apr_pstrcat(pool, "\n > ", apr_psprintf(pool, _("moved to %s"), relpath), (char *)NULL); } } if (detailed) { char ood_status, lock_status; const char *working_rev; if (! status->versioned) working_rev = ""; else if (status->copied || ! SVN_IS_VALID_REVNUM(status->revision)) working_rev = "-"; else working_rev = apr_psprintf(pool, "%ld", status->revision); if (status->repos_node_status != svn_wc_status_none) ood_status = '*'; else ood_status = ' '; if (repos_locks) { if (status->repos_lock) { if (status->lock) { if (strcmp(status->repos_lock->token, status->lock->token) == 0) lock_status = 'K'; else lock_status = 'T'; } else lock_status = 'O'; } else if (status->lock) lock_status = 'B'; else lock_status = ' '; } else lock_status = (status->lock) ? 'K' : ' '; if (show_last_committed) { const char *commit_rev; const char *commit_author; if (SVN_IS_VALID_REVNUM(status->changed_rev)) commit_rev = apr_psprintf(pool, "%ld", status->changed_rev); else if (status->versioned) commit_rev = " ? "; else commit_rev = ""; if (status->changed_author) commit_author = status->changed_author; else if (status->versioned) commit_author = " ? "; else commit_author = ""; SVN_ERR (svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n", generate_status_code(combined_status(status)), generate_status_code(prop_status), status->wc_is_locked ? 'L' : ' ', status->copied ? '+' : ' ', generate_switch_column_code(status), lock_status, tree_status_code, ood_status, working_rev, commit_rev, commit_author, path, moved_to_line, moved_from_line, tree_desc_line)); } else SVN_ERR( svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n", generate_status_code(combined_status(status)), generate_status_code(prop_status), status->wc_is_locked ? 'L' : ' ', status->copied ? '+' : ' ', generate_switch_column_code(status), lock_status, tree_status_code, ood_status, working_rev, path, moved_to_line, moved_from_line, tree_desc_line)); } else SVN_ERR( svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n", generate_status_code(combined_status(status)), generate_status_code(prop_status), status->wc_is_locked ? 'L' : ' ', status->copied ? '+' : ' ', generate_switch_column_code(status), ((status->lock) ? 'K' : ' '), tree_status_code, path, moved_to_line, moved_from_line, tree_desc_line)); return svn_cmdline_fflush(stdout); }
/* This is a helper for svn_client__update_internal(), which see for an explanation of most of these parameters. Some stuff that's unique is as follows: ANCHOR_ABSPATH is the local absolute path of the update anchor. This is typically either the same as LOCAL_ABSPATH, or the immediate parent of LOCAL_ABSPATH. If NOTIFY_SUMMARY is set (and there's a notification handler in CTX), transmit the final update summary upon successful completion of the update. */ static svn_error_t * update_internal(svn_revnum_t *result_rev, const char *local_abspath, const char *anchor_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 *timestamp_sleep, svn_boolean_t notify_summary, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_delta_editor_t *update_editor; void *update_edit_baton; const svn_ra_reporter3_t *reporter; void *report_baton; const char *anchor_url; const char *corrected_url; const char *target; const char *repos_root; svn_error_t *err; svn_revnum_t revnum; svn_boolean_t use_commit_times; svn_boolean_t sleep_here = FALSE; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; svn_boolean_t clean_checkout = FALSE; const char *diff3_cmd; svn_ra_session_t *ra_session; const char *preserved_exts_str; apr_array_header_t *preserved_exts; struct svn_client__dirent_fetcher_baton_t dfb; svn_boolean_t server_supports_depth; svn_boolean_t tree_conflicted; svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; /* Get full URL from the ANCHOR. */ SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, pool, pool)); if (! anchor_url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("'%s' has no URL"), svn_dirent_local_style(anchor_abspath, pool)); /* Check if our anchor exists in BASE. If it doesn't we can't update. ### For performance reasons this should be handled with the same query ### as retrieving the anchor url. */ SVN_ERR(svn_wc__node_get_base_rev(&revnum, ctx->wc_ctx, anchor_abspath, pool)); /* It does not make sense to update tree-conflict victims. */ err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, ctx->wc_ctx, local_abspath, pool); if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); tree_conflicted = FALSE; } else SVN_ERR(err); if (!SVN_IS_VALID_REVNUM(revnum) || tree_conflicted) { if (ctx->notify_func2) { svn_wc_notify_t *nt; nt = svn_wc_create_notify(local_abspath, tree_conflicted ? svn_wc_notify_skip_conflicted : svn_wc_notify_update_skip_working_only, pool); ctx->notify_func2(ctx->notify_baton2, nt, pool); } return SVN_NO_ERROR; } /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, local_abspath, TRUE, pool)); if (target_kind == svn_node_dir) { SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } } /* check whether the "clean c/o" optimization is applicable */ SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool)); /* 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, pool)); /* 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)); /* 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, pool) : NULL; /* Let everyone know we're starting a real update (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started, 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; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* Open an RA session for the URL */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, anchor_url, anchor_abspath, NULL, TRUE, TRUE, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); /* If we got a corrected URL from the RA subsystem, we'll need to relocate our working copy first. */ if (corrected_url) { const char *current_repos_root; const char *current_uuid; /* To relocate everything inside our repository we need the old and new repos root. ### And we should only perform relocates on the wcroot */ SVN_ERR(svn_wc__node_get_repos_info(¤t_repos_root, ¤t_uuid, ctx->wc_ctx, anchor_abspath, pool, pool)); /* ### Check uuid here before calling relocate? */ SVN_ERR(svn_client_relocate2(anchor_abspath, current_repos_root, repos_root, ignore_externals, ctx, pool)); anchor_url = corrected_url; } /* ### todo: shouldn't svn_client__get_revision_number be able to take a URL as easily as a local path? */ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, local_abspath, ra_session, revision, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; dfb.target_revision = revnum; dfb.anchor_url = anchor_url; /* Fetch the update editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision later on. */ SVN_ERR(svn_wc_get_update_editor4(&update_editor, &update_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, adds_as_modification, server_supports_depth, clean_checkout, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, ctx->conflict_func2, ctx->conflict_baton2, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, 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_update2(ra_session, &reporter, &report_baton, revnum, target, (!server_supports_depth || depth_is_sticky ? depth : svn_depth_unknown), FALSE, update_editor, update_edit_baton, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. */ err = svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool); if (err) { /* Don't rely on the error handling to handle the sleep later, do it now */ svn_io_sleep_for_timestamps(local_abspath, pool); return svn_error_trace(err); } *use_sleep = TRUE; /* We handle externals after the update is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, repos_root, local_abspath, depth, use_sleep, ctx, pool)); } if (sleep_here) svn_io_sleep_for_timestamps(local_abspath, pool); /* Let everyone know we're finished here (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, 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, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }