/* svn_wc_upgrade_get_repos_info_t implementation for calling svn_wc_upgrade() from svn_client_upgrade() */ static svn_error_t * fetch_repos_info(const char **repos_root, const char **repos_uuid, void *baton, const char *url, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct repos_info_baton *ri = baton; apr_pool_t *subpool; svn_ra_session_t *ra_session; /* The same info is likely to retrieved multiple times (e.g. externals) */ if (ri->last_repos && svn_uri__is_child(ri->last_repos, url, scratch_pool)) { *repos_root = apr_pstrdup(result_pool, ri->last_repos); *repos_uuid = apr_pstrdup(result_pool, ri->last_uuid); return SVN_NO_ERROR; } subpool = svn_pool_create(scratch_pool); SVN_ERR(svn_client_open_ra_session(&ra_session, url, ri->ctx, subpool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool)); SVN_ERR(svn_ra_get_uuid2(ra_session, repos_uuid, result_pool)); /* Store data for further calls */ ri->last_repos = apr_pstrdup(ri->state_pool, *repos_root); ri->last_uuid = apr_pstrdup(ri->state_pool, *repos_uuid); svn_pool_destroy(subpool); return SVN_NO_ERROR; }
svn_error_t * svn_client_get_repos_root(const char **repos_root, const char **repos_uuid, const char *abspath_or_url, svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_ra_session_t *ra_session; /* If PATH_OR_URL is a local path we can fetch the repos root locally. */ if (!svn_path_is_url(abspath_or_url)) { SVN_ERR(svn_wc__node_get_repos_info(repos_root, repos_uuid, ctx->wc_ctx, abspath_or_url, result_pool, scratch_pool)); return SVN_NO_ERROR; } /* If PATH_OR_URL was a URL, we use the RA layer to look it up. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, abspath_or_url, NULL, NULL, FALSE, TRUE, ctx, scratch_pool)); if (repos_root) SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool)); if (repos_uuid) SVN_ERR(svn_ra_get_uuid2(ra_session, repos_uuid, result_pool)); return SVN_NO_ERROR; }
/* Wrapper which populates the repos_root field of the commit_info struct */ static svn_error_t * commit_callback_wrapper(const svn_commit_info_t *commit_info, void *baton, apr_pool_t *pool) { struct ccw_baton *ccwb = baton; svn_commit_info_t *ci = svn_commit_info_dup(commit_info, pool); SVN_ERR(svn_ra_get_repos_root2(ccwb->session, &ci->repos_root, pool)); return ccwb->original_callback(ci, ccwb->original_baton, pool); }
/* Determine if LOCAL_ABSPATH needs an inherited property cache. If it does, then set *NEEDS_CACHE to TRUE, set it to FALSE otherwise. All other args are as per svn_client__get_inheritable_props(). */ static svn_error_t * need_to_cache_iprops(svn_boolean_t *needs_cache, const char *local_abspath, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_boolean_t is_wc_root; svn_boolean_t is_switched; svn_error_t *err; err = svn_wc_check_root(&is_wc_root, &is_switched, NULL, ctx->wc_ctx, local_abspath, scratch_pool); /* LOCAL_ABSPATH doesn't need a cache if it doesn't exist. */ if (err) { if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); is_wc_root = FALSE; is_switched = FALSE; } else { return svn_error_trace(err); } } /* Starting assumption. */ *needs_cache = FALSE; if (is_wc_root || is_switched) { const char *session_url; const char *session_root_url; /* Looks likely that we need an inherited properties cache...Unless LOCAL_ABSPATH is a WC root that points to the repos root. Then it doesn't need a cache because it has nowhere to inherit from. Check for that case. */ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_root_url, scratch_pool)); if (strcmp(session_root_url, session_url) != 0) *needs_cache = TRUE; } return SVN_NO_ERROR; }
svn_error_t *svn_ra_reparent(svn_ra_session_t *session, const char *url, apr_pool_t *pool) { const char *repos_root; /* Make sure the new URL is in the same repository, so that the implementations don't have to do it. */ SVN_ERR(svn_ra_get_repos_root2(session, &repos_root, pool)); if (! svn_uri__is_ancestor(repos_root, url)) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("'%s' isn't in the same repository as '%s'"), url, repos_root); return session->vtable->reparent(session, url, pool); }
svn_error_t * svn_client__pathrev_create_with_session(svn_client__pathrev_t **pathrev_p, svn_ra_session_t *ra_session, svn_revnum_t rev, const char *url, apr_pool_t *result_pool) { svn_client__pathrev_t *pathrev = apr_palloc(result_pool, sizeof(*pathrev)); SVN_ERR_ASSERT(svn_path_is_url(url)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &pathrev->repos_root_url, result_pool)); SVN_ERR(svn_ra_get_uuid2(ra_session, &pathrev->repos_uuid, result_pool)); pathrev->rev = rev; pathrev->url = apr_pstrdup(result_pool, url); *pathrev_p = pathrev; return SVN_NO_ERROR; }
/* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */ static svn_error_t * dump_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool) { opt_baton_t *opt_baton = baton; svn_ra_session_t *extra_ra_session; const char *repos_root; SVN_ERR(svn_client_open_ra_session(&extra_ra_session, opt_baton->url, opt_baton->ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool)); SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool)); return replay_revisions(opt_baton->session, extra_ra_session, opt_baton->url, opt_baton->start_revision.value.number, opt_baton->end_revision.value.number, opt_baton->quiet, opt_baton->incremental, pool); }
/* Implements svn_ra_reporter3_t->finish_report. */ static svn_error_t * reporter_finish_report(void *report_baton, apr_pool_t *pool) { report_baton_t *rb = report_baton; svn_ra_session_t *ras; apr_hash_t *locks; const char *repos_root; apr_pool_t *subpool = svn_pool_create(pool); svn_error_t *err = SVN_NO_ERROR; /* Open an RA session to our common ancestor and grab the locks under it. */ SVN_ERR(svn_client__open_ra_session_internal(&ras, rb->ancestor, NULL, NULL, NULL, FALSE, TRUE, rb->ctx, subpool)); /* The locks need to live throughout the edit. Note that if the server doesn't support lock discovery, we'll just not do locky stuff. */ err = svn_ra_get_locks(ras, &locks, "", rb->pool); if (err && ((err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) || (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))) { svn_error_clear(err); err = SVN_NO_ERROR; locks = apr_hash_make(rb->pool); } SVN_ERR(err); SVN_ERR(svn_ra_get_repos_root2(ras, &repos_root, rb->pool)); /* Close the RA session. */ svn_pool_destroy(subpool); SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks, repos_root, rb->pool)); return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool); }
svn_error_t * svn_client_info3(const char *abspath_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t fetch_excluded, svn_boolean_t fetch_actual_only, const apr_array_header_t *changelists, svn_client_info_receiver2_t receiver, void *receiver_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session, *parent_ra_session; svn_revnum_t rev; const char *url; svn_node_kind_t url_kind; const char *repos_root_URL, *repos_UUID; svn_lock_t *lock; svn_boolean_t related; apr_hash_t *parent_ents; const char *parent_url, *base_name; svn_dirent_t *the_ent; svn_client_info2_t *info; svn_error_t *err; if (depth == svn_depth_unknown) depth = svn_depth_empty; if ((revision == NULL || revision->kind == svn_opt_revision_unspecified) && (peg_revision == NULL || peg_revision->kind == svn_opt_revision_unspecified)) { /* Do all digging in the working copy. */ wc_info_receiver_baton_t b = { receiver, receiver_baton }; return svn_error_trace( svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth, fetch_excluded, fetch_actual_only, changelists, wc_info_receiver, &b, ctx->cancel_func, ctx->cancel_baton, pool)); } /* Go repository digging instead. */ /* Trace rename history (starting at path_or_url@peg_revision) and return RA session to the possibly-renamed URL as it exists in REVISION. The ra_session returned will be anchored on this "final" URL. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &rev, &url, abspath_or_url, NULL, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_URL, pool)); SVN_ERR(svn_ra_get_uuid2(ra_session, &repos_UUID, pool)); svn_uri_split(&parent_url, &base_name, url, pool); /* Get the dirent for the URL itself. */ err = svn_ra_stat(ra_session, "", rev, &the_ent, pool); /* svn_ra_stat() will work against old versions of mod_dav_svn, but not old versions of svnserve. In the case of a pre-1.2 svnserve, catch the specific error it throws:*/ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { /* Fall back to pre-1.2 strategy for fetching dirent's URL. */ svn_error_clear(err); if (strcmp(url, repos_root_URL) == 0) { /* In this universe, there's simply no way to fetch information about the repository's root directory! If we're recursing, degrade gracefully: rather than throw an error, return no information about the repos root. */ if (depth > svn_depth_empty) goto pre_1_2_recurse; /* Otherwise, we really are stuck. Better tell the user what's going on. */ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Server does not support retrieving " "information about the repository root")); } SVN_ERR(svn_ra_check_path(ra_session, "", rev, &url_kind, pool)); if (url_kind == svn_node_none) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); /* Open a new RA session to the item's parent. */ SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session, NULL, parent_url, NULL, NULL, FALSE, TRUE, ctx, pool)); /* Get all parent's entries, and find the item's dirent in the hash. */ SVN_ERR(svn_ra_get_dir2(parent_ra_session, &parent_ents, NULL, NULL, "", rev, DIRENT_FIELDS, pool)); the_ent = apr_hash_get(parent_ents, base_name, APR_HASH_KEY_STRING); if (the_ent == NULL) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); } else if (err) { return svn_error_trace(err); } if (! the_ent) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); /* Check if the URL exists in HEAD and refers to the same resource. In this case, we check the repository for a lock on this URL. ### There is a possible race here, since HEAD might have changed since ### we checked it. A solution to this problem could be to do the below ### check in a loop which only terminates if the HEAD revision is the same ### before and after this check. That could, however, lead to a ### starvation situation instead. */ SVN_ERR(same_resource_in_head(&related, url, rev, ra_session, ctx, pool)); if (related) { err = svn_ra_get_lock(ra_session, &lock, "", pool); /* An old mod_dav_svn will always work; there's nothing wrong with doing a PROPFIND for a property named "DAV:supportedlock". But an old svnserve will error. */ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { svn_error_clear(err); lock = NULL; } else if (err) return svn_error_trace(err); } else lock = NULL; /* Push the URL's dirent (and lock) at the callback.*/ SVN_ERR(build_info_from_dirent(&info, the_ent, lock, url, rev, repos_UUID, repos_root_URL, pool)); SVN_ERR(receiver(receiver_baton, base_name, info, pool)); /* Possibly recurse, using the original RA session. */ if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir)) { apr_hash_t *locks; pre_1_2_recurse: if (peg_revision->kind == svn_opt_revision_head) { err = svn_ra_get_locks2(ra_session, &locks, "", depth, pool); /* Catch specific errors thrown by old mod_dav_svn or svnserve. */ if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)) { svn_error_clear(err); locks = apr_hash_make(pool); /* use an empty hash */ } else if (err) return svn_error_trace(err); } else locks = apr_hash_make(pool); /* use an empty hash */ SVN_ERR(push_dir_info(ra_session, url, "", rev, repos_UUID, repos_root_URL, receiver, receiver_baton, depth, ctx, locks, pool)); } return SVN_NO_ERROR; }
/* 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; }
static int isvn_fetch(struct isvn_client_ctx *ctx) { unsigned i; int rc; isvn_globals_init(); isvn_revmap_init(); isvn_fetch_init(); isvn_brancher_init(); isvn_git_compat_init(); assert_noerr( svn_ra_get_repos_root2(ctx->svn_session, &g_repos_root, ctx->svn_pool), "svn_ra_get_repos_root2"); if (option_verbosity >= 2) printf("SVN root is: %s\n", g_repos_root); isvn_editor_init(); /* 1. Determine g_rev_low/g_rev_high. */ if (option_cloning) g_rev_low = g_rev_lo_avail = 1; else die("low rev ???"); /* Determine. Maybe store as a ?bogus ref */ g_rev_fetchdone = g_rev_commitdone = g_rev_low - 1; g_rev_high = option_maxrev; /* 2. Spawn fetchers. */ for (i = 0; i < g_nr_fetch_workers; i++) { rc = pthread_create(&g_fetch_workers[i], NULL, isvn_fetch_worker, (void *)(uintptr_t)i); if (rc) die("pthread_create: %s(%d)\n", strerror(rc), rc); } /* 3. Spawn bucket workers. */ for (i = 0; i < g_nr_commit_workers; i++) { rc = pthread_create(&g_branch_workers[i], NULL, isvn_bucket_worker, (void *)(uintptr_t)i); if (rc) die("pthread_create: %s(%d)\n", strerror(rc), rc); } /* 4. Wind it down. */ for (i = 0; i < g_nr_fetch_workers; i++) { rc = pthread_join(g_fetch_workers[i], NULL); if (rc) die("pthread_join: %s(%d)\n", strerror(rc), rc); } commit_signal_fetchdone(); for (i = 0; i < g_nr_commit_workers; i++) { rc = pthread_join(g_branch_workers[i], NULL); if (rc) die("pthread_join: %s(%d)\n", strerror(rc), rc); } return 0; }
svn_error_t * svn_ra__file_revs_from_log(svn_ra_session_t *ra_session, const char *path, svn_revnum_t start, svn_revnum_t end, svn_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool) { svn_node_kind_t kind; const char *repos_url, *session_url, *fs_path; apr_array_header_t *condensed_targets; struct fr_log_message_baton lmb; struct rev *rev; apr_hash_t *last_props; svn_stream_t *last_stream; apr_pool_t *currpool, *lastpool; /* Fetch the absolute FS path associated with PATH. */ SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool)); /* Check to make sure we're dealing with a file. */ SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool)); if (kind == svn_node_dir) return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file"), fs_path); condensed_targets = apr_array_make(pool, 1, sizeof(const char *)); APR_ARRAY_PUSH(condensed_targets, const char *) = path; lmb.path = fs_path; lmb.eldest = NULL; lmb.pool = pool; /* Accumulate revision metadata by walking the revisions backwards; this allows us to follow moves/copies correctly. */ SVN_ERR(svn_ra_get_log2(ra_session, condensed_targets, end, start, 0, /* no limit */ TRUE, FALSE, FALSE, NULL, fr_log_message_receiver, &lmb, pool)); /* Reparent the session while we go back through the history. */ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool)); SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool)); currpool = svn_pool_create(pool); lastpool = svn_pool_create(pool); /* We want the first txdelta to be against the empty file. */ last_props = apr_hash_make(lastpool); last_stream = svn_stream_empty(lastpool); /* Walk the revision list in chronological order, downloading each fulltext, diffing it with its predecessor, and calling the file_revs handler for each one. Use two iteration pools rather than one, because the diff routines need to look at a sliding window of revisions. Two pools gives us a ring buffer of sorts. */ for (rev = lmb.eldest; rev; rev = rev->next) { const char *temp_path; apr_pool_t *tmppool; apr_hash_t *props; apr_file_t *file; svn_stream_t *stream; apr_array_header_t *prop_diffs; svn_txdelta_stream_t *delta_stream; svn_txdelta_window_handler_t delta_handler = NULL; void *delta_baton = NULL; svn_pool_clear(currpool); /* Get the contents of the file from the repository, and put them in a temporary local file. */ SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL, svn_io_file_del_on_pool_cleanup, currpool, currpool)); SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision, stream, NULL, &props, currpool)); SVN_ERR(svn_stream_close(stream)); /* Open up a stream to the local file. */ SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT, currpool)); stream = svn_stream_from_aprfile2(file, FALSE, currpool); /* Calculate the property diff */ SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool)); /* Call the file_rev handler */ SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props, FALSE, /* merged revision */ &delta_handler, &delta_baton, prop_diffs, lastpool)); /* Compute and send delta if client asked for it. */ if (delta_handler) { /* Get the content delta. Don't calculate checksums as we don't * use them. */ svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool); /* And send. */ SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, delta_baton, lastpool)); } /* Switch the pools and data for the next iteration */ tmppool = currpool; currpool = lastpool; lastpool = tmppool; SVN_ERR(svn_stream_close(last_stream)); last_stream = stream; last_props = props; } SVN_ERR(svn_stream_close(last_stream)); svn_pool_destroy(currpool); svn_pool_destroy(lastpool); /* Reparent the session back to the original URL. */ return svn_ra_reparent(ra_session, session_url, pool); }
/* 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; }
svn_error_t * svn_client__switch_internal(svn_revnum_t *result_rev, const char *path, const char *switch_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_wc_adm_access_t *adm_access, svn_depth_t depth, svn_boolean_t depth_is_sticky, svn_boolean_t *timestamp_sleep, svn_boolean_t ignore_externals, svn_boolean_t allow_unver_obstructions, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const svn_wc_entry_t *entry; const char *URL, *anchor, *target, *source_root, *switch_rev_url; svn_ra_session_t *ra_session; svn_revnum_t revnum; svn_error_t *err = SVN_NO_ERROR; svn_wc_adm_access_t *dir_access; const svn_boolean_t close_adm_access = ! adm_access; const char *diff3_cmd; svn_boolean_t use_commit_times; svn_boolean_t sleep_here; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool); const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; 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; /* Do not support the situation of both exclude and switch a target. */ if (depth_is_sticky && depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); /* 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; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(path); SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); /* ### Need to lock the whole target tree to invalidate wcprops. Does non-recursive switch really need to invalidate the whole tree? */ if (adm_access) { svn_wc_adm_access_t *a = adm_access; const char *dir_access_path; /* This is a little hacky, but open two new read-only access baton's to get the anchor and target access batons that would be used if a locked access baton was not available. */ SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target, path, FALSE, -1, ctx->cancel_func, ctx->cancel_baton, pool)); anchor = svn_wc_adm_access_path(adm_access); dir_access_path = svn_wc_adm_access_path(dir_access); SVN_ERR(svn_wc_adm_close2(adm_access, pool)); SVN_ERR(svn_wc_adm_retrieve(&adm_access, a, anchor, pool)); SVN_ERR(svn_wc_adm_retrieve(&dir_access, a, dir_access_path, pool)); } else { SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target, path, TRUE, -1, ctx->cancel_func, ctx->cancel_baton, pool)); anchor = svn_wc_adm_access_path(adm_access); } SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool)); if (! entry->url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_path_local_style(anchor, pool)); URL = apr_pstrdup(pool, entry->url); /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum, &switch_rev_url, switch_url, adm_access, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_root, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_path_is_ancestor(source_root, URL)) return svn_error_createf (SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\n" "is not the same repository as\n" "'%s'"), URL, source_root); /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { const svn_wc_entry_t *target_entry; SVN_ERR(svn_wc_entry( &target_entry, svn_dirent_join(svn_wc_adm_access_path(adm_access), target, pool), adm_access, TRUE, pool)); if (target_entry && target_entry->kind == svn_node_dir) { SVN_ERR(svn_wc_crop_tree(adm_access, target, depth, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, pool)); } } SVN_ERR(svn_ra_reparent(ra_session, URL, pool)); /* Fetch the switch (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_switch_editor3(&revnum, adm_access, target, switch_rev_url, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, ctx->conflict_func, ctx->conflict_baton, diff3_cmd, preserved_exts, &switch_editor, &switch_edit_baton, traversal_info, 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_switch2(ra_session, &reporter, &report_baton, revnum, target, depth, switch_rev_url, switch_editor, switch_edit_baton, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, 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. We pass in a traversal_info for recording all externals. It shouldn't be needed for a switch if it wasn't for the relative externals of type '../path'. All of those must be resolved to the new location. */ err = svn_wc_crawl_revisions4(path, dir_access, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->notify_func2, ctx->notify_baton2, traversal_info, pool); if (err) { /* Don't rely on the error handling to handle the sleep later, do it now */ svn_io_sleep_for_timestamps(path, pool); return err; } *use_sleep = TRUE; /* We handle externals after the switch 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)) err = svn_client__handle_externals(adm_access, traversal_info, switch_url, path, source_root, depth, use_sleep, ctx, pool); /* Sleep to ensure timestamp integrity (we do this regardless of errors in the actual switch operation(s)). */ if (sleep_here) svn_io_sleep_for_timestamps(path, pool); /* Return errors we might have sustained. */ if (err) return err; if (close_adm_access) SVN_ERR(svn_wc_adm_close2(adm_access, pool)); /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor, 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_ra__get_inherited_props_walk(svn_ra_session_t *session, const char *path, svn_revnum_t revision, apr_array_header_t **inherited_props, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *repos_root_url; const char *session_url; const char *parent_url; apr_pool_t *iterpool = svn_pool_create(scratch_pool); *inherited_props = apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *)); /* Walk to the root of the repository getting inherited props for PATH. */ SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool)); SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool)); parent_url = session_url; while (strcmp(repos_root_url, parent_url)) { apr_hash_index_t *hi; apr_hash_t *parent_props; apr_hash_t *final_hash = apr_hash_make(result_pool); svn_error_t *err; svn_pool_clear(iterpool); parent_url = svn_uri_dirname(parent_url, scratch_pool); SVN_ERR(svn_ra_reparent(session, parent_url, iterpool)); err = session->vtable->get_dir(session, NULL, NULL, &parent_props, "", revision, SVN_DIRENT_ALL, iterpool); /* If the user doesn't have read access to a parent path then skip, but allow them to inherit from further up. */ if (err) { if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)) { svn_error_clear(err); continue; } else { return svn_error_trace(err); } } for (hi = apr_hash_first(scratch_pool, parent_props); hi; hi = apr_hash_next(hi)) { const char *name = apr_hash_this_key(hi); apr_ssize_t klen = apr_hash_this_key_len(hi); svn_string_t *value = apr_hash_this_val(hi); if (svn_property_kind2(name) == svn_prop_regular_kind) { name = apr_pstrdup(result_pool, name); value = svn_string_dup(value, result_pool); apr_hash_set(final_hash, name, klen, value); } } if (apr_hash_count(final_hash)) { svn_prop_inherited_item_t *new_iprop = apr_palloc(result_pool, sizeof(*new_iprop)); new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url, parent_url, result_pool); new_iprop->prop_hash = final_hash; svn_sort__array_insert(*inherited_props, &new_iprop, 0); } } /* Reparent session back to original URL. */ SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool)); svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
/* Like svn_ra_stat() but with a compatibility hack for pre-1.2 svnserve. */ static svn_error_t * ra_stat_compatible(svn_ra_session_t *ra_session, svn_revnum_t rev, svn_dirent_t **dirent_p, apr_uint32_t dirent_fields, svn_client_ctx_t *ctx, apr_pool_t *pool) { const char *repos_root_URL, *url; svn_error_t *err; svn_dirent_t *the_ent; SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_URL, pool)); SVN_ERR(svn_ra_get_session_url(ra_session, &url, pool)); err = svn_ra_stat(ra_session, "", rev, &the_ent, pool); /* svn_ra_stat() will work against old versions of mod_dav_svn, but not old versions of svnserve. In the case of a pre-1.2 svnserve, catch the specific error it throws:*/ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { /* Fall back to pre-1.2 strategy for fetching dirent's URL. */ svn_node_kind_t url_kind; svn_ra_session_t *parent_ra_session; apr_hash_t *parent_ents; const char *parent_url, *base_name; if (strcmp(url, repos_root_URL) == 0) { /* In this universe, there's simply no way to fetch information about the repository's root directory! */ return err; } svn_error_clear(err); SVN_ERR(svn_ra_check_path(ra_session, "", rev, &url_kind, pool)); if (url_kind == svn_node_none) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); /* Open a new RA session to the item's parent. */ svn_uri_split(&parent_url, &base_name, url, pool); SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session, NULL, parent_url, NULL, NULL, FALSE, TRUE, ctx, pool)); /* Get all parent's entries, and find the item's dirent in the hash. */ SVN_ERR(svn_ra_get_dir2(parent_ra_session, &parent_ents, NULL, NULL, "", rev, dirent_fields, pool)); the_ent = apr_hash_get(parent_ents, base_name, APR_HASH_KEY_STRING); if (the_ent == NULL) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); } else if (err) { return svn_error_trace(err); } *dirent_p = the_ent; return SVN_NO_ERROR; }
static svn_error_t * switch_internal(svn_revnum_t *result_rev, const char *local_abspath, const char *anchor_abspath, const char *switch_url, const svn_opt_revision_t *peg_revision, 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 ignore_ancestry, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const char *url, *target, *source_root, *switch_rev_url; svn_ra_session_t *ra_session; svn_revnum_t revnum; svn_error_t *err = SVN_NO_ERROR; const char *diff3_cmd; svn_boolean_t use_commit_times; svn_boolean_t sleep_here = FALSE; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; struct svn_client__dirent_fetcher_baton_t dfb; 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; /* Do not support the situation of both exclude and switch a target. */ if (depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* 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)); { svn_boolean_t has_working; SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath, pool)); if (has_working) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot switch '%s' because it is not in the " "repository yet"), svn_dirent_local_style(local_abspath, 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, pool) : NULL; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, anchor_abspath, pool, pool)); if (! url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_dirent_local_style(anchor_abspath, pool)); /* 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)); } /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum, &switch_rev_url, switch_url, anchor_abspath, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_root, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_uri__is_ancestor(source_root, url)) return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\nis not the same repository as\n'%s'"), url, source_root); /* If we're not ignoring ancestry, then error out if the switch source and target don't have a common ancestory. ### We're acting on the anchor here, not the target. Is that ### okay? */ if (! ignore_ancestry) { const char *target_url, *yc_path; svn_revnum_t target_rev, yc_rev; SVN_ERR(svn_wc__node_get_url(&target_url, ctx->wc_ctx, local_abspath, pool, pool)); SVN_ERR(svn_wc__node_get_base_rev(&target_rev, ctx->wc_ctx, local_abspath, pool)); /* ### It would be nice if this function could reuse the existing ra session instead of opening two for its own use. */ SVN_ERR(svn_client__get_youngest_common_ancestor(&yc_path, &yc_rev, switch_rev_url, revnum, target_url, target_rev, ctx, pool)); if (! (yc_path && SVN_IS_VALID_REVNUM(yc_rev))) return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, _("'%s' shares no common ancestry with '%s'"), switch_url, local_abspath); } SVN_ERR(svn_ra_reparent(ra_session, url, pool)); /* Fetch the switch (update) editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision() later on. */ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; SVN_ERR(svn_ra_get_session_url(ra_session, &dfb.anchor_url, pool)); dfb.target_revision = revnum; SVN_ERR(svn_wc_get_switch_editor4(&switch_editor, &switch_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, switch_rev_url, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, server_supports_depth, 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_switch2(ra_session, &reporter, &report_baton, revnum, target, depth_is_sticky ? depth : svn_depth_unknown, switch_rev_url, switch_editor, switch_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. We pass in an external_func for recording all externals. It shouldn't be needed for a switch if it wasn't for the relative externals of type '../path'. All of those must be resolved to the new location. */ 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 switch 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, source_root, local_abspath, depth, use_sleep, ctx, pool)); } /* Sleep to ensure timestamp integrity (we do this regardless of errors in the actual switch operation(s)). */ if (sleep_here) svn_io_sleep_for_timestamps(local_abspath, pool); /* Return errors we might have sustained. */ if (err) return svn_error_trace(err); /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor_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_blame5(const char *target, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start, const svn_opt_revision_t *end, const svn_diff_file_options_t *diff_options, svn_boolean_t ignore_mime_type, svn_boolean_t include_merged_revisions, svn_client_blame_receiver3_t receiver, void *receiver_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { struct file_rev_baton frb; svn_ra_session_t *ra_session; svn_revnum_t start_revnum, end_revnum; struct blame *walk, *walk_merged = NULL; apr_pool_t *iterpool; svn_stream_t *last_stream; svn_stream_t *stream; const char *target_abspath_or_url; if (start->kind == svn_opt_revision_unspecified || end->kind == svn_opt_revision_unspecified) return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); if (svn_path_is_url(target)) target_abspath_or_url = target; else SVN_ERR(svn_dirent_get_absolute(&target_abspath_or_url, target, pool)); /* Get an RA plugin for this filesystem object. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &end_revnum, NULL, target, NULL, peg_revision, end, ctx, pool)); SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx, target_abspath_or_url, ra_session, start, pool)); if (end_revnum < start_revnum) return svn_error_create (SVN_ERR_CLIENT_BAD_REVISION, NULL, _("Start revision must precede end revision")); frb.start_rev = start_revnum; frb.end_rev = end_revnum; frb.target = target; frb.ctx = ctx; frb.diff_options = diff_options; frb.ignore_mime_type = ignore_mime_type; frb.include_merged_revisions = include_merged_revisions; frb.last_filename = NULL; frb.last_original_filename = NULL; frb.chain = apr_palloc(pool, sizeof(*frb.chain)); frb.chain->blame = NULL; frb.chain->avail = NULL; frb.chain->pool = pool; if (include_merged_revisions) { frb.merged_chain = apr_palloc(pool, sizeof(*frb.merged_chain)); frb.merged_chain->blame = NULL; frb.merged_chain->avail = NULL; frb.merged_chain->pool = pool; } SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool)); frb.mainpool = pool; /* The callback will flip the following two pools, because it needs information from the previous call. Obviously, it can't rely on the lifetime of the pool provided by get_file_revs. */ frb.lastpool = svn_pool_create(pool); frb.currpool = svn_pool_create(pool); if (include_merged_revisions) { frb.filepool = svn_pool_create(pool); frb.prevfilepool = svn_pool_create(pool); } /* Collect all blame information. We need to ensure that we get one revision before the start_rev, if available so that we can know what was actually changed in the start revision. */ SVN_ERR(svn_ra_get_file_revs2(ra_session, "", start_revnum - (start_revnum > 0 ? 1 : 0), end_revnum, include_merged_revisions, file_rev_handler, &frb, pool)); if (end->kind == svn_opt_revision_working) { /* If the local file is modified we have to call the handler on the working copy file with keywords unexpanded */ svn_wc_status3_t *status; SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, target_abspath_or_url, pool, pool)); if (status->text_status != svn_wc_status_normal) { apr_hash_t *props; svn_stream_t *wcfile; svn_string_t *keywords; svn_stream_t *tempfile; const char *temppath; apr_hash_t *kw = NULL; SVN_ERR(svn_wc_prop_list2(&props, ctx->wc_ctx, target_abspath_or_url, pool, pool)); SVN_ERR(svn_stream_open_readonly(&wcfile, target, pool, pool)); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); if (keywords) SVN_ERR(svn_subst_build_keywords2(&kw, keywords->data, NULL, NULL, 0, NULL, pool)); wcfile = svn_subst_stream_translated(wcfile, "\n", TRUE, kw, FALSE, pool); SVN_ERR(svn_stream_open_unique(&tempfile, &temppath, NULL, svn_io_file_del_on_pool_cleanup, pool, pool)); SVN_ERR(svn_stream_copy3(wcfile, tempfile, ctx->cancel_func, ctx->cancel_baton, pool)); SVN_ERR(add_file_blame(frb.last_filename, temppath, frb.chain, NULL, frb.diff_options, pool)); frb.last_filename = temppath; } } /* Report the blame to the caller. */ /* The callback has to have been called at least once. */ SVN_ERR_ASSERT(frb.last_filename != NULL); /* Create a pool for the iteration below. */ iterpool = svn_pool_create(pool); /* Open the last file and get a stream. */ SVN_ERR(svn_stream_open_readonly(&last_stream, frb.last_filename, pool, pool)); stream = svn_subst_stream_translated(last_stream, "\n", TRUE, NULL, FALSE, pool); /* Perform optional merged chain normalization. */ if (include_merged_revisions) { /* If we never created any blame for the original chain, create it now, with the most recent changed revision. This could occur if a file was created on a branch and them merged to another branch. This is semanticly a copy, and we want to use the revision on the branch as the most recently changed revision. ### Is this really what we want to do here? Do the sematics of copy change? */ if (!frb.chain->blame) frb.chain->blame = blame_create(frb.chain, frb.rev, 0); normalize_blames(frb.chain, frb.merged_chain, pool); walk_merged = frb.merged_chain->blame; } /* Process each blame item. */ for (walk = frb.chain->blame; walk; walk = walk->next) { apr_off_t line_no; svn_revnum_t merged_rev; const char *merged_path; apr_hash_t *merged_rev_props; if (walk_merged) { merged_rev = walk_merged->rev->revision; merged_rev_props = walk_merged->rev->rev_props; merged_path = walk_merged->rev->path; } else { merged_rev = SVN_INVALID_REVNUM; merged_rev_props = NULL; merged_path = NULL; } for (line_no = walk->start; !walk->next || line_no < walk->next->start; ++line_no) { svn_boolean_t eof; svn_stringbuf_t *sb; svn_pool_clear(iterpool); SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, iterpool)); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); if (!eof || sb->len) { if (walk->rev) SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, line_no, walk->rev->revision, walk->rev->rev_props, merged_rev, merged_rev_props, merged_path, sb->data, FALSE, iterpool)); else SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, line_no, SVN_INVALID_REVNUM, NULL, SVN_INVALID_REVNUM, NULL, NULL, sb->data, TRUE, iterpool)); } if (eof) break; } if (walk_merged) walk_merged = walk_merged->next; } SVN_ERR(svn_stream_close(stream)); svn_pool_destroy(frb.lastpool); svn_pool_destroy(frb.currpool); if (include_merged_revisions) { svn_pool_destroy(frb.filepool); svn_pool_destroy(frb.prevfilepool); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }