Return-Path: X-Original-To: apmail-subversion-commits-archive@minotaur.apache.org Delivered-To: apmail-subversion-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 25D98C4B1 for ; Fri, 14 Nov 2014 10:06:15 +0000 (UTC) Received: (qmail 32244 invoked by uid 500); 14 Nov 2014 10:06:15 -0000 Delivered-To: apmail-subversion-commits-archive@subversion.apache.org Received: (qmail 32212 invoked by uid 500); 14 Nov 2014 10:06:15 -0000 Mailing-List: contact commits-help@subversion.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@subversion.apache.org Delivered-To: mailing list commits@subversion.apache.org Received: (qmail 32202 invoked by uid 99); 14 Nov 2014 10:06:14 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 14 Nov 2014 10:06:14 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 14 Nov 2014 10:06:10 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id BFD822388A38; Fri, 14 Nov 2014 10:05:49 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1639592 [4/4] - in /subversion/branches/move-tracking-2/subversion: include/private/svn_editor3.h libsvn_delta/branching.c libsvn_delta/compat3.c libsvn_delta/compat3b.c Date: Fri, 14 Nov 2014 10:05:41 -0000 To: commits@subversion.apache.org From: julianfoad@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20141114100549.BFD822388A38@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Copied: subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3b.c (from r1639314, subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c) URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3b.c?p2=subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3b.c&p1=subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c&r1=1639314&r2=1639592&rev=1639592&view=diff ============================================================================== --- subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c (original) +++ subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3b.c Fri Nov 14 10:05:32 2014 @@ -1,5 +1,5 @@ /* - * compat3.c : Ev3-to-Ev1 compatibility. + * compat3b.c : Ev3-to-Ev1 compatibility via element-based branching. * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one @@ -33,7 +33,6 @@ #include "svn_pools.h" #include "private/svn_delta_private.h" -#include "private/svn_sorts_private.h" #include "../libsvn_delta/debug_editor.h" #include "svn_private_config.h" @@ -52,120 +51,6 @@ peg_path_str(svn_editor3_peg_path_t loc, } #endif -/* - * ======================================================================== - * Configuration Options - * ======================================================================== - */ - -/* Features that are not wanted for this commit editor shim but may be - * wanted in a similar but different shim such as for an update editor. */ -/* #define SHIM_WITH_ADD_ABSENT */ -/* #define SHIM_WITH_UNLOCK */ - -/* The Ev2 shim ran the accumulated actions during abort... But why? - * If we don't, then aborting and re-opening a commit txn doesn't find - * all the previous changes, so tests/libsvn_repos/repos-test 12 fails. - */ -#define SHIM_WITH_ACTIONS_DURING_ABORT - -/* Whether to support switching from relative to absolute paths in the - * Ev1 methods. */ -/* #define SHIM_WITH_ABS_PATHS */ - - -/* - * ======================================================================== - * Shim Connector - * ======================================================================== - * - * The shim connector enables a more exact round-trip conversion from an - * Ev1 drive to Ev3 and back to Ev1. - */ -struct svn_editor3__shim_connector_t -{ - /* Set to true if and when an Ev1 receiving shim receives an absolute - * path (prefixed with '/') from the delta edit, and causes the Ev1 - * sending shim to send absolute paths. - * ### NOT IMPLEMENTED - */ -#ifdef SHIM_WITH_ABS_PATHS - svn_boolean_t *ev1_absolute_paths; -#endif - - /* The Ev1 set_target_revision and start_edit methods, respectively, will - * call the TARGET_REVISION_FUNC and START_EDIT_FUNC callbacks, if non-null. - * Otherwise, default calls will be used. - * - * (Possibly more useful for update editors than for commit editors?) */ - svn_editor3__set_target_revision_func_t target_revision_func; - - /* If not null, a callback that the Ev3 driver may call to - * provide the "base revision" of the root directory, even if it is not - * going to modify that directory. (If it does modify it, then it will - * pass in the appropriate base revision at that time.) If null - * or if the driver does not call it, then the Ev1 - * open_root() method will be called with SVN_INVALID_REVNUM as the base - * revision parameter. - */ - svn_delta__start_edit_func_t start_edit_func; - -#ifdef SHIM_WITH_UNLOCK - /* A callback which will be called when an unlocking action is received. - (For update editors?) */ - svn_delta__unlock_func_t unlock_func; -#endif - - void *baton; -}; - -svn_error_t * -svn_editor3__insert_shims( - const svn_delta_editor_t **new_deditor, - void **new_dedit_baton, - const svn_delta_editor_t *old_deditor, - void *old_dedit_baton, - const char *repos_root, - const char *base_relpath, - svn_editor3__shim_fetch_func_t fetch_func, - void *fetch_baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_editor3_t *editor3; - svn_editor3__shim_connector_t *shim_connector; - -#ifdef SVN_DEBUG - /*SVN_ERR(svn_delta__get_debug_editor(&old_deditor, &old_dedit_baton, - old_deditor, old_dedit_baton, - "[OUT] ", result_pool));*/ -#endif - SVN_ERR(svn_delta__ev3_from_delta_for_commit( - &editor3, - &shim_connector, - old_deditor, old_dedit_baton, - repos_root, base_relpath, - fetch_func, fetch_baton, - NULL, NULL /*cancel*/, - result_pool, scratch_pool)); -#ifdef SVN_DEBUG - /*SVN_ERR(svn_editor3__get_debug_editor(&editor3, editor3, result_pool));*/ -#endif - SVN_ERR(svn_delta__delta_from_ev3_for_commit( - new_deditor, new_dedit_baton, - editor3, - repos_root, base_relpath, - fetch_func, fetch_baton, - shim_connector, - result_pool, scratch_pool)); -#ifdef SVN_DEBUG - /*SVN_ERR(svn_delta__get_debug_editor(new_deditor, new_dedit_baton, - *new_deditor, *new_dedit_baton, - "[IN] ", result_pool));*/ -#endif - return SVN_NO_ERROR; -} - /* * ======================================================================== @@ -367,8 +252,8 @@ delete_subtree(apr_hash_t *changes, { /* If this previous change was a non-replacing addition, there is no longer any change to be made at this path. If it was - a replacement or a modification, it now becomes a delete. (If it was a delete, - this attempt to delete is an error.) */ + a replacement or a modification, it now becomes a delete. + (If it was a delete, this attempt to delete is an error.) */ VERIFY(change->action != RESTRUCTURE_DELETE); if (change->action == RESTRUCTURE_ADD && !change->deleting) { @@ -412,2860 +297,49 @@ delete_subtree(apr_hash_t *changes, return SVN_NO_ERROR; } -/* Insert a new change for RELPATH, or return an existing one. - * - * Verify Ev1 ordering. - * - * RELPATH is relative to the repository root. - */ -static svn_error_t * -insert_change_ev1_rules(change_node_t **change_p, apr_hash_t *changes, - const char *relpath, - enum restructure_action_t action, - svn_node_kind_t kind) -{ - apr_pool_t *changes_pool = apr_hash_pool_get(changes); - apr_pool_t *scratch_pool = changes_pool; - change_node_t *change = svn_hash_gets(changes, relpath); - svn_tristate_t exists = check_existence(changes, relpath); - - /* Check whether this op is allowed. */ - switch (action) - { - case RESTRUCTURE_NONE: - VERIFY(kind == svn_node_dir || kind == svn_node_file); - VERIFY(exists != svn_tristate_false); - if (change) - { - VERIFY(change->kind == kind); - } - break; - - case RESTRUCTURE_ADD: - VERIFY(kind == svn_node_dir || kind == svn_node_file); - if (change) - { - /* Add or copy is allowed after delete (and replaces the delete), - * but not allowed after an add or a no-restructure change. */ - VERIFY(change->action == RESTRUCTURE_DELETE); - } - else - { - const char *parent_path = svn_relpath_dirname(relpath, scratch_pool); - - /* Disallow if *parent* path is known to be non-existent - * (deleted (root or child), or child of a non-copy add). */ - VERIFY(check_existence(changes, parent_path) != svn_tristate_false); - } - break; - -#ifdef SHIM_WITH_ADD_ABSENT - case RESTRUCTURE_ADD_ABSENT: - VERIFY(kind == svn_node_dir || kind == svn_node_file); - /* ### */ - break; -#endif - - case RESTRUCTURE_DELETE: - VERIFY(kind == svn_node_none); - /* Delete is allowed only on a currently existing path. */ - VERIFY(exists != svn_tristate_false); - /* Remove change records for any child paths inside this delete. */ - { - apr_hash_index_t *hi; - - for (hi = apr_hash_first(scratch_pool, changes); - hi; hi = apr_hash_next(hi)) - { - const char *this_relpath = apr_hash_this_key(hi); - const char *r = svn_relpath_skip_ancestor(relpath, this_relpath); - - if (r && r[0]) - { - svn_hash_sets(changes, this_relpath, NULL); - } - } - } - - break; - } - - if (change) - { - if (action != RESTRUCTURE_NONE) - { - change->action = action; - } - change->kind = kind; - } - else - { - /* Create a new change. Callers will set the other fields as needed. */ - change = apr_pcalloc(changes_pool, sizeof(*change)); - change->action = action; - change->kind = kind; - change->changing_rev = SVN_INVALID_REVNUM; - change->deleting_rev = SVN_INVALID_REVNUM; - - svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change); - } - - *change_p = change; - return SVN_NO_ERROR; -} - -/* Duplicate any child changes from the subtree under (but excluding) - * FROM_PATH into the subtree under (but excluding) NEW_PATH. */ -static svn_error_t * -duplicate_child_changes(apr_hash_t *changes, - const char *from_path, - const char *new_path, - apr_pool_t *scratch_pool) -{ - apr_pool_t *changes_pool = apr_hash_pool_get(changes); - apr_hash_index_t *hi; - - /* for each change ... */ - for (hi = apr_hash_first(scratch_pool, changes); - hi; hi = apr_hash_next(hi)) - { - const char *this_path = apr_hash_this_key(hi); - change_node_t *this_change = apr_hash_this_val(hi); - const char *r = svn_relpath_skip_ancestor(from_path, this_path); - - /* ... at a child path strictly below FROM_PATH ... */ - if (r && r[0]) - { - const char *new_child_path - = svn_relpath_join(new_path, r, changes_pool); - - /* ... duplicate that change as a child of NEW_PATH */ - svn_hash_sets(changes, new_child_path, this_change); - } - } - - return SVN_NO_ERROR; -} - /* * =================================================================== - * Commit Editor converter to join a v1 driver to a v3 consumer + * Commit Editor converter to join a v3 driver to a v1 consumer * =================================================================== * - * The following code maps the calls to a traditional delta editor to an - * Editor v3. - * - * It does not create 'move' operations, neither heuristically nor using - * out-of-band cues. In fact, the code structure is likely to be - * unsuitable for processing moves. + * This editor buffers all the changes before driving the Ev1 at the end, + * since it needs to do a single depth-first traversal of the path space + * and this cannot be started until all moves are known. * - * The design assumes that each Ev1 path maps to a different Ev3 element. + * Moves are converted to copy-and-delete, with the copy being from + * the source peg rev. (### Should it request copy-from revision "-1"?) * * It works like this: * * +------+--------+ * | path | change | - * Ev1 --> +------+--------+ --> Ev3 + * Ev3 --> +------+--------+ --> Ev1 * | ... | ... | * | ... | ... | * - * 1. Ev1 changes are accumulated in a per-path table, EB->changes. - * Changes are de-duplicated so there is only one change per path. - * - * 2. On Ev1 close-edit, walk through the table in a depth-first order, - * sending the equivalent Ev3 action for each change. - * - * ### This was designed (in its Ev2 form) for both commit and update - * editors, but Ev3 is currently only designed as a commit editor. - * Therefore 'update' functionality probably doesn't work, including: - * - create 'absent' node (currently just omits the 'put', which - * Ev3 currently defines will create an empty node) - * - * ### Need to review all revisions passed to pathrev()/txn_path() - * constructors: are they really the right peg revs? - * - * TODO: Fetch the base (kind, props, text) of an opened file or dir - * right away when it's opened. Delaying the fetch, as we do for the - * sake of 'optimization', adds complexity & is probably a poor choice. - */ - -/* Construct a peg-path-rev */ -static svn_editor3_peg_path_t -pathrev(const char *repos_relpath, svn_revnum_t revision) -{ - svn_editor3_peg_path_t p; - - p.rev = revision; - p.relpath = repos_relpath; - return p; -} - -/* Construct a txn-path-rev */ -static svn_editor3_txn_path_t -txn_path(const char *repos_relpath, svn_revnum_t revision, - const char *created_relpath) -{ - svn_editor3_txn_path_t p; - - p.peg.rev = revision; - p.peg.relpath = repos_relpath; - p.relpath = created_relpath; - return p; -} - -struct ev3_edit_baton -{ - svn_editor3_t *editor; - - apr_hash_t *changes; /* REPOS_RELPATH -> struct change_node */ - - /* (const char *) paths relative to repository root, in path visiting - order. */ - apr_array_header_t *path_order; -#ifdef SHIM_WITH_ACTIONS_DURING_ABORT - /* Number of paths in PATH_ORDER processed so far. */ - int paths_processed; -#endif - - /* Repository root URL. */ - const char *repos_root_url; - /* Base directory of the edit, relative to the repository root. */ - const char *base_relpath; - - const svn_editor3__shim_connector_t *shim_connector; - - svn_editor3__shim_fetch_func_t fetch_func; - void *fetch_baton; - - svn_boolean_t closed; - - apr_pool_t *edit_pool; -}; - -struct ev3_dir_baton -{ - struct ev3_edit_baton *eb; - - /* Path of this directory relative to repository root. */ - const char *path; - /* The base revision if this is a pre-existing directory; - SVN_INVALID_REVNUM if added/copied. - ### Can also be SVN_INVALID_REVNUM for a pre-existing dir, - meaning the base is the youngest revision. */ - svn_revnum_t base_revision; - - /* Copy-from path (relative to repository root) and revision. This is - set for each dir inside a copy, not just the copy root. */ - const char *copyfrom_relpath; - svn_revnum_t copyfrom_rev; -}; - -struct ev3_file_baton -{ - struct ev3_edit_baton *eb; - - /* Path of this file relative to repository root. */ - const char *path; - /* The base revision if this is a pre-existing file; - SVN_INVALID_REVNUM if added/copied. - ### Can also be SVN_INVALID_REVNUM for a pre-existing file, - meaning the base is the youngest revision. */ - svn_revnum_t base_revision; - - /* Copy-from path (relative to repository root) and revision. This is - set for each file inside a copy, not just the copy root. */ - const char *copyfrom_relpath; - svn_revnum_t copyfrom_rev; - - /* Path to a file containing the base text. */ - svn_stringbuf_t *delta_base_text; -}; - -/* Return the 'txn_path' that addresses the node that is currently at - * RELPATH according to the info in CHANGES. - * - * One way to describe it: - * - * If relpath is a created path: - * find_txn_path(its parent) - * add (its basename) to the created-path part - * elif relpath is an already-existing path: - * return txn_path(relpath, base rev, "") - * else: # it's deleted - * return None + * 1. Ev3 changes are accumulated in a per-path table, EB->changes. * - * Another way: + * 2. On Ev3 close-edit, walk through the table in a depth-first order, + * sending the equivalent Ev1 action for each change. * - * p := first path component that is add/copy, starting from root - * d := dirname(p) - * return txn_path(d, changing_rev(d), remainder-relpath) - */ -static svn_editor3_txn_path_t -find_txn_path(apr_hash_t *changes, - const char *relpath, - apr_pool_t *result_pool) -{ - const char *existing_path = "", *remainder_path = relpath; - svn_revnum_t existing_revnum = SVN_INVALID_REVNUM; - int i; - svn_editor3_txn_path_t loc; - - /* The root path was necessarily existing. For each further path component, - if it was existing, add it to the 'existing path', else stop there. */ - for (i = 1; *remainder_path; i++) - { - const char *new_prefix_path = svn_relpath_limit(relpath, i, result_pool); - change_node_t *change = svn_hash_gets(changes, new_prefix_path); - - if (change && change->action == RESTRUCTURE_ADD) - break; - - existing_path = new_prefix_path; - remainder_path = svn_relpath_skip_ancestor(existing_path, relpath); - if (change && change->action == RESTRUCTURE_NONE) - /* ### This is all well and good when there is a RESTRUCTURE_NONE - change recorded for this dir, but for an unchanged parent - dir we don't know what the base revision was ... unless we - record every 'opened' parent dir. Should we do that? */ - existing_revnum = change->changing_rev; - } - loc = txn_path(existing_path, existing_revnum, remainder_path); - return loc; -} - -/* Drive the Ev3 EB->editor to make the Ev1-style edits described by - * CHANGE for the path REPOS_RELPATH. + * TODO * - * Note: We do not support converting copy-and-delete to send an Ev3 move. - * This per-path model of processing is not well suited to doing so. - */ -static svn_error_t * -process_actions(struct ev3_edit_baton *eb, - const char *repos_relpath, - const change_node_t *change, - apr_pool_t *scratch_pool) -{ - svn_editor3_txn_path_t change_loc - = find_txn_path(eb->changes, repos_relpath, scratch_pool); - const char *repos_relpath_dirname = svn_relpath_dirname(repos_relpath, scratch_pool); - const char *repos_relpath_basename = svn_relpath_basename(repos_relpath, scratch_pool); - - SVN_ERR_ASSERT(change != NULL); - -#ifdef SHIM_WITH_UNLOCK - if (eb->shim_connector && change->unlock) - SVN_ERR(eb->shim_connector->unlock_func( - eb->shim_connector->baton, repos_relpath, scratch_pool)); -#endif - - /* Process any delete, no matter whether it will be replaced. */ - if (change->deleting) - { - SVN_ERR(svn_editor3_rm(eb->editor, - txn_path(repos_relpath, change->deleting_rev, ""))); - } - -#ifdef SHIM_WITH_ADD_ABSENT - if (change->action == RESTRUCTURE_ADD_ABSENT) - { - SVN_ERR(svn_editor3_mk(eb->editor, change->kind, - parent_loc, repos_relpath_basename)); - - /* No further work possible on this path. */ - return SVN_NO_ERROR; - } -#endif - - if (change->action == RESTRUCTURE_ADD) - { - svn_editor3_txn_path_t parent_loc - = find_txn_path(eb->changes, repos_relpath_dirname, scratch_pool); - - if (change->copyfrom_path != NULL) - { - SVN_ERR(svn_editor3_cp(eb->editor, -#ifdef SVN_EDITOR3_WITH_COPY_FROM_THIS_REV - txn_path(change->copyfrom_path, change->copyfrom_rev, ""), -#else - pathrev(change->copyfrom_path, change->copyfrom_rev), -#endif - parent_loc, repos_relpath_basename)); - /* Fall through to possibly make changes post-copy. */ - } - else - { - SVN_ERR(svn_editor3_mk(eb->editor, change->kind, - parent_loc, repos_relpath_basename)); - /* Fall through to make changes post-add. */ - } - } - - if (change->props || change->contents_changed) - { - svn_editor3_node_content_t *new_content; - - if (change->kind == svn_node_file) - { - svn_stringbuf_t *text; - - if (change->contents_text) - { - /*SVN_DBG(("contents_changed=%d, contents_text='%.20s...'", - change->contents_changed, change->contents_text->data));*/ - text = change->contents_text; - } - else - { - SVN_DBG(("file '%s', no content, act=%d, cp=%s@%ld", - repos_relpath, change->action, change->copyfrom_path, change->copyfrom_rev)); - /*### not: VERIFY(change->action == RESTRUCTURE_ADD && ! change->copyfrom_path);*/ - - /* If this file was added, but apply_txdelta() was not called (i.e. - CONTENTS_CHANGED is FALSE), we're adding an empty file. */ - text = svn_stringbuf_create_empty(scratch_pool); - } - - new_content = svn_editor3_node_content_create_file( - change->props, text, scratch_pool); - } - else if (change->kind == svn_node_dir) - { - new_content = svn_editor3_node_content_create_dir( - change->props, scratch_pool); - } - else - SVN_ERR_MALFUNCTION(); - SVN_ERR(svn_editor3_put(eb->editor, change_loc, new_content)); - } - - return SVN_NO_ERROR; -} - -/* */ -static svn_error_t * -run_actions(struct ev3_edit_baton *eb, - apr_pool_t *scratch_pool) -{ - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - int i; - -#ifdef SHIM_WITH_ACTIONS_DURING_ABORT - /* Possibly pick up where we left off. Occasionally, we do some of these - as part of close_edit() and then some more as part of abort_edit() */ - for (i = eb->paths_processed; i < eb->path_order->nelts; ++i, ++eb->paths_processed) -#else - for (i = 0; i < eb->path_order->nelts; ++i) -#endif - { - const char *repos_relpath - = APR_ARRAY_IDX(eb->path_order, i, const char *); - const change_node_t *change = svn_hash_gets(eb->changes, repos_relpath); - - /* Process the change for each path only once, no - matter how many times the Ev1 driver visited it. When we've - processed a path successfully, remove it from the queue. */ - if (change) - { - svn_pool_clear(iterpool); - - SVN_ERR(process_actions(eb, repos_relpath, change, iterpool)); - - /* Remove the action, as we've now processed it. */ - svn_hash_sets(eb->changes, repos_relpath, NULL); - } - } - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} - -/* Return the repository-relative path for a given Ev1 input path (that is, - * a relpath-within-edit or a URL). */ -static const char * -map_to_repos_relpath(struct ev3_edit_baton *eb, - const char *path_or_url, - apr_pool_t *result_pool) -{ - if (svn_path_is_url(path_or_url)) - { - return svn_uri_skip_ancestor(eb->repos_root_url, path_or_url, result_pool); - } - else - { - return svn_relpath_join(eb->base_relpath, - path_or_url[0] == '/' - ? path_or_url + 1 : path_or_url, - result_pool); - } -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_set_target_revision(void *edit_baton, - svn_revnum_t target_revision, - apr_pool_t *scratch_pool) -{ - struct ev3_edit_baton *eb = edit_baton; - - if (eb->shim_connector && eb->shim_connector->target_revision_func) - SVN_ERR(eb->shim_connector->target_revision_func( - eb->shim_connector->baton, target_revision, scratch_pool)); - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_open_root(void *edit_baton, - svn_revnum_t base_revision, - apr_pool_t *result_pool, - void **root_baton) -{ - struct ev3_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db)); - struct ev3_edit_baton *eb = edit_baton; - - db->eb = eb; - db->path = apr_pstrdup(eb->edit_pool, eb->base_relpath); - db->base_revision = base_revision; - - *root_baton = db; - - if (eb->shim_connector && eb->shim_connector->start_edit_func) - SVN_ERR(eb->shim_connector->start_edit_func( - eb->shim_connector->baton, base_revision)); - - return SVN_NO_ERROR; -} - -/* Record a property change in the (existing or new) change record for - * the node at RELPATH of kind KIND. Change property NAME to value VALUE, - * or delete the property if VALUE is NULL. - * - * Fetch and store the base properties for this node, using the callback - * EB->fetch_props_func/baton, if we have not yet done so. Then apply the - * edit to those base properties or to the set of properties resulting - * from the previous edit. - * - * BASE_REVISION is the base revision of the node that is currently at - * RELPATH, or SVN_INVALID_REVNUM for an added/copied node. COPYFROM_PATH - * and COPYFROM_REV are the base location for a copied node, including a - * child of a copy. - * - * RELPATH is relative to the repository root. - */ -static svn_error_t * -apply_propedit(struct ev3_edit_baton *eb, - const char *relpath, - svn_node_kind_t kind, - svn_revnum_t base_revision, - const char *copyfrom_path, - svn_revnum_t copyfrom_rev, - const char *name, - const svn_string_t *value, - apr_pool_t *scratch_pool) -{ - change_node_t *change; - - SVN_ERR(insert_change_ev1_rules(&change, eb->changes, relpath, - RESTRUCTURE_NONE, kind)); - - /* Record the observed order. */ - APR_ARRAY_PUSH(eb->path_order, const char *) - = apr_pstrdup(eb->edit_pool, relpath); - - /* We're changing the node, so record the base revision in case this is - the first change. (But we don't need to fill in the copy-from, as a - change entry would already have been recorded for a copy-root.) */ - VERIFY(!SVN_IS_VALID_REVNUM(change->changing_rev) - || change->changing_rev == base_revision); - change->changing_rev = base_revision; - - /* Fetch the original set of properties, if we haven't done so yet. */ - if (change->props == NULL) - { - /* If this is a copied/moved node, then the original properties come - from there. If the node has been added, it starts with empty props. - Otherwise, we get the properties from BASE. */ - if (copyfrom_path) - { - SVN_ERR(eb->fetch_func(NULL, &change->props, NULL, NULL, - eb->fetch_baton, - copyfrom_path, copyfrom_rev, - eb->edit_pool, scratch_pool)); - SVN_DBG(("apply_propedit('%s@%ld'): fetched %d copy-from props (from %s@%ld)", - relpath, base_revision, apr_hash_count(change->props), - copyfrom_path, copyfrom_rev)); - } - else if (change->action == RESTRUCTURE_ADD) - { - change->props = apr_hash_make(eb->edit_pool); - } - else - { - if (! SVN_IS_VALID_REVNUM(base_revision)) - SVN_DBG(("apply_propedit('%s@%ld') ### need to resolve to HEAD?", - relpath, base_revision)); - SVN_ERR(eb->fetch_func(NULL, &change->props, NULL, NULL, - eb->fetch_baton, - relpath, base_revision, - eb->edit_pool, scratch_pool)); - SVN_DBG(("apply_propedit('%s@%ld'): fetched %d original props", - relpath, base_revision, apr_hash_count(change->props))); - } - } - - if (value == NULL) - svn_hash_sets(change->props, name, NULL); - else - svn_hash_sets(change->props, - apr_pstrdup(eb->edit_pool, name), - svn_string_dup(value, eb->edit_pool)); - SVN_DBG(("apply_propedit('%s@%ld'): set prop %s=%.50s", - relpath, base_revision, name, value ? value->data : "")); - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_delete_entry(const char *path, - svn_revnum_t revision, - void *parent_baton, - apr_pool_t *scratch_pool) -{ - struct ev3_dir_baton *pb = parent_baton; - svn_revnum_t base_revision; - const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); - change_node_t *change; - - SVN_ERR(insert_change_ev1_rules(&change, pb->eb->changes, relpath, - RESTRUCTURE_DELETE, svn_node_none)); - - /* Record the observed order. */ - APR_ARRAY_PUSH(pb->eb->path_order, const char *) - = apr_pstrdup(pb->eb->edit_pool, relpath); - - if (SVN_IS_VALID_REVNUM(revision)) - base_revision = revision; - else - base_revision = pb->base_revision; - /* ### Shouldn't base_revision be SVN_INVALID_REVNUM instead, if the - node to delete was created (added/copied) in this edit? */ - - /* ### Should these checks be in insert_change()? */ - VERIFY(!change->deleting || change->deleting_rev == base_revision); - change->deleting = TRUE; - change->deleting_rev = base_revision; - if (!SVN_IS_VALID_REVNUM(base_revision)) - SVN_DBG(("ev3_delete_entry('%s'): deleting_rev = base_revision == -1", path)); - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_add_directory(const char *path, - void *parent_baton, - const char *copyfrom_path, - svn_revnum_t copyfrom_revision, - apr_pool_t *result_pool, - void **child_baton) -{ - apr_pool_t *scratch_pool = result_pool; - struct ev3_dir_baton *pb = parent_baton; - struct ev3_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb)); - const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); - change_node_t *change; - - SVN_ERR(insert_change_ev1_rules(&change, pb->eb->changes, relpath, - RESTRUCTURE_ADD, svn_node_dir)); - - /* Record the observed order. */ - APR_ARRAY_PUSH(pb->eb->path_order, const char *) - = apr_pstrdup(pb->eb->edit_pool, relpath); - - cb->eb = pb->eb; - cb->path = apr_pstrdup(result_pool, relpath); - cb->base_revision = SVN_INVALID_REVNUM; - *child_baton = cb; - - if (!copyfrom_path) - { - if (pb->copyfrom_relpath) - { - const char *name = svn_relpath_basename(relpath, scratch_pool); - cb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name, - result_pool); - cb->copyfrom_rev = pb->copyfrom_rev; - } - } - else - { - /* A copy */ - - change->copyfrom_path = map_to_repos_relpath(pb->eb, copyfrom_path, - pb->eb->edit_pool); - change->copyfrom_rev = copyfrom_revision; - - cb->copyfrom_relpath = change->copyfrom_path; - cb->copyfrom_rev = change->copyfrom_rev; - } - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_open_directory(const char *path, - void *parent_baton, - svn_revnum_t base_revision, - apr_pool_t *result_pool, - void **child_baton) -{ - apr_pool_t *scratch_pool = result_pool; - struct ev3_dir_baton *pb = parent_baton; - struct ev3_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db)); - const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); - - db->eb = pb->eb; - db->path = apr_pstrdup(result_pool, relpath); - db->base_revision = base_revision; - if (! SVN_IS_VALID_REVNUM(base_revision)) - SVN_DBG(("ev3_open_directory('%s', base_revision == -1) ### need to resolve to HEAD?", path)); - - if (pb->copyfrom_relpath) - { - /* We are inside a copy. */ - const char *name = svn_relpath_basename(relpath, scratch_pool); - - db->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name, - result_pool); - db->copyfrom_rev = pb->copyfrom_rev; - } - - *child_baton = db; - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_change_dir_prop(void *dir_baton, - const char *name, - const svn_string_t *value, - apr_pool_t *scratch_pool) -{ - struct ev3_dir_baton *db = dir_baton; - - SVN_ERR(apply_propedit(db->eb, db->path, svn_node_dir, - db->base_revision, db->copyfrom_relpath, db->copyfrom_rev, - name, value, scratch_pool)); - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_close_directory(void *dir_baton, - apr_pool_t *scratch_pool) -{ - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_add_file(const char *path, - void *parent_baton, - const char *copyfrom_path, - svn_revnum_t copyfrom_revision, - apr_pool_t *result_pool, - void **file_baton) -{ - apr_pool_t *scratch_pool = result_pool; - struct ev3_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb)); - struct ev3_dir_baton *pb = parent_baton; - const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); - change_node_t *change; - - SVN_ERR(insert_change_ev1_rules(&change, pb->eb->changes, relpath, - RESTRUCTURE_ADD, svn_node_file)); - - /* Record the observed order. */ - APR_ARRAY_PUSH(pb->eb->path_order, const char *) - = apr_pstrdup(pb->eb->edit_pool, relpath); - - fb->eb = pb->eb; - fb->path = apr_pstrdup(result_pool, relpath); - fb->base_revision = SVN_INVALID_REVNUM; - *file_baton = fb; - - /* Fetch the base text as a file FB->delta_base, if it's a copy */ - if (copyfrom_path) - { - change->copyfrom_path = map_to_repos_relpath(fb->eb, copyfrom_path, - fb->eb->edit_pool); - change->copyfrom_rev = copyfrom_revision; - - SVN_ERR(fb->eb->fetch_func(NULL, NULL, &fb->delta_base_text, NULL, - fb->eb->fetch_baton, - change->copyfrom_path, - change->copyfrom_rev, - result_pool, scratch_pool)); - fb->copyfrom_relpath = change->copyfrom_path; - fb->copyfrom_rev = change->copyfrom_rev; - } - else - { - /* It's a plain add -- we don't have a base. */ - fb->delta_base_text = NULL; - - if (pb->copyfrom_relpath) - { - const char *name = svn_relpath_basename(relpath, scratch_pool); - fb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name, - result_pool); - fb->copyfrom_rev = pb->copyfrom_rev; - } - } - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_open_file(const char *path, - void *parent_baton, - svn_revnum_t base_revision, - apr_pool_t *result_pool, - void **file_baton) -{ - apr_pool_t *scratch_pool = result_pool; - struct ev3_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb)); - struct ev3_dir_baton *pb = parent_baton; - const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); - - SVN_DBG(("ev3_open_file(%s@%ld)", path, base_revision)); - - fb->eb = pb->eb; - fb->path = apr_pstrdup(result_pool, relpath); - fb->base_revision = base_revision; - if (! SVN_IS_VALID_REVNUM(base_revision)) - SVN_DBG(("ev3_open_file(%s@%ld): base_revision == -1; should we resolve to head?", - path, base_revision)); - - if (pb->copyfrom_relpath) - { - /* We're in a copied directory, so delta is based on copy source. */ - const char *name = svn_relpath_basename(relpath, scratch_pool); - const char *copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, - name, scratch_pool); - - fb->copyfrom_relpath = copyfrom_relpath; - fb->copyfrom_rev = pb->copyfrom_rev; - SVN_ERR(fb->eb->fetch_func(NULL, NULL, &fb->delta_base_text, NULL, - fb->eb->fetch_baton, - copyfrom_relpath, pb->copyfrom_rev, - result_pool, scratch_pool)); - } - else - { - SVN_ERR(fb->eb->fetch_func(NULL, NULL, &fb->delta_base_text, NULL, - fb->eb->fetch_baton, - relpath, base_revision, - result_pool, scratch_pool)); - } - - *file_baton = fb; - return SVN_NO_ERROR; -} - -struct ev3_handler_baton -{ - svn_txdelta_window_handler_t apply_handler; - void *apply_baton; - - svn_stream_t *source; - - apr_pool_t *pool; -}; - -static svn_error_t * -ev3_window_handler(svn_txdelta_window_t *window, void *baton) -{ - struct ev3_handler_baton *hb = baton; - svn_error_t *err; - - /* - if (! window) - SVN_DBG(("ev3 window handler(%s): END", - hb->fb->path)); - else if (window->new_data) - SVN_DBG(("ev3 window handler(%s): with new data [%d]'%s'", - hb->fb->path, (int)window->new_data->len, window->new_data->data)); - else - SVN_DBG(("ev3 window handler(%s): with no new data", - hb->fb->path)); - */ - err = hb->apply_handler(window, hb->apply_baton); - if (window != NULL && !err) - return SVN_NO_ERROR; - - SVN_ERR(svn_stream_close(hb->source)); - - svn_pool_destroy(hb->pool); - - return svn_error_trace(err); -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_apply_textdelta(void *file_baton, - const char *base_checksum, - apr_pool_t *result_pool, - svn_txdelta_window_handler_t *handler, - void **handler_baton) -{ - struct ev3_file_baton *fb = file_baton; - apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool); - struct ev3_handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb)); - change_node_t *change; - svn_stream_t *target; - - SVN_ERR(insert_change_ev1_rules(&change, fb->eb->changes, fb->path, - RESTRUCTURE_NONE, svn_node_file)); - - /* Record the observed order. */ - APR_ARRAY_PUSH(fb->eb->path_order, const char *) - = apr_pstrdup(fb->eb->edit_pool, fb->path); - - /* The content for this path must be changed only once. (Not explicitly - mandated by svn_delta_editor, but we'll assume it is mandatory.) */ - VERIFY(!change->contents_changed); - change->contents_changed = TRUE; - - /* This can come after property changes or no changes or an add. */ - VERIFY(!SVN_IS_VALID_REVNUM(change->changing_rev) - || change->changing_rev == fb->base_revision); - change->changing_rev = fb->base_revision; - - if (! fb->delta_base_text) - { - /*SVN_DBG(("apply_textdelta(%s): preparing read from delta-base ", - fb->path));*/ - hb->source = svn_stream_empty(handler_pool); - } - else - { - /*SVN_DBG(("apply_textdelta(%s): preparing read of delta-base '%.20s...'", - fb->path, fb->delta_base_text));*/ - hb->source = svn_stream_from_stringbuf(fb->delta_base_text, handler_pool); - } - - change->contents_text = svn_stringbuf_create_empty(fb->eb->edit_pool); - target = svn_stream_from_stringbuf(change->contents_text, fb->eb->edit_pool); - - svn_txdelta_apply(hb->source, target, - NULL, NULL, - handler_pool, - &hb->apply_handler, &hb->apply_baton); - - hb->pool = handler_pool; - - *handler_baton = hb; - *handler = ev3_window_handler; - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_change_file_prop(void *file_baton, - const char *name, - const svn_string_t *value, - apr_pool_t *scratch_pool) -{ - struct ev3_file_baton *fb = file_baton; - -#ifdef SHIM_WITH_UNLOCK - if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL) - { - /* We special case the lock token propery deletion, which is the - server's way of telling the client to unlock the path. */ - - /* ### this duplicates much of apply_propedit(). fix in future. */ - const char *relpath = map_to_repos_relpath(fb->eb, fb->path, - scratch_pool); - change_node_t *change = locate_change(fb->eb, relpath); - - change->unlock = TRUE; - } -#endif - - SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, - fb->base_revision, fb->copyfrom_relpath, fb->copyfrom_rev, - name, value, scratch_pool)); - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_close_file(void *file_baton, - const char *text_checksum, - apr_pool_t *scratch_pool) -{ - struct ev3_file_baton *fb = file_baton; - change_node_t *change = svn_hash_gets(fb->eb->changes, fb->path); - - /* If this file is being modified, or copied, and apply_txdelta() - was not called (i.e. CONTENTS_CHANGED is FALSE), then there is - no change to the content. We must fetch the original content - in order to tell Ev3 not to change it. - (### Or we could retract the changing of this file entirely - if there were no prop changes either.) - - The only exception is for a new, empty file, where we leave - CONTENTS_CHANGED false for now (and CONTENTS_ABSPATH null), and - generate an empty stream for it later. */ - /*SVN_DBG(("close_file(%s): action=%d, contents_changed=%d, contents_abspath='%s'", - fb->path, change->action, change->contents_changed, change->contents_abspath));*/ - if (! change->contents_changed - && (change->action == RESTRUCTURE_NONE || change->copyfrom_path)) - { - change->contents_changed = TRUE; - change->contents_text = svn_stringbuf_dup(fb->delta_base_text, - fb->eb->edit_pool); - SVN_DBG(("close_file(%s): unchanged => use base text '%.20s...'", - fb->path, change->contents_text->data)); - } - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_absent_directory(const char *path, - void *parent_baton, - apr_pool_t *scratch_pool) -{ -#ifdef SHIM_WITH_ADD_ABSENT - struct ev3_dir_baton *pb = parent_baton; - const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); - change_node_t *change; - - SVN_ERR(insert_change_ev1_rules(&change, eb->changes, relpath, - RESTRUCTURE_ADD_ABSENT, svn_node_dir)); - - /* Record the observed order. */ - APR_ARRAY_PUSH(pb->eb->path_order, const char *) - = apr_pstrdup(pb->eb->edit_pool, path); -#endif - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_absent_file(const char *path, - void *parent_baton, - apr_pool_t *scratch_pool) -{ -#ifdef SHIM_WITH_ADD_ABSENT - struct ev3_dir_baton *pb = parent_baton; - const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool); - change_node_t *change; - - SVN_ERR(insert_change_ev1_rules(&change, eb->changes, relpath, - RESTRUCTURE_ADD_ABSENT, svn_node_file)); - - /* Record the observed order. */ - APR_ARRAY_PUSH(pb->eb->path_order, const char *) - = apr_pstrdup(pb->eb->edit_pool, path); -#endif - - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_close_edit(void *edit_baton, - apr_pool_t *scratch_pool) -{ - struct ev3_edit_baton *eb = edit_baton; - - SVN_ERR(run_actions(edit_baton, scratch_pool)); - eb->closed = TRUE; - SVN_ERR(svn_editor3_complete(eb->editor)); - return SVN_NO_ERROR; -} - -/* An svn_delta_editor_t method. */ -static svn_error_t * -ev3_abort_edit(void *edit_baton, - apr_pool_t *scratch_pool) -{ - struct ev3_edit_baton *eb = edit_baton; - -#ifdef SHIM_WITH_ACTIONS_DURING_ABORT - SVN_ERR(run_actions(edit_baton, scratch_pool)); -#endif - - if (!eb->closed) - SVN_ERR(svn_editor3_abort(eb->editor)); - return SVN_NO_ERROR; -} - -svn_error_t * -svn_delta__delta_from_ev3_for_commit( - const svn_delta_editor_t **deditor, - void **dedit_baton, - svn_editor3_t *editor, - const char *repos_root_url, - const char *base_relpath, - svn_editor3__shim_fetch_func_t fetch_func, - void *fetch_baton, - const svn_editor3__shim_connector_t *shim_connector, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - static const svn_delta_editor_t delta_editor = { - ev3_set_target_revision, - ev3_open_root, - ev3_delete_entry, - ev3_add_directory, - ev3_open_directory, - ev3_change_dir_prop, - ev3_close_directory, - ev3_absent_directory, - ev3_add_file, - ev3_open_file, - ev3_apply_textdelta, - ev3_change_file_prop, - ev3_close_file, - ev3_absent_file, - ev3_close_edit, - ev3_abort_edit - }; - struct ev3_edit_baton *eb = apr_pcalloc(result_pool, sizeof(*eb)); - - if (!base_relpath) - base_relpath = ""; - else if (base_relpath[0] == '/') - base_relpath += 1; - - eb->editor = editor; - eb->changes = apr_hash_make(result_pool); - eb->path_order = apr_array_make(result_pool, 1, sizeof(const char *)); - eb->edit_pool = result_pool; - eb->shim_connector = shim_connector; - if (shim_connector) - { -#ifdef SHIM_WITH_ABS_PATHS - *eb->shim_connector->ev1_absolute_paths = FALSE; -#endif - } - eb->repos_root_url = apr_pstrdup(result_pool, repos_root_url); - eb->base_relpath = apr_pstrdup(result_pool, base_relpath); - - eb->fetch_func = fetch_func; - eb->fetch_baton = fetch_baton; - - *dedit_baton = eb; - *deditor = &delta_editor; - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_delta__delta_from_ev3_for_update( - const svn_delta_editor_t **deditor, - void **dedit_baton, - svn_update_editor3_t *update_editor, - const char *repos_root_url, - const char *base_repos_relpath, - svn_editor3__shim_fetch_func_t fetch_func, - void *fetch_baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_editor3__shim_connector_t *shim_connector - = apr_pcalloc(result_pool, sizeof(*shim_connector)); - - shim_connector->target_revision_func = update_editor->set_target_revision_func; - shim_connector->baton = update_editor->set_target_revision_baton; -#ifdef SHIM_WITH_ABS_PATHS - shim_connector->ev1_absolute_paths /*...*/; -#endif - - SVN_ERR(svn_delta__delta_from_ev3_for_commit( - deditor, dedit_baton, - update_editor->editor, - repos_root_url, base_repos_relpath, - fetch_func, fetch_baton, - shim_connector, - result_pool, scratch_pool)); - SVN_ERR(svn_delta__get_debug_editor(deditor, dedit_baton, - *deditor, *dedit_baton, - "[UP>1] ", result_pool)); - - return SVN_NO_ERROR; -} - - -/* - * =================================================================== - * Commit Editor converter to join a v3 driver to a v1 consumer - * =================================================================== - * - * This editor buffers all the changes before driving the Ev1 at the end, - * since it needs to do a single depth-first traversal of the path space - * and this cannot be started until all moves are known. - * - * Moves are converted to copy-and-delete, with the copy being from - * the source peg rev. (### Should it request copy-from revision "-1"?) - * - * It works like this: - * - * +------+--------+ - * | path | change | - * Ev3 --> +------+--------+ --> Ev1 - * | ... | ... | - * | ... | ... | - * - * 1. Ev3 changes are accumulated in a per-path table, EB->changes. - * - * 2. On Ev3 close-edit, walk through the table in a depth-first order, - * sending the equivalent Ev1 action for each change. - * - * TODO - * - * ### For changes inside a copied subtree, the calls to the "open dir" - * and "open file" Ev1 methods may be passing the wrong revision - * number: see comment in apply_change(). - * - * ### Have we got our rel-paths in order? Ev1, Ev3 and callbacks may - * all expect different paths. 'repos_relpath' or relative to - * eb->base_relpath? Leading slash (unimplemented 'send_abs_paths' - * feature), etc. - * - * ### May be tidier for OPEN_ROOT_FUNC callback (see open_root_ev3()) - * not to actually open the root in advance, but instead just to - * remember the base revision that the driver wants us to specify - * when we do open it. - */ - - -/* Record a move of a subtree from INITIAL_RELPATH to CURRENT_RELPATH. - */ -static void -record_move(apr_hash_t *moves, - const char *initial_relpath, - const char *current_relpath) -{ - apr_pool_t *pool = apr_hash_pool_get(moves); - - svn_hash_sets(moves, apr_pstrdup(pool, initial_relpath), - apr_pstrdup(pool, current_relpath)); -} - -/* Return the path to which INITIAL_RELPATH would be moved, according to - * the information in MOVES. Return INITIAL_RELPATH unchanged if it would - * not be moved. - */ -static const char * -find_move(apr_hash_t *moves, - const char *initial_relpath, - apr_pool_t *scratch_pool) -{ - const char *p = initial_relpath; - apr_hash_index_t *hi; - - /* follow moves: find the longest matching prefix */ - for (hi = apr_hash_first(scratch_pool, moves); - hi; hi = apr_hash_next(hi)) - { - const char *this_from_relpath = apr_hash_this_key(hi); - const char *this_to_relpath = apr_hash_this_val(hi); - const char *r - = svn_relpath_skip_ancestor(this_from_relpath, initial_relpath); - - if (r) - { - p = svn_relpath_join(this_to_relpath, r, scratch_pool); - /* look for a longer match unless we found an exact match */ - if (r[0] == '\0') - break; - } - } - - return p; -} - - -/* - * ======================================================================== - * Element-Based Branching - * ======================================================================== - */ - -svn_branch_repos_t * -svn_branch_repos_create(apr_pool_t *result_pool) -{ - svn_branch_repos_t *repos = apr_pcalloc(result_pool, sizeof(*repos)); - - repos->rev_roots = apr_array_make(result_pool, 1, sizeof(void *)); - repos->families = apr_hash_make(result_pool); - repos->pool = result_pool; - return repos; -} - -/* Find the existing family with id FID in REPOS. - * - * Return NULL if not found. - * - * Note: An FID is unique among all families. - */ -static svn_branch_family_t * -repos_get_family_by_id(svn_branch_repos_t *repos, - int fid) -{ - return apr_hash_get(repos->families, &fid, sizeof(fid)); -} - -static void -repos_register_family(svn_branch_repos_t *repos, - svn_branch_family_t *family) -{ - int fid = family->fid; - - apr_hash_set(repos->families, - apr_pmemdup(repos->pool, &fid, sizeof(fid)), sizeof(fid), - family); -} - -svn_branch_revision_root_t * -svn_branch_revision_root_create(svn_branch_repos_t *repos, - svn_revnum_t rev, - struct svn_branch_instance_t *root_branch, - apr_pool_t *result_pool) -{ - svn_branch_revision_root_t *rev_root - = apr_pcalloc(result_pool, sizeof(*rev_root)); - - rev_root->repos = repos; - rev_root->rev = rev; - rev_root->root_branch = root_branch; - rev_root->branch_instances = apr_array_make(result_pool, 1, sizeof(void *)); - return rev_root; -} - -svn_branch_family_t * -svn_branch_family_create(svn_branch_repos_t *repos, - int fid, - int first_bid, - int next_bid, - int first_eid, - int next_eid, - apr_pool_t *result_pool) -{ - svn_branch_family_t *f = apr_pcalloc(result_pool, sizeof(*f)); - - f->fid = fid; - f->repos = repos; - f->branch_siblings = apr_array_make(result_pool, 1, sizeof(void *)); - f->sub_families = apr_array_make(result_pool, 1, sizeof(void *)); - f->first_bid = first_bid; - f->next_bid = next_bid; - f->first_eid = first_eid; - f->next_eid = next_eid; - f->pool = result_pool; - return f; -} - -/* Assign a new element id in FAMILY. - */ -static int -family_add_new_element(svn_branch_family_t *family) -{ - int eid = family->next_eid++; - - return eid; -} - -/* Create a new, empty family in OUTER_FAMILY. - */ -static svn_branch_family_t * -family_add_new_subfamily(svn_branch_family_t *outer_family) -{ - svn_branch_repos_t *repos = outer_family->repos; - int fid = repos->next_fid++; - svn_branch_family_t *family - = svn_branch_family_create(repos, fid, - fid * 10, fid * 10, - fid * 100, fid * 100, - outer_family->pool); - - /* Register the family */ - repos_register_family(repos, family); - APR_ARRAY_PUSH(outer_family->sub_families, void *) = family; - - return family; -} - -/* Create a new branch sibling in FAMILY, with branch id BID and - * root element ROOT_EID, and register it as a member of the family. - */ -static svn_branch_sibling_t * -family_create_branch_sibling(svn_branch_family_t *family, - int bid, - int root_eid) -{ - svn_branch_sibling_t *branch_sibling - = svn_branch_sibling_create(family, bid, root_eid, family->pool); - - /* The root EID must be an existing EID. */ - SVN_ERR_ASSERT_NO_RETURN(root_eid >= family->first_eid - /*&& root_eid < family->next_eid*/); - /* ROOT_RRPATH must not be within another branch of the family. */ - - /* Register the branch */ - APR_ARRAY_PUSH(family->branch_siblings, void *) = branch_sibling; - - return branch_sibling; -} - -/* Return the branch sibling definition with branch id BID in FAMILY. - * - * Return NULL if not found. - */ -static svn_branch_sibling_t * -family_find_branch_sibling(svn_branch_family_t *family, - int bid) -{ - int i; - - for (i = 0; i < family->branch_siblings->nelts; i++) - { - svn_branch_sibling_t *this - = APR_ARRAY_IDX(family->branch_siblings, i, void *); - - if (this->bid == bid) - return this; - } - return NULL; -} - -/* Return an existing (if found) or new (otherwise) branch sibling - * definition object with id BID and root-eid ROOT_EID in FAMILY. - */ -static svn_branch_sibling_t * -family_find_or_create_branch_sibling(svn_branch_family_t *family, - int bid, - int root_eid) -{ - svn_branch_sibling_t *sibling = family_find_branch_sibling(family, bid); - - if (!sibling) - { - sibling = family_create_branch_sibling(family, bid, root_eid); - } - - SVN_ERR_ASSERT_NO_RETURN(sibling->root_eid == root_eid); - return sibling; -} - -/* Add a new branch sibling definition to FAMILY, with root element id - * ROOT_EID. - */ -static svn_branch_sibling_t * -family_add_new_branch_sibling(svn_branch_family_t *family, - int root_eid) -{ - int bid = family->next_bid++; - svn_branch_sibling_t *branch_sibling - = family_create_branch_sibling(family, bid, root_eid); - - return branch_sibling; -} - -apr_array_header_t * -svn_branch_family_get_children(svn_branch_family_t *family, - apr_pool_t *result_pool) -{ - return family->sub_families; -} - -apr_array_header_t * -svn_branch_family_get_branch_instances( - svn_branch_revision_root_t *rev_root, - svn_branch_family_t *family, - apr_pool_t *result_pool) -{ - apr_array_header_t *rev_branch_instances = rev_root->branch_instances; - apr_array_header_t *fam_branch_instances - = apr_array_make(result_pool, 0, sizeof(void *)); - int i; - - for (i = 0; i < rev_branch_instances->nelts; i++) - { - svn_branch_instance_t *branch - = APR_ARRAY_IDX(rev_branch_instances, i, svn_branch_instance_t *); - - if (branch->sibling_defn->family == family) - APR_ARRAY_PUSH(fam_branch_instances, void *) = branch; - } - - return fam_branch_instances; -} - -svn_branch_sibling_t * -svn_branch_sibling_create(svn_branch_family_t *family, - int bid, - int root_eid, - apr_pool_t *result_pool) -{ - svn_branch_sibling_t *b = apr_pcalloc(result_pool, sizeof(*b)); - - SVN_ERR_ASSERT_NO_RETURN(bid >= family->first_bid - && bid < family->next_bid); - SVN_ERR_ASSERT_NO_RETURN(root_eid >= family->first_eid - && root_eid < family->next_eid); - - b->family = family; - b->bid = bid; - b->root_eid = root_eid; - return b; -} - -svn_branch_instance_t * -svn_branch_instance_create(svn_branch_sibling_t *branch_sibling, - svn_branch_revision_root_t *rev_root, - const char *branch_root_rrpath, - apr_pool_t *result_pool) -{ - svn_branch_instance_t *b = apr_pcalloc(result_pool, sizeof(*b)); - - b->sibling_defn = branch_sibling; - b->rev_root = rev_root; - b->e_map = apr_hash_make(result_pool); - b->branch_root_rrpath = apr_pstrdup(result_pool, branch_root_rrpath); - return b; -} - -svn_branch_el_rev_id_t * -svn_branch_el_rev_id_create(svn_branch_instance_t *branch, - int eid, - svn_revnum_t rev, - apr_pool_t *result_pool) -{ - svn_branch_el_rev_id_t *id = apr_palloc(result_pool, sizeof(*id)); - - id->branch = branch; - id->eid = eid; - id->rev = rev; - return id; -} - -svn_branch_el_rev_content_t * -svn_branch_el_rev_content_create(svn_editor3_eid_t parent_eid, - const char *name, - const svn_editor3_node_content_t *node_content, - apr_pool_t *result_pool) -{ - svn_branch_el_rev_content_t *content - = apr_palloc(result_pool, sizeof(*content)); - - content->parent_eid = parent_eid; - content->name = apr_pstrdup(result_pool, name); - content->content = svn_editor3_node_content_dup(node_content, result_pool); - return content; -} - -svn_branch_el_rev_content_t * -svn_branch_el_rev_content_dup(const svn_branch_el_rev_content_t *old, - apr_pool_t *result_pool) -{ - svn_branch_el_rev_content_t *content - = apr_pmemdup(result_pool, old, sizeof(*content)); - - content->name = apr_pstrdup(result_pool, old->name); - content->content = svn_editor3_node_content_dup(old->content, result_pool); - return content; -} - -svn_boolean_t -svn_branch_el_rev_content_equal(const svn_branch_el_rev_content_t *content_left, - const svn_branch_el_rev_content_t *content_right, - apr_pool_t *scratch_pool) -{ - if (!content_left && !content_right) - { - return TRUE; - } - else if (!content_left || !content_right) - { - return FALSE; - } - - if (content_left->parent_eid != content_right->parent_eid) - { - return FALSE; - } - if (strcmp(content_left->name, content_right->name) != 0) - { - return FALSE; - } - if (! svn_editor3_node_content_equal(content_left->content, - content_right->content, - scratch_pool)) - { - return FALSE; - } - - return TRUE; -} - - -/* - * ======================================================================== - * Branch mappings - * ======================================================================== - */ - -const char * -svn_branch_get_root_rrpath(const svn_branch_instance_t *branch) -{ - const char *root_rrpath = branch->branch_root_rrpath; - - SVN_ERR_ASSERT_NO_RETURN(root_rrpath); - return root_rrpath; -} - -/* Validate that NODE is suitable for a mapping of BRANCH:EID. - * NODE->content may be null. - */ -static void -branch_map_node_validate(const svn_branch_instance_t *branch, - int eid, - const svn_branch_el_rev_content_t *node) -{ - SVN_ERR_ASSERT_NO_RETURN(node); - - /* Parent EID must be valid, or -1 iff EID is the branch root. */ - SVN_ERR_ASSERT_NO_RETURN( - (eid == branch->sibling_defn->root_eid) - ? (node->parent_eid == -1) - : (node->parent_eid >= branch->sibling_defn->family->first_eid - && node->parent_eid < branch->sibling_defn->family->next_eid)); - - /* Node name must be given, and empty iff EID is the branch root. */ - SVN_ERR_ASSERT_NO_RETURN( - node->name - && (eid == branch->sibling_defn->root_eid) == (*node->name == '\0')); - - /* Content, if specified, must be in full or by reference. */ - if (node->content) - SVN_ERR_ASSERT_NO_RETURN(node->content - && ((SVN_IS_VALID_REVNUM(node->content->ref.rev) - && node->content->ref.relpath) - || (node->content->kind != svn_node_unknown - && node->content->kind != svn_node_none))); -} - -/* In BRANCH, get element EID's node (parent, name, content). - * - * If element EID is not present, return null. Otherwise, the returned - * node's content may be null meaning it is unknown. - */ -static svn_branch_el_rev_content_t * -branch_map_get(const svn_branch_instance_t *branch, - int eid) -{ - svn_branch_el_rev_content_t *node; - - SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid - && eid < branch->sibling_defn->family->next_eid); - - node = apr_hash_get(branch->e_map, &eid, sizeof(eid)); - - if (node) - branch_map_node_validate(branch, eid, node); - return node; -} - -/* In BRANCH, set element EID's node (parent, name, content) to NODE. - * - * If NODE is null, delete element EID. Otherwise, NODE->content may be - * null meaning it is unknown. - * - * Assume NODE is already allocated with sufficient lifetime. - */ -static void -branch_map_set(svn_branch_instance_t *branch, - int eid, - svn_branch_el_rev_content_t *node) -{ - apr_pool_t *map_pool = apr_hash_pool_get(branch->e_map); - int *eid_p = apr_pmemdup(map_pool, &eid, sizeof(eid)); - - SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid - && eid < branch->sibling_defn->family->next_eid); - if (node) - branch_map_node_validate(branch, eid, node); - - apr_hash_set(branch->e_map, eid_p, sizeof(*eid_p), node); -} - -/* In BRANCH, delete element EID. - */ -static void -branch_map_delete(svn_branch_instance_t *branch, - int eid) -{ - SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid - && eid < branch->sibling_defn->family->next_eid); - - branch_map_set(branch, eid, NULL); -} - -/* Set or change the EID:element mapping for EID in BRANCH. - * - * Duplicate NEW_NAME and NEW_CONTENT into the branch mapping's pool. - */ -static void -branch_map_update(svn_branch_instance_t *branch, - int eid, - svn_editor3_eid_t new_parent_eid, - const char *new_name, - const svn_editor3_node_content_t *new_content) -{ - apr_pool_t *map_pool = apr_hash_pool_get(branch->e_map); - svn_branch_el_rev_content_t *node - = svn_branch_el_rev_content_create(new_parent_eid, new_name, new_content, - map_pool); - - /* EID must be a valid element id of the branch family */ - SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid - && eid < branch->sibling_defn->family->next_eid); - /* NEW_CONTENT must be specified, either in full or by reference */ - SVN_ERR_ASSERT_NO_RETURN(new_content); - - /* We don't expect to be called more than once per eid. */ - /*SVN_ERR_ASSERT_NO_RETURN(branch_map_get(branch, eid) == NULL); ### hmm, no! */ - - /* Insert the new version */ - branch_map_set(branch, eid, node); -} - -/* Set or change the EID:element mapping for EID in BRANCH to reflect a - * subbranch root node. This node has no content in this branch; the - * corresponding element of the subbranch will define its content. - * - * Duplicate NEW_NAME into the branch mapping's pool. - */ -static void -branch_map_update_as_subbranch_root(svn_branch_instance_t *branch, - int eid, - svn_editor3_eid_t new_parent_eid, - const char *new_name) -{ - apr_pool_t *map_pool = apr_hash_pool_get(branch->e_map); - svn_branch_el_rev_content_t *node - = svn_branch_el_rev_content_create(new_parent_eid, new_name, NULL /*content*/, - map_pool); - - /* EID must be a valid element id of the branch family */ - SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid - && eid < branch->sibling_defn->family->next_eid); - branch_map_node_validate(branch, eid, node); - - /* We don't expect to be called more than once per eid. */ - /*SVN_ERR_ASSERT_NO_RETURN(branch_map_get(branch, eid) == NULL); ### hmm, no! */ - - /* Insert the new version */ - branch_map_set(branch, eid, node); -} - -/* Remove from BRANCH's mapping any elements that do not have a complete - * line of parents to the branch root. In other words, remove elements - * that have been implicitly deleted. - * - * This does not remove subbranches. - */ -static void -branch_map_purge_orphans(svn_branch_instance_t *branch, - apr_pool_t *scratch_pool) -{ - apr_hash_index_t *hi; - svn_boolean_t changed; - - do - { - changed = FALSE; - - for (hi = apr_hash_first(scratch_pool, branch->e_map); - hi; hi = apr_hash_next(hi)) - { - int this_eid = *(const int *)apr_hash_this_key(hi); - svn_branch_el_rev_content_t *this_node = apr_hash_this_val(hi); - - if (this_node->parent_eid != -1 - && ! branch_map_get(branch, this_node->parent_eid)) - { - SVN_DBG(("purge orphan: e%d", this_eid)); - branch_map_delete(branch, this_eid); - changed = TRUE; - } - } - } - while (changed); -} - -const char * -svn_branch_get_path_by_eid(const svn_branch_instance_t *branch, - int eid, - apr_pool_t *result_pool) -{ - const char *path = ""; - svn_branch_el_rev_content_t *node; - - SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid - && eid < branch->sibling_defn->family->next_eid); - - for (; eid != branch->sibling_defn->root_eid; eid = node->parent_eid) - { - node = branch_map_get(branch, eid); - if (! node) - return NULL; - path = svn_relpath_join(node->name, path, result_pool); - } - SVN_ERR_ASSERT_NO_RETURN(eid == branch->sibling_defn->root_eid); - return path; -} - -const char * -svn_branch_get_rrpath_by_eid(const svn_branch_instance_t *branch, - int eid, - apr_pool_t *result_pool) -{ - const char *path = svn_branch_get_path_by_eid(branch, eid, result_pool); - const char *rrpath = NULL; - - if (path) - { - rrpath = svn_relpath_join(svn_branch_get_root_rrpath(branch), - path, result_pool); - } - return rrpath; -} - -/* Assuming the mapping has complete paths from the root to each element, - * get the EID for path PATH which is relative to the root of the branch. - */ -static int -branch_map_get_eid_by_path(const svn_branch_instance_t *branch, - const char *path, - apr_pool_t *scratch_pool) -{ - apr_hash_index_t *hi; - - /* ### This is a crude, linear search */ - for (hi = apr_hash_first(scratch_pool, branch->e_map); - hi; hi = apr_hash_next(hi)) - { - int eid = *(const int *)apr_hash_this_key(hi); - const char *this_path = svn_branch_get_path_by_eid(branch, eid, - scratch_pool); - - if (! this_path) - { - /* Mapping is not complete; this element is in effect not present. */ - continue; - } - if (strcmp(path, this_path) == 0) - { - return eid; - } - } - - return -1; -} - -/* Assuming the mapping has complete paths from the root to each element, - * get the EID for path PATH which is relative to the root of the branch. - */ -static int -branch_map_get_eid_by_rrpath(svn_branch_instance_t *branch, - const char *rrpath, - apr_pool_t *scratch_pool) -{ - const char *path = svn_relpath_skip_ancestor(svn_branch_get_root_rrpath(branch), - rrpath); - int eid = -1; - - if (path) - { - eid = branch_map_get_eid_by_path(branch, path, scratch_pool); - } - return eid; -} - -/* Get an element's content (props, text, ...) in full or by reference. - */ -static svn_error_t * -copy_content_from(svn_editor3_node_content_t **content_p, - svn_branch_instance_t *from_branch, - int from_eid, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_branch_el_rev_content_t *old_el = branch_map_get(from_branch, from_eid); - svn_editor3_node_content_t *content = old_el->content; - - /* If content is unknown, then presumably this is a committed rev and - so we can provide a reference to the committed content. */ - if (! content) - { - svn_editor3_peg_path_t peg; - - SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(from_branch->rev_root->rev)); - peg.rev = from_branch->rev_root->rev; - peg.relpath = svn_branch_get_rrpath_by_eid(from_branch, from_eid, - scratch_pool); - content = svn_editor3_node_content_create_ref(peg, result_pool); - } - *content_p = content; - return SVN_NO_ERROR; -} - -/* In BRANCH, update the path mappings to delete all children of EID, - * recursively (but not deleting subbranches). - * - * EID may be any element id in BRANCH: existing or nonexistent, root or - * normal or subbranch root (useful if we have just branchified a subtree). - */ -static svn_error_t * -branch_map_delete_children(svn_branch_instance_t *branch, - int eid, - apr_pool_t *scratch_pool) -{ - apr_hash_index_t *hi; - - for (hi = apr_hash_first(scratch_pool, branch->e_map); - hi; hi = apr_hash_next(hi)) - { - int this_eid = *(const int *)apr_hash_this_key(hi); - svn_branch_el_rev_content_t *this_node = apr_hash_this_val(hi); - - if (this_node->parent_eid == eid) - { - /* Recurse. (We don't try to check whether it's a directory node, - as we might not have the node kind in the map.) */ - SVN_ERR(branch_map_delete_children(branch, this_eid, - scratch_pool)); - - /* Delete this immediate child. */ - branch_map_delete(branch, this_eid); - } - } - - return SVN_NO_ERROR; -} -/* In TO_BRANCH, assign new EIDs and path mappings to reflect the copying - * of all children of FROM_BRANCH:FROM_PARENT_EID to TO_BRANCH:TO_PARENT_EID, - * recursively, excluding the specified parent element itself. - * - * Assign a new EID in TO_BRANCH's family for each copied element. - * - * FROM_BRANCH and TO_BRANCH may be the same or different branch instances - * in the same or different branch families. - * - * FROM_PARENT_EID MUST be an existing element in FROM_BRANCH. It may be the - * root element of FROM_BRANCH, but not a subtree root. - * - * TO_PARENT_EID MUST be an existing path in TO_BRANCH. It may be the - * root element of TO_BRANCH, but not a subtree root. - */ -static svn_error_t * -branch_map_copy_children(svn_branch_instance_t *from_branch, - int from_parent_eid, - svn_branch_instance_t *to_branch, - int to_parent_eid, - apr_pool_t *scratch_pool) -{ - apr_hash_index_t *hi; - - /* The 'from' and 'to' nodes must exist. */ - SVN_ERR_ASSERT(branch_map_get(from_branch, from_parent_eid)); - SVN_ERR_ASSERT(branch_map_get(to_branch, to_parent_eid)); - - /* Process the immediate children of FROM_PARENT_EID. */ - for (hi = apr_hash_first(scratch_pool, from_branch->e_map); - hi; hi = apr_hash_next(hi)) - { - int this_from_eid = *(const int *)apr_hash_this_key(hi); - svn_branch_el_rev_content_t *from_node = apr_hash_this_val(hi); - - if (from_node->parent_eid == from_parent_eid) - { - int new_eid = family_add_new_element(to_branch->sibling_defn->family); - - branch_map_update(to_branch, new_eid, - to_parent_eid, from_node->name, - from_node->content); - - /* Recurse. (We don't try to check whether it's a directory node, - as we might not have the node kind in the map.) */ - SVN_ERR(branch_map_copy_children(from_branch, this_from_eid, - to_branch, new_eid, - scratch_pool)); - } - } - - return SVN_NO_ERROR; -} - -/* Update the mappings to reflect branching the subtree [### the what?!] - * at FROM_BRANCH:FROM_PARENT_EID to TO_BRANCH:TO_PARENT_EID. - * - * FROM_BRANCH and TO_BRANCH must be different branch instances in the - * same branch family. - * - * FROM_PATH MUST be an existing path in FROM_BRANCH, and may be the - * root path of FROM_BRANCH. - * - * TO_PATH MUST be a path in TO_BRANCH at which nothing currently exists - * if INCLUDE_SELF, or an existing path if not INCLUDE_SELF. - * - * If INCLUDE_SELF is true, include the element at FROM_PATH, otherwise - * only act on children (recursively) of FROM_PATH. - */ -static svn_error_t * -branch_map_branch_children(svn_branch_instance_t *from_branch, - int from_parent_eid, - svn_branch_instance_t *to_branch, - int to_parent_eid, - apr_pool_t *scratch_pool) -{ - apr_hash_index_t *hi; - - SVN_ERR_ASSERT(from_branch->sibling_defn->family->fid - == to_branch->sibling_defn->family->fid); - SVN_ERR_ASSERT(from_branch->sibling_defn->bid != to_branch->sibling_defn->bid); - - /* The 'from' and 'to' nodes must exist. */ - SVN_ERR_ASSERT(branch_map_get(from_branch, from_parent_eid)); - SVN_ERR_ASSERT(branch_map_get(to_branch, to_parent_eid)); - - /* Process the immediate children of FROM_PARENT_EID. */ - for (hi = apr_hash_first(scratch_pool, from_branch->e_map); - hi; hi = apr_hash_next(hi)) - { - int this_eid = *(const int *)apr_hash_this_key(hi); - svn_branch_el_rev_content_t *from_node = branch_map_get(from_branch, - this_eid); - - if (from_node->parent_eid == from_parent_eid) - { - svn_editor3_node_content_t *this_content; - - SVN_ERR(copy_content_from(&this_content, from_branch, this_eid, - scratch_pool, scratch_pool)); - branch_map_update(to_branch, this_eid, - from_node->parent_eid, from_node->name, - this_content); - - /* Recurse. (We don't try to check whether it's a directory node, - as we might not have the node kind in the map.) */ - SVN_ERR(branch_map_branch_children(from_branch, this_eid, - to_branch, this_eid, - scratch_pool)); - } - } - - return SVN_NO_ERROR; -} - -/* Return true iff CHILD_FAMILY is an immediate child of PARENT_FAMILY. */ -static svn_boolean_t -family_is_child(svn_branch_family_t *parent_family, - svn_branch_family_t *child_family) -{ - int i; - - for (i = 0; i < parent_family->sub_families->nelts; i++) - { - svn_branch_family_t *sub_family - = APR_ARRAY_IDX(parent_family->sub_families, i, void *); - - if (sub_family == child_family) - return TRUE; - } - return FALSE; -} - -/* Return an array of pointers to the branch instances that are immediate - * sub-branches of BRANCH at or below EID. - */ -static apr_array_header_t * -branch_get_sub_branches(const svn_branch_instance_t *branch, - int eid, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_branch_family_t *family = branch->sibling_defn->family; - const char *top_rrpath = svn_branch_get_rrpath_by_eid(branch, eid, - scratch_pool); - apr_array_header_t *sub_branches = apr_array_make(result_pool, 0, - sizeof(void *)); - int i; - - for (i = 0; i < branch->rev_root->branch_instances->nelts; i++) - { - svn_branch_instance_t *sub_branch - = APR_ARRAY_IDX(branch->rev_root->branch_instances, i, void *); - svn_branch_family_t *sub_branch_family = sub_branch->sibling_defn->family; - const char *sub_branch_root_rrpath - = svn_branch_get_root_rrpath(sub_branch); - - /* Is it an immediate child at or below EID? */ - if (family_is_child(family, sub_branch_family) - && svn_relpath_skip_ancestor(top_rrpath, sub_branch_root_rrpath)) - { - APR_ARRAY_PUSH(sub_branches, void *) = sub_branch; - } - } - return sub_branches; -} - -/* Return an array of pointers to the branch instances that are immediate - * sub-branches of BRANCH. - */ -static apr_array_header_t * -branch_get_all_sub_branches(const svn_branch_instance_t *branch, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - return branch_get_sub_branches(branch, branch->sibling_defn->root_eid, - result_pool, scratch_pool); -} - -/* Delete the branch instance BRANCH by removing the record of it from its - * revision-root. - */ -static svn_error_t * -branch_instance_delete(svn_branch_instance_t *branch, - apr_pool_t *scratch_pool) -{ - apr_array_header_t *branch_instances = branch->rev_root->branch_instances; - int i; - - for (i = 0; i < branch_instances->nelts; i++) - { - svn_branch_instance_t *b - = APR_ARRAY_IDX(branch_instances, i, void *); - - if (b == branch) - { - svn_sort__array_delete(branch_instances, i, 1); - break; - } - } - - return SVN_NO_ERROR; -} - -/* Delete the branch instance object BRANCH and any nested branch instances, - * recursively. - */ -static svn_error_t * -branch_instance_delete_r(svn_branch_instance_t *branch, - apr_pool_t *scratch_pool) -{ - apr_array_header_t *subbranches - = branch_get_all_sub_branches(branch, scratch_pool, scratch_pool); - int i; - - /* Delete nested branch instances, recursively */ - for (i = 0; i < subbranches->nelts; i++) - { - svn_branch_instance_t *b = APR_ARRAY_IDX(subbranches, i, void *); - - branch_instance_delete_r(b, scratch_pool); - } - - /* Remove the record of this branch instance */ - SVN_ERR(branch_instance_delete(branch, scratch_pool)); - - return SVN_NO_ERROR; -} - -/* Create a new branch instance at OUTER_BRANCH:EID, of the branch class - * BRANCH_SIBLING, with no elements. - */ -static svn_branch_instance_t * -branch_add_new_branch_instance(svn_branch_instance_t *outer_branch, - int outer_eid, - svn_branch_sibling_t *branch_sibling, - apr_pool_t *scratch_pool) -{ - svn_branch_family_t *family = branch_sibling->family; - - /* All this next bit is to get an RRPATH. Should ultimately go away. */ - const char *outer_root_rrpath = svn_branch_get_root_rrpath(outer_branch); - const char *outer_eid_relpath - = svn_branch_get_path_by_eid(outer_branch, outer_eid, scratch_pool); - const char *new_root_rrpath - = svn_relpath_join(outer_root_rrpath, outer_eid_relpath, scratch_pool); - - svn_branch_instance_t *branch_instance - = svn_branch_instance_create(branch_sibling, outer_branch->rev_root, - new_root_rrpath, family->pool); - - APR_ARRAY_PUSH(branch_instance->rev_root->branch_instances, void *) - = branch_instance; - - return branch_instance; -} - - -/* - * ======================================================================== - * Parsing and Serializing - * ======================================================================== - */ - -/* Create a new branch *NEW_BRANCH that belongs to FAMILY, initialized - * with info parsed from STREAM, allocated in RESULT_POOL. - */ -static svn_error_t * -svn_branch_instance_parse(svn_branch_instance_t **new_branch, - svn_branch_family_t *family, - svn_branch_revision_root_t *rev_root, - svn_stream_t *stream, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_stringbuf_t *line; - svn_boolean_t eof; - int n; - int fid, bid, root_eid; - svn_branch_sibling_t *branch_sibling; - svn_branch_instance_t *branch_instance; - char branch_root_path[100]; - const char *branch_root_rrpath; - int eid; - - SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool)); - SVN_ERR_ASSERT(! eof); - n = sscanf(line->data, "f%db%d: root-eid %d at %s\n", - &fid, &bid, &root_eid, branch_root_path); - SVN_ERR_ASSERT(n == 4); - - SVN_ERR_ASSERT(fid == family->fid); - branch_root_rrpath - = strcmp(branch_root_path, ".") == 0 ? "" : branch_root_path; - branch_sibling = family_find_or_create_branch_sibling(family, bid, root_eid); - branch_instance = svn_branch_instance_create(branch_sibling, rev_root, - branch_root_rrpath, result_pool); - - for (eid = family->first_eid; eid < family->next_eid; eid++) - { - int this_fid, this_bid, this_eid, this_parent_eid; - char this_name[20], this_path[100]; - - SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool)); - SVN_ERR_ASSERT(! eof); - n = sscanf(line->data, "f%db%de%d: %d %20s %100s\n", - &this_fid, &this_bid, &this_eid, - &this_parent_eid, this_name, this_path); - SVN_ERR_ASSERT(n == 6); - if (strcmp(this_path, "(null)") != 0) - { - const char *name = strcmp(this_name, ".") == 0 ? "" : this_name; - const char *path = strcmp(this_path, ".") == 0 ? "" : this_path; - const char *rrpath = svn_relpath_join(branch_root_rrpath, path, - scratch_pool); - svn_editor3_peg_path_t peg; - svn_editor3_node_content_t *content; - - /* Specify the content by reference */ - peg.rev = rev_root->rev; - peg.relpath = rrpath; - content = svn_editor3_node_content_create_ref(peg, scratch_pool); - - branch_map_update(branch_instance, this_eid, - this_parent_eid, name, content); - } - } - - *new_branch = branch_instance; - return SVN_NO_ERROR; -} - -/* Parse a branch family from STREAM. - * - * If the family is already found in REPOS, update it (assume it's from a - * later revision), otherwise create a new one and register it in REPOS. - * - * Set *NEW_FAMILY to the branch family object, allocated in REPOS's pool. - */ -static svn_error_t * -svn_branch_family_parse(svn_branch_family_t **new_family, - int *parent_fid, - svn_branch_repos_t *repos, - svn_stream_t *stream, - apr_pool_t *scratch_pool) -{ - svn_stringbuf_t *line; - svn_boolean_t eof; - int n; - int fid, first_bid, next_bid, first_eid, next_eid; - svn_branch_family_t *family; - - SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool)); - SVN_ERR_ASSERT(!eof); - n = sscanf(line->data, "f%d: bids %d %d eids %d %d parent-fid %d\n", - &fid, - &first_bid, &next_bid, &first_eid, &next_eid, - parent_fid); - SVN_ERR_ASSERT(n == 6); - - family = repos_get_family_by_id(repos, fid); - if (family) - { - SVN_ERR_ASSERT(first_bid == family->first_bid); - SVN_ERR_ASSERT(next_bid >= family->next_bid); - SVN_ERR_ASSERT(first_eid == family->first_eid); - SVN_ERR_ASSERT(next_eid >= family->next_eid); - family->next_bid = next_bid; - family->next_eid = next_eid; - } - else - { - family = svn_branch_family_create(repos, fid, - first_bid, next_bid, - first_eid, next_eid, - repos->pool); - - /* Register this family in the repos and in its parent family (if any) */ - repos_register_family(repos, family); - if (*parent_fid >= 0) - { - svn_branch_family_t *parent_family - = repos_get_family_by_id(repos, *parent_fid); - - SVN_ERR_ASSERT(parent_family); - APR_ARRAY_PUSH(parent_family->sub_families, void *) = family; - } - } - - *new_family = family; - return SVN_NO_ERROR; -} - -svn_error_t * -svn_branch_revision_root_parse(svn_branch_revision_root_t **rev_root_p, - int *next_fid_p, - svn_branch_repos_t *repos, - svn_stream_t *stream, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_branch_revision_root_t *rev_root; - svn_revnum_t rev; - int root_fid; - svn_stringbuf_t *line; - svn_boolean_t eof; - int n, i; - - SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool)); - SVN_ERR_ASSERT(! eof); - n = sscanf(line->data, "r%ld: fids %*d %d root-fid %d", - &rev, /* 0, */ next_fid_p, &root_fid); - SVN_ERR_ASSERT(n == 3); - - rev_root = svn_branch_revision_root_create(repos, rev, NULL /*root_branch*/, - result_pool); - - /* parse the families */ - for (i = 0; i < *next_fid_p; i++) - { - svn_branch_family_t *family; - int bid, parent_fid; - - SVN_ERR(svn_branch_family_parse(&family, &parent_fid, repos, stream, - scratch_pool)); - - /* parse the branches */ - for (bid = family->first_bid; bid < family->next_bid; ++bid) - { - svn_branch_instance_t *branch; - - SVN_ERR(svn_branch_instance_parse(&branch, family, rev_root, stream, - family->pool, scratch_pool)); - APR_ARRAY_PUSH(branch->rev_root->branch_instances, void *) = branch; - if (family->fid == root_fid) - { - branch->rev_root->root_branch = branch; - } - } - } - - *rev_root_p = rev_root; - return SVN_NO_ERROR; -} - -/* Write to STREAM a parseable representation of BRANCH. - */ -static svn_error_t * -svn_branch_instance_serialize(svn_stream_t *stream, - svn_branch_instance_t *branch, - apr_pool_t *scratch_pool) -{ - svn_branch_family_t *family = branch->sibling_defn->family; - const char *branch_root_rrpath = svn_branch_get_root_rrpath(branch); - int eid; - - SVN_ERR(svn_stream_printf(stream, scratch_pool, - "f%db%d: root-eid %d at %s\n", - family->fid, branch->sibling_defn->bid, - branch->sibling_defn->root_eid, [... 1215 lines stripped ...]