static svn_error_t * fetch_base_func(const char **filename, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct edit_baton *eb = baton; svn_stream_t *fstream; svn_error_t *err; SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, svn_io_file_del_on_pool_cleanup, result_pool, scratch_pool)); err = svn_ra_get_file(eb->ra_session, path, eb->revision, fstream, NULL, NULL, scratch_pool); if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) { svn_error_clear(err); SVN_ERR(svn_stream_close(fstream)); *filename = NULL; return SVN_NO_ERROR; } else if (err) return svn_error_trace(err); SVN_ERR(svn_stream_close(fstream)); return SVN_NO_ERROR; }
svn_error_t* CommitEditor::provide_props_cb(apr_hash_t **props, svn_revnum_t *revision, void *baton, const char *repos_relpath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { CommitEditor* editor = static_cast<CommitEditor*>(baton); if (editor->m_get_props_cb.get()) { const Java::Env env; SVN_JAVAHL_CATCH(env, SVN_ERR_BASE, invoke_get_props_cb(props, revision, env, editor->m_get_props_cb.get(), repos_relpath, result_pool)); } else { SVN_ERR(open_callback_session(editor->m_callback_session, editor->m_callback_session_url, editor->m_callback_session_uuid, editor->m_session->m_context, editor->pool)); svn_node_kind_t kind = svn_node_unknown; SVN_ERR(svn_ra_check_path(editor->m_callback_session, repos_relpath, SVN_INVALID_REVNUM, &kind, scratch_pool)); // FIXME: Getting properties from the youngest revision is in // fact not such a bright idea, as the path may have been moved // or deleted in the repository. On the other hand, if that // happens, the commit would fail due to a conflict anyway. if (kind == svn_node_file) return svn_ra_get_file(editor->m_callback_session, repos_relpath, SVN_INVALID_REVNUM, NULL, revision, props, scratch_pool); else if (kind == svn_node_dir) return svn_ra_get_dir2(editor->m_callback_session, NULL, revision, props, repos_relpath, SVN_INVALID_REVNUM, 0, scratch_pool); else return svn_error_createf( SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Expected node kind '%s' or '%s' but got '%s'"), svn_node_kind_to_word(svn_node_file), svn_node_kind_to_word(svn_node_dir), svn_node_kind_to_word(kind)); } return SVN_NO_ERROR; }
/* Get revision B->base_revision of the file described by B from the * repository, through B->edit_baton->ra_session. * * Unless PROPS_ONLY is true: * Set B->path_start_revision to the path of a new temporary file containing * the file's text. * Set B->start_md5_checksum to that file's MD-5 checksum. * Install a pool cleanup handler on B->pool to delete the file. * * Always: * Set B->pristine_props to a new hash containing the file's properties. * * Allocate all results in B->pool. */ static svn_error_t * get_file_from_ra(struct file_baton *b, svn_boolean_t props_only, apr_pool_t *scratch_pool) { if (! props_only) { svn_stream_t *fstream; SVN_ERR(svn_stream_open_unique(&fstream, &(b->path_start_revision), NULL, svn_io_file_del_on_pool_cleanup, b->pool, scratch_pool)); fstream = svn_stream_checksummed2(fstream, NULL, &b->start_md5_checksum, svn_checksum_md5, TRUE, scratch_pool); /* Retrieve the file and its properties */ SVN_ERR(svn_ra_get_file(b->edit_baton->ra_session, b->path, b->base_revision, fstream, NULL, &(b->pristine_props), b->pool)); SVN_ERR(svn_stream_close(fstream)); } else { SVN_ERR(svn_ra_get_file(b->edit_baton->ra_session, b->path, b->base_revision, NULL, NULL, &(b->pristine_props), b->pool)); } return SVN_NO_ERROR; }
static svn_error_t * get_file_for_validation(const svn_string_t **mime_type, svn_stream_t *stream, void *baton, apr_pool_t *pool) { struct getter_baton *gb = baton; svn_ra_session_t *ra_session = gb->ra_session; apr_hash_t *props; SVN_ERR(svn_ra_get_file(ra_session, "", gb->base_revision_for_url, stream, NULL, (mime_type ? &props : NULL), pool)); if (mime_type) *mime_type = apr_hash_get(props, SVN_PROP_MIME_TYPE, APR_HASH_KEY_STRING); return SVN_NO_ERROR; }
static svn_error_t * fetch_props_func(apr_hash_t **props, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct revision_baton *rb = baton; svn_node_kind_t node_kind; if (! SVN_IS_VALID_REVNUM(base_revision)) base_revision = rb->rev - 1; SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, &node_kind, scratch_pool)); if (node_kind == svn_node_file) { SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision, NULL, NULL, props, result_pool)); } else if (node_kind == svn_node_dir) { apr_array_header_t *tmp_props; SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path, base_revision, 0 /* Dirent fields */, result_pool)); tmp_props = svn_prop_hash_to_array(*props, result_pool); SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, result_pool)); *props = svn_prop_array_to_hash(tmp_props, result_pool); } else { *props = apr_hash_make(result_pool); } return SVN_NO_ERROR; }
static svn_error_t * bench_null_export(svn_revnum_t *result_rev, const char *from_path_or_url, svn_opt_revision_t *peg_revision, svn_opt_revision_t *revision, svn_depth_t depth, void *baton, svn_client_ctx_t *ctx, svn_boolean_t quiet, apr_pool_t *pool) { svn_revnum_t edit_revision = SVN_INVALID_REVNUM; svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url); SVN_ERR_ASSERT(peg_revision != NULL); SVN_ERR_ASSERT(revision != NULL); if (peg_revision->kind == svn_opt_revision_unspecified) peg_revision->kind = svn_path_is_url(from_path_or_url) ? svn_opt_revision_head : svn_opt_revision_working; if (revision->kind == svn_opt_revision_unspecified) revision = peg_revision; if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)) { svn_client__pathrev_t *loc; svn_ra_session_t *ra_session; svn_node_kind_t kind; /* Get the RA connection. */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, from_path_or_url, NULL, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool)); if (kind == svn_node_file) { apr_hash_t *props; /* Since you cannot actually root an editor at a file, we * manually drive a few functions of our editor. */ /* Step outside the editor-likeness for a moment, to actually talk * to the repository. */ /* ### note: the stream will not be closed */ SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, svn_stream_empty(pool), NULL, &props, pool)); } else if (kind == svn_node_dir) { void *edit_baton = NULL; const svn_delta_editor_t *export_editor = NULL; const svn_ra_reporter3_t *reporter; void *report_baton; svn_delta_editor_t *editor = svn_delta_default_editor(pool); editor->set_target_revision = set_target_revision; editor->open_root = open_root; editor->add_directory = add_directory; editor->add_file = add_file; editor->apply_textdelta = apply_textdelta; editor->close_file = close_file; editor->change_file_prop = change_file_prop; editor->change_dir_prop = change_dir_prop; /* for ra_svn, we don't need an editior in quiet mode */ if (!quiet || strncmp(loc->repos_root_url, "svn:", 4)) SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func, ctx->cancel_baton, editor, baton, &export_editor, &edit_baton, pool)); /* Manufacture a basic 'report' to the update reporter. */ SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton, loc->rev, "", /* no sub-target */ depth, FALSE, /* don't want copyfrom-args */ FALSE, /* don't want ignore_ancestry */ export_editor, edit_baton, pool, pool)); SVN_ERR(reporter->set_path(report_baton, "", loc->rev, /* Depth is irrelevant, as we're passing start_empty=TRUE anyway. */ svn_depth_infinity, TRUE, /* "help, my dir is empty!" */ NULL, pool)); SVN_ERR(reporter->finish_report(report_baton, pool)); } else if (kind == svn_node_none) { return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' doesn't exist"), from_path_or_url); } /* kind == svn_node_unknown not handled */ } if (result_rev) *result_rev = edit_revision; return SVN_NO_ERROR; }
/* Helper for the remote case of svn_client_proplist. * * Push a new 'svn_client_proplist_item_t *' item onto PROPLIST, * containing the properties for "TARGET_PREFIX/TARGET_RELATIVE" in * REVNUM, obtained using RA_LIB and SESSION. The item->node_name * will be "TARGET_PREFIX/TARGET_RELATIVE", and the value will be a * hash mapping 'const char *' property names onto 'svn_string_t *' * property values. * * Allocate the new item and its contents in POOL. * Do all looping, recursion, and temporary work in SCRATCHPOOL. * * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE". * * If the target is a directory, only fetch properties for the files * and directories at depth DEPTH. */ static svn_error_t * remote_proplist(const char *target_prefix, const char *target_relative, svn_node_kind_t kind, svn_revnum_t revnum, svn_ra_session_t *ra_session, svn_depth_t depth, svn_proplist_receiver_t receiver, void *receiver_baton, apr_pool_t *pool, apr_pool_t *scratchpool) { apr_hash_t *dirents; apr_hash_t *prop_hash, *final_hash; apr_hash_index_t *hi; const char *target_full_url = svn_path_url_add_component2(target_prefix, target_relative, scratchpool); if (kind == svn_node_dir) { SVN_ERR(svn_ra_get_dir2(ra_session, (depth > svn_depth_empty) ? &dirents : NULL, NULL, &prop_hash, target_relative, revnum, SVN_DIRENT_KIND, scratchpool)); } else if (kind == svn_node_file) { SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum, NULL, NULL, &prop_hash, scratchpool)); } else { return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, _("Unknown node kind for '%s'"), target_full_url); } /* Filter out non-regular properties, since the RA layer returns all kinds. Copy regular properties keys/vals from the prop_hash allocated in SCRATCHPOOL to the "final" hash allocated in POOL. */ final_hash = apr_hash_make(pool); for (hi = apr_hash_first(scratchpool, prop_hash); hi; hi = apr_hash_next(hi)) { const char *name = svn__apr_hash_index_key(hi); apr_ssize_t klen = svn__apr_hash_index_klen(hi); svn_string_t *value = svn__apr_hash_index_val(hi); svn_prop_kind_t prop_kind; prop_kind = svn_property_kind(NULL, name); if (prop_kind == svn_prop_regular_kind) { name = apr_pstrdup(pool, name); value = svn_string_dup(value, pool); apr_hash_set(final_hash, name, klen, value); } } SVN_ERR(call_receiver(target_full_url, final_hash, receiver, receiver_baton, pool)); if (depth > svn_depth_empty && (kind == svn_node_dir) && (apr_hash_count(dirents) > 0)) { apr_pool_t *subpool = svn_pool_create(scratchpool); for (hi = apr_hash_first(scratchpool, dirents); hi; hi = apr_hash_next(hi)) { const char *this_name = svn__apr_hash_index_key(hi); svn_dirent_t *this_ent = svn__apr_hash_index_val(hi); const char *new_target_relative; svn_pool_clear(subpool); new_target_relative = svn_relpath_join(target_relative, this_name, subpool); if (this_ent->kind == svn_node_file || depth > svn_depth_files) { svn_depth_t depth_below_here = depth; if (depth == svn_depth_immediates) depth_below_here = svn_depth_empty; SVN_ERR(remote_proplist(target_prefix, new_target_relative, this_ent->kind, revnum, ra_session, depth_below_here, receiver, receiver_baton, pool, subpool)); } } svn_pool_destroy(subpool); } return SVN_NO_ERROR; }
/* Helper for the remote case of svn_client_propget. * * Get the value of property PROPNAME in REVNUM, using RA_LIB and * SESSION. Store the value ('svn_string_t *') in PROPS, under the * path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *'). * * Recurse according to DEPTH, similarly to svn_client_propget3(). * * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE". * Yes, caller passes this; it makes the recursion more efficient :-). * * Allocate the keys and values in PERM_POOL, but do all temporary * work in WORK_POOL. The two pools can be the same; recursive * calls may use a different WORK_POOL, however. */ static svn_error_t * remote_propget(apr_hash_t *props, const char *propname, const char *target_prefix, const char *target_relative, svn_node_kind_t kind, svn_revnum_t revnum, svn_ra_session_t *ra_session, svn_depth_t depth, apr_pool_t *perm_pool, apr_pool_t *work_pool) { apr_hash_t *dirents; apr_hash_t *prop_hash; const svn_string_t *val; const char *target_full_url = svn_path_url_add_component2(target_prefix, target_relative, work_pool); if (kind == svn_node_dir) { SVN_ERR(svn_ra_get_dir2(ra_session, (depth >= svn_depth_files ? &dirents : NULL), NULL, &prop_hash, target_relative, revnum, SVN_DIRENT_KIND, work_pool)); } else if (kind == svn_node_file) { SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum, NULL, NULL, &prop_hash, work_pool)); } else if (kind == svn_node_none) { return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, _("'%s' does not exist in revision %ld"), target_full_url, revnum); } else { return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, _("Unknown node kind for '%s'"), target_full_url); } if ((val = apr_hash_get(prop_hash, propname, APR_HASH_KEY_STRING))) { apr_hash_set(props, apr_pstrdup(perm_pool, target_full_url), APR_HASH_KEY_STRING, svn_string_dup(val, perm_pool)); } if (depth >= svn_depth_files && kind == svn_node_dir && apr_hash_count(dirents) > 0) { apr_hash_index_t *hi; apr_pool_t *iterpool = svn_pool_create(work_pool); for (hi = apr_hash_first(work_pool, dirents); hi; hi = apr_hash_next(hi)) { const char *this_name = svn__apr_hash_index_key(hi); svn_dirent_t *this_ent = svn__apr_hash_index_val(hi); const char *new_target_relative; svn_depth_t depth_below_here = depth; svn_pool_clear(iterpool); if (depth == svn_depth_files && this_ent->kind == svn_node_dir) continue; if (depth == svn_depth_files || depth == svn_depth_immediates) depth_below_here = svn_depth_empty; new_target_relative = svn_relpath_join(target_relative, this_name, iterpool); SVN_ERR(remote_propget(props, propname, target_prefix, new_target_relative, this_ent->kind, revnum, ra_session, depth_below_here, perm_pool, iterpool)); } svn_pool_destroy(iterpool); } return SVN_NO_ERROR; }
svn_error_t * svn_client_cat2(svn_stream_t *out, const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session; svn_revnum_t rev; svn_string_t *eol_style; svn_string_t *keywords; apr_hash_t *props; const char *url; svn_stream_t *output = out; svn_error_t *err; /* ### Inconsistent default revision logic in this command. */ if (peg_revision->kind == svn_opt_revision_unspecified) { peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, path_or_url); revision = svn_cl__rev_default_to_head_or_base(revision, path_or_url); } else { peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, path_or_url); revision = svn_cl__rev_default_to_peg(revision, peg_revision); } if (! svn_path_is_url(path_or_url) && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind) && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)) { const char *local_abspath; svn_stream_t *normal_stream; SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, pool)); SVN_ERR(svn_client__get_normalized_stream(&normal_stream, ctx->wc_ctx, local_abspath, revision, TRUE, FALSE, ctx->cancel_func, ctx->cancel_baton, pool, pool)); /* We don't promise to close output, so disown it to ensure we don't. */ output = svn_stream_disown(output, pool); return svn_error_trace(svn_stream_copy3(normal_stream, output, ctx->cancel_func, ctx->cancel_baton, pool)); } /* Get an RA plugin for this filesystem object. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &rev, &url, path_or_url, NULL, peg_revision, revision, ctx, pool)); /* Grab some properties we need to know in order to figure out if anything special needs to be done with this file. */ err = svn_ra_get_file(ra_session, "", rev, NULL, NULL, &props, pool); if (err) { if (err->apr_err == SVN_ERR_FS_NOT_FILE) { return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, err, _("URL '%s' refers to a directory"), url); } else { return svn_error_trace(err); } } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); if (eol_style || keywords) { /* It's a file with no special eol style or keywords. */ svn_subst_eol_style_t eol; const char *eol_str; apr_hash_t *kw; if (eol_style) svn_subst_eol_style_from_value(&eol, &eol_str, eol_style->data); else { eol = svn_subst_eol_style_none; eol_str = NULL; } if (keywords) { svn_string_t *cmt_rev, *cmt_date, *cmt_author; apr_time_t when = 0; cmt_rev = apr_hash_get(props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING); cmt_date = apr_hash_get(props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING); cmt_author = apr_hash_get(props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING); if (cmt_date) SVN_ERR(svn_time_from_cstring(&when, cmt_date->data, pool)); SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, cmt_rev->data, url, when, cmt_author ? cmt_author->data : NULL, pool)); } else kw = NULL; /* Interject a translating stream */ output = svn_subst_stream_translated(svn_stream_disown(out, pool), eol_str, FALSE, kw, TRUE, pool); } SVN_ERR(svn_ra_get_file(ra_session, "", rev, output, NULL, NULL, pool)); if (out != output) /* Close the interjected stream */ SVN_ERR(svn_stream_close(output)); return SVN_NO_ERROR; }
svn_error_t * svn_client__copy_foreign(const char *url, const char *dst_abspath, svn_opt_revision_t *peg_revision, svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t make_parents, svn_boolean_t already_locked, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_ra_session_t *ra_session; svn_client__pathrev_t *loc; svn_node_kind_t kind; svn_node_kind_t wc_kind; const char *dir_abspath; SVN_ERR_ASSERT(svn_path_is_url(url)); SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); /* Do we need to validate/update revisions? */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, url, NULL, peg_revision, revision, ctx, scratch_pool)); SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool)); if (kind != svn_node_file && kind != svn_node_dir) return svn_error_createf( SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' is not a valid location inside a repository"), url); SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE, scratch_pool)); if (wc_kind != svn_node_none) { return svn_error_createf( SVN_ERR_ENTRY_EXISTS, NULL, _("'%s' is already under version control"), svn_dirent_local_style(dst_abspath, scratch_pool)); } dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, FALSE, FALSE, scratch_pool)); if (wc_kind == svn_node_none) { if (make_parents) SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx, scratch_pool)); SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, FALSE, FALSE, scratch_pool)); } if (wc_kind != svn_node_dir) return svn_error_createf( SVN_ERR_ENTRY_NOT_FOUND, NULL, _("Can't add '%s', because no parent directory is found"), svn_dirent_local_style(dst_abspath, scratch_pool)); if (kind == svn_node_file) { svn_stream_t *target; apr_hash_t *props; apr_hash_index_t *hi; SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props, scratch_pool)); if (props != NULL) for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) { const char *name = apr_hash_this_key(hi); if (svn_property_kind2(name) != svn_prop_regular_kind || ! strcmp(name, SVN_PROP_MERGEINFO)) { /* We can't handle DAV, ENTRY and merge specific props here */ svn_hash_sets(props, name, NULL); } } if (!already_locked) SVN_WC__CALL_WITH_WRITE_LOCK( svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props, TRUE /* skip checks */, ctx->notify_func2, ctx->notify_baton2, scratch_pool), ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); else SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props, TRUE /* skip checks */, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); } else { if (!already_locked) SVN_WC__CALL_WITH_WRITE_LOCK( copy_foreign_dir(ra_session, loc, ctx->wc_ctx, dst_abspath, depth, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, scratch_pool), ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); else SVN_ERR(copy_foreign_dir(ra_session, loc, ctx->wc_ctx, dst_abspath, depth, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, scratch_pool)); } return SVN_NO_ERROR; }
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); }