1333347Speter/* 2333347Speter * branch_compat.c : Branching compatibility layer. 3333347Speter * 4333347Speter * ==================================================================== 5333347Speter * Licensed to the Apache Software Foundation (ASF) under one 6333347Speter * or more contributor license agreements. See the NOTICE file 7333347Speter * distributed with this work for additional information 8333347Speter * regarding copyright ownership. The ASF licenses this file 9333347Speter * to you under the Apache License, Version 2.0 (the 10333347Speter * "License"); you may not use this file except in compliance 11333347Speter * with the License. You may obtain a copy of the License at 12333347Speter * 13333347Speter * http://www.apache.org/licenses/LICENSE-2.0 14333347Speter * 15333347Speter * Unless required by applicable law or agreed to in writing, 16333347Speter * software distributed under the License is distributed on an 17333347Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18333347Speter * KIND, either express or implied. See the License for the 19333347Speter * specific language governing permissions and limitations 20333347Speter * under the License. 21333347Speter * ==================================================================== 22333347Speter */ 23333347Speter 24333347Speter#include "svn_types.h" 25333347Speter#include "svn_error.h" 26333347Speter#include "svn_delta.h" 27333347Speter#include "svn_dirent_uri.h" 28333347Speter#include "svn_path.h" 29333347Speter#include "svn_hash.h" 30333347Speter#include "svn_iter.h" 31333347Speter#include "svn_props.h" 32333347Speter#include "svn_pools.h" 33333347Speter 34333347Speter#include "private/svn_branch_impl.h" 35333347Speter#include "private/svn_branch_repos.h" 36333347Speter#include "private/svn_branch_nested.h" 37333347Speter#include "private/svn_delta_private.h" 38333347Speter#include "private/svn_branch_compat.h" 39333347Speter 40333347Speter#include "svn_private_config.h" 41333347Speter 42333347Speter 43333347Speter/* Verify EXPR is true; raise an error if not. */ 44333347Speter#define VERIFY(expr) SVN_ERR_ASSERT(expr) 45333347Speter 46333347Speter 47333347Speter/* 48333347Speter * =================================================================== 49333347Speter * Minor data types 50333347Speter * =================================================================== 51333347Speter */ 52333347Speter 53333347Speter/** A location in a committed revision. 54333347Speter * 55333347Speter * @a rev shall not be #SVN_INVALID_REVNUM unless the interface using this 56333347Speter * type specifically allows it and defines its meaning. */ 57333347Spetertypedef struct svn_pathrev_t 58333347Speter{ 59333347Speter svn_revnum_t rev; 60333347Speter const char *relpath; 61333347Speter} svn_pathrev_t; 62333347Speter 63333347Speter/* Return true iff PEG_PATH1 and PEG_PATH2 are both the same location. 64333347Speter */ 65333347Speterstatic svn_boolean_t 66333347Speterpathrev_equal(const svn_pathrev_t *p1, 67333347Speter const svn_pathrev_t *p2) 68333347Speter{ 69333347Speter if (p1->rev != p2->rev) 70333347Speter return FALSE; 71333347Speter if (strcmp(p1->relpath, p2->relpath) != 0) 72333347Speter return FALSE; 73333347Speter 74333347Speter return TRUE; 75333347Speter} 76333347Speter 77333347Speter#if 0 78333347Speter/* Return a human-readable string representation of LOC. */ 79333347Speterstatic const char * 80333347Speterpathrev_str(const svn_pathrev_t *loc, 81333347Speter apr_pool_t *result_pool) 82333347Speter{ 83333347Speter if (! loc) 84333347Speter return "<nil>"; 85333347Speter return apr_psprintf(result_pool, "%s@%ld", 86333347Speter loc->relpath, loc->rev); 87333347Speter} 88333347Speter 89333347Speter/* Return a string representation of the (string) keys of HASH. */ 90333347Speterstatic const char * 91333347Speterhash_keys_str(apr_hash_t *hash) 92333347Speter{ 93333347Speter const char *str = NULL; 94333347Speter apr_pool_t *pool; 95333347Speter apr_hash_index_t *hi; 96333347Speter 97333347Speter if (! hash) 98333347Speter return "<nil>"; 99333347Speter 100333347Speter pool = apr_hash_pool_get(hash); 101333347Speter for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi)) 102333347Speter { 103333347Speter const char *key = apr_hash_this_key(hi); 104333347Speter 105333347Speter if (!str) 106333347Speter str = key; 107333347Speter else 108333347Speter str = apr_psprintf(pool, "%s, %s", str, key); 109333347Speter } 110333347Speter return apr_psprintf(pool, "{%s}", str); 111333347Speter} 112333347Speter#endif 113333347Speter 114333347Speter/** 115333347Speter * Merge two hash tables into one new hash table. The values of the overlay 116333347Speter * hash override the values of the base if both have the same key. 117333347Speter * 118333347Speter * Unlike apr_hash_overlay(), this doesn't care whether the input hashes use 119333347Speter * the same hash function, nor about the relationship between the three pools. 120333347Speter * 121333347Speter * @param p The pool to use for the new hash table 122333347Speter * @param overlay The table to add to the initial table 123333347Speter * @param base The table that represents the initial values of the new table 124333347Speter * @return A new hash table containing all of the data from the two passed in 125333347Speter * @remark Makes a shallow copy: keys and values are not copied 126333347Speter */ 127333347Speterstatic apr_hash_t * 128333347Speterhash_overlay(apr_hash_t *overlay, 129333347Speter apr_hash_t *base) 130333347Speter{ 131333347Speter apr_pool_t *pool = apr_hash_pool_get(base); 132333347Speter apr_hash_t *result = apr_hash_copy(pool, base); 133333347Speter apr_hash_index_t *hi; 134333347Speter 135333347Speter for (hi = apr_hash_first(pool, overlay); hi; hi = apr_hash_next(hi)) 136333347Speter { 137333347Speter svn_hash_sets(result, apr_hash_this_key(hi), apr_hash_this_val(hi)); 138333347Speter } 139333347Speter return result; 140333347Speter} 141333347Speter 142333347Speter 143333347Speter/* 144333347Speter * ======================================================================== 145333347Speter * Configuration Options 146333347Speter * ======================================================================== 147333347Speter */ 148333347Speter 149333347Speter/* Features that are not wanted for this commit editor shim but may be 150333347Speter * wanted in a similar but different shim such as for an update editor. */ 151333347Speter/* #define SHIM_WITH_ADD_ABSENT */ 152333347Speter/* #define SHIM_WITH_UNLOCK */ 153333347Speter 154333347Speter/* Whether to support switching from relative to absolute paths in the 155333347Speter * Ev1 methods. */ 156333347Speter/* #define SHIM_WITH_ABS_PATHS */ 157333347Speter 158333347Speter 159333347Speter/* 160333347Speter * ======================================================================== 161333347Speter * Shim Connector 162333347Speter * ======================================================================== 163333347Speter * 164333347Speter * The shim connector enables a more exact round-trip conversion from an 165333347Speter * Ev1 drive to Ev3 and back to Ev1. 166333347Speter */ 167333347Speterstruct svn_branch__compat_shim_connector_t 168333347Speter{ 169333347Speter /* Set to true if and when an Ev1 receiving shim receives an absolute 170333347Speter * path (prefixed with '/') from the delta edit, and causes the Ev1 171333347Speter * sending shim to send absolute paths. 172333347Speter * ### NOT IMPLEMENTED 173333347Speter */ 174333347Speter#ifdef SHIM_WITH_ABS_PATHS 175333347Speter svn_boolean_t *ev1_absolute_paths; 176333347Speter#endif 177333347Speter 178333347Speter /* The Ev1 set_target_revision and start_edit methods, respectively, will 179333347Speter * call the TARGET_REVISION_FUNC and START_EDIT_FUNC callbacks, if non-null. 180333347Speter * Otherwise, default calls will be used. 181333347Speter * 182333347Speter * (Possibly more useful for update editors than for commit editors?) */ 183333347Speter svn_branch__compat_set_target_revision_func_t target_revision_func; 184333347Speter 185333347Speter /* If not null, a callback that the Ev3 driver may call to 186333347Speter * provide the "base revision" of the root directory, even if it is not 187333347Speter * going to modify that directory. (If it does modify it, then it will 188333347Speter * pass in the appropriate base revision at that time.) If null 189333347Speter * or if the driver does not call it, then the Ev1 190333347Speter * open_root() method will be called with SVN_INVALID_REVNUM as the base 191333347Speter * revision parameter. 192333347Speter */ 193333347Speter svn_delta__start_edit_func_t start_edit_func; 194333347Speter 195333347Speter#ifdef SHIM_WITH_UNLOCK 196333347Speter /* A callback which will be called when an unlocking action is received. 197333347Speter (For update editors?) */ 198333347Speter svn_delta__unlock_func_t unlock_func; 199333347Speter#endif 200333347Speter 201333347Speter void *baton; 202333347Speter}; 203333347Speter 204333347Spetersvn_error_t * 205333347Spetersvn_branch__compat_insert_shims( 206333347Speter const svn_delta_editor_t **new_deditor, 207333347Speter void **new_dedit_baton, 208333347Speter const svn_delta_editor_t *old_deditor, 209333347Speter void *old_dedit_baton, 210333347Speter const char *repos_root, 211333347Speter const char *base_relpath, 212333347Speter svn_branch__compat_fetch_func_t fetch_func, 213333347Speter void *fetch_baton, 214333347Speter apr_pool_t *result_pool, 215333347Speter apr_pool_t *scratch_pool) 216333347Speter{ 217333347Speter#if 0 218333347Speter svn_branch__txn_t *edit_txn; 219333347Speter svn_branch__compat_shim_connector_t *shim_connector; 220333347Speter 221333347Speter#ifdef SVN_DEBUG 222333347Speter /*SVN_ERR(svn_delta__get_debug_editor(&old_deditor, &old_dedit_baton, 223333347Speter old_deditor, old_dedit_baton, 224333347Speter "[OUT] ", result_pool));*/ 225333347Speter#endif 226333347Speter SVN_ERR(svn_branch__compat_txn_from_delta_for_commit( 227333347Speter &edit_txn, 228333347Speter &shim_connector, 229333347Speter old_deditor, old_dedit_baton, 230333347Speter branching_txn, 231333347Speter repos_root, 232333347Speter fetch_func, fetch_baton, 233333347Speter NULL, NULL /*cancel*/, 234333347Speter result_pool, scratch_pool)); 235333347Speter SVN_ERR(svn_branch__compat_delta_from_txn_for_commit( 236333347Speter new_deditor, new_dedit_baton, 237333347Speter edit_txn, 238333347Speter repos_root, base_relpath, 239333347Speter fetch_func, fetch_baton, 240333347Speter shim_connector, 241333347Speter result_pool, scratch_pool)); 242333347Speter#ifdef SVN_DEBUG 243333347Speter /*SVN_ERR(svn_delta__get_debug_editor(new_deditor, new_dedit_baton, 244333347Speter *new_deditor, *new_dedit_baton, 245333347Speter "[IN] ", result_pool));*/ 246333347Speter#endif 247333347Speter#else 248333347Speter *new_deditor = old_deditor; 249333347Speter *new_dedit_baton = old_dedit_baton; 250333347Speter#endif 251333347Speter return SVN_NO_ERROR; 252333347Speter} 253333347Speter 254333347Speter 255333347Speter/* 256333347Speter * ======================================================================== 257333347Speter * Buffering the Delta Editor Changes 258333347Speter * ======================================================================== 259333347Speter */ 260333347Speter 261333347Speter/* The kind of Ev1 restructuring operation on a particular path. For each 262333347Speter * visited path we use exactly one restructuring action. */ 263333347Speterenum restructure_action_t 264333347Speter{ 265333347Speter RESTRUCTURE_NONE = 0, 266333347Speter RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */ 267333347Speter#ifdef SHIM_WITH_ADD_ABSENT 268333347Speter RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */ 269333347Speter#endif 270333347Speter RESTRUCTURE_DELETE /* delete this node */ 271333347Speter}; 272333347Speter 273333347Speter/* Records everything about how this node is to be changed, from an Ev1 274333347Speter * point of view. */ 275333347Spetertypedef struct change_node_t 276333347Speter{ 277333347Speter /* what kind of (tree) restructure is occurring at this node? */ 278333347Speter enum restructure_action_t action; 279333347Speter 280333347Speter svn_node_kind_t kind; /* the NEW kind of this node */ 281333347Speter 282333347Speter /* We may need to specify the revision we are altering or the revision 283333347Speter to delete or replace. These are mutually exclusive, but are separate 284333347Speter for clarity. */ 285333347Speter /* CHANGING_REV is the base revision of the change if ACTION is 'none', 286333347Speter else is SVN_INVALID_REVNUM. (If ACTION is 'add' and COPYFROM_PATH 287333347Speter is non-null, then COPYFROM_REV serves the equivalent purpose for the 288333347Speter copied node.) */ 289333347Speter /* ### Can also be SVN_INVALID_REVNUM for a pre-existing file/dir, 290333347Speter meaning the base is the youngest revision. This is probably not 291333347Speter a good idea -- it is at least confusing -- and we should instead 292333347Speter resolve to a real revnum when Ev1 passes in SVN_INVALID_REVNUM 293333347Speter in such cases. */ 294333347Speter svn_revnum_t changing_rev; 295333347Speter /* If ACTION is 'delete' or if ACTION is 'add' and it is a replacement, 296333347Speter DELETING is TRUE and DELETING_REV is the revision to delete. */ 297333347Speter /* ### Can also be SVN_INVALID_REVNUM for a pre-existing file/dir, 298333347Speter meaning the base is the youngest revision. This is probably not 299333347Speter a good idea -- it is at least confusing -- and we should instead 300333347Speter resolve to a real revnum when Ev1 passes in SVN_INVALID_REVNUM 301333347Speter in such cases. */ 302333347Speter svn_boolean_t deleting; 303333347Speter svn_revnum_t deleting_rev; 304333347Speter 305333347Speter /* new/final set of props to apply; null => no *change*, not no props */ 306333347Speter apr_hash_t *props; 307333347Speter 308333347Speter /* new fulltext; null => no change */ 309333347Speter svn_boolean_t contents_changed; 310333347Speter svn_stringbuf_t *contents_text; 311333347Speter 312333347Speter /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node. 313333347Speter RESTRUCTURE must be RESTRUCTURE_ADD. */ 314333347Speter const char *copyfrom_path; 315333347Speter svn_revnum_t copyfrom_rev; 316333347Speter 317333347Speter#ifdef SHIM_WITH_UNLOCK 318333347Speter /* Record whether an incoming propchange unlocked this node. */ 319333347Speter svn_boolean_t unlock; 320333347Speter#endif 321333347Speter} change_node_t; 322333347Speter 323333347Speter#if 0 324333347Speter/* Return a string representation of CHANGE. */ 325333347Speterstatic const char * 326333347Speterchange_node_str(change_node_t *change, 327333347Speter apr_pool_t *result_pool) 328333347Speter{ 329333347Speter const char *copyfrom = "<nil>"; 330333347Speter const char *str; 331333347Speter 332333347Speter if (change->copyfrom_path) 333333347Speter copyfrom = apr_psprintf(result_pool, "'%s'@%ld", 334333347Speter change->copyfrom_path, change->copyfrom_rev); 335333347Speter str = apr_psprintf(result_pool, 336333347Speter "action=%d, kind=%s, changing_rev=%ld, " 337333347Speter "deleting=%d, deleting_rev=%ld, ..., " 338333347Speter "copyfrom=%s", 339333347Speter change->action, 340333347Speter svn_node_kind_to_word(change->kind), 341333347Speter change->changing_rev, 342333347Speter change->deleting, change->deleting_rev, 343333347Speter copyfrom); 344333347Speter return str; 345333347Speter} 346333347Speter#endif 347333347Speter 348333347Speter/* Check whether RELPATH is known to exist, known to not exist, or unknown. */ 349333347Speterstatic svn_tristate_t 350333347Spetercheck_existence(apr_hash_t *changes, 351333347Speter const char *relpath) 352333347Speter{ 353333347Speter apr_pool_t *changes_pool = apr_hash_pool_get(changes); 354333347Speter apr_pool_t *scratch_pool = changes_pool; 355333347Speter change_node_t *change = svn_hash_gets(changes, relpath); 356333347Speter svn_tristate_t exists = svn_tristate_unknown; 357333347Speter 358333347Speter if (change && change->action != RESTRUCTURE_DELETE) 359333347Speter exists = svn_tristate_true; 360333347Speter else if (change && change->action == RESTRUCTURE_DELETE) 361333347Speter exists = svn_tristate_false; 362333347Speter else 363333347Speter { 364333347Speter const char *parent_path = relpath; 365333347Speter 366333347Speter /* Find the nearest parent change. If that's a delete or a simple 367333347Speter (non-recursive) add, this path cannot exist, else we don't know. */ 368333347Speter while ((parent_path = svn_relpath_dirname(parent_path, scratch_pool)), 369333347Speter *parent_path) 370333347Speter { 371333347Speter change = svn_hash_gets(changes, parent_path); 372333347Speter if (change) 373333347Speter { 374333347Speter if ((change->action == RESTRUCTURE_ADD && !change->copyfrom_path) 375333347Speter || change->action == RESTRUCTURE_DELETE) 376333347Speter exists = svn_tristate_false; 377333347Speter break; 378333347Speter } 379333347Speter } 380333347Speter } 381333347Speter 382333347Speter return exists; 383333347Speter} 384333347Speter 385333347Speter/* Insert a new Ev1-style change for RELPATH, or return an existing one. 386333347Speter * 387333347Speter * Verify Ev3 rules. Primary differences from Ev1 rules are ... 388333347Speter * 389333347Speter * If ACTION is 'delete', elide any previous explicit deletes inside 390333347Speter * that subtree. (Other changes inside that subtree are not allowed.) We 391333347Speter * do not store multiple change records per path even with nested moves 392333347Speter * -- the most complex change is delete + copy which all fits in one 393333347Speter * record with action='add'. 394333347Speter */ 395333347Speterstatic svn_error_t * 396333347Speterinsert_change(change_node_t **change_p, apr_hash_t *changes, 397333347Speter const char *relpath, 398333347Speter enum restructure_action_t action) 399333347Speter{ 400333347Speter apr_pool_t *changes_pool = apr_hash_pool_get(changes); 401333347Speter change_node_t *change = svn_hash_gets(changes, relpath); 402333347Speter 403333347Speter /* Check whether this op is allowed. */ 404333347Speter switch (action) 405333347Speter { 406333347Speter case RESTRUCTURE_NONE: 407333347Speter if (change) 408333347Speter { 409333347Speter /* A no-restructure change is allowed after add, but not allowed 410333347Speter * (in Ev3) after another no-restructure change, nor a delete. */ 411333347Speter VERIFY(change->action == RESTRUCTURE_ADD); 412333347Speter } 413333347Speter break; 414333347Speter 415333347Speter case RESTRUCTURE_ADD: 416333347Speter if (change) 417333347Speter { 418333347Speter /* Add or copy is allowed after delete (and replaces the delete), 419333347Speter * but not allowed after an add or a no-restructure change. */ 420333347Speter VERIFY(change->action == RESTRUCTURE_DELETE); 421333347Speter change->action = action; 422333347Speter } 423333347Speter break; 424333347Speter 425333347Speter#ifdef SHIM_WITH_ADD_ABSENT 426333347Speter case RESTRUCTURE_ADD_ABSENT: 427333347Speter /* ### */ 428333347Speter break; 429333347Speter#endif 430333347Speter 431333347Speter case RESTRUCTURE_DELETE: 432333347Speter SVN_ERR_MALFUNCTION(); 433333347Speter } 434333347Speter 435333347Speter if (change) 436333347Speter { 437333347Speter if (action != RESTRUCTURE_NONE) 438333347Speter { 439333347Speter change->action = action; 440333347Speter } 441333347Speter } 442333347Speter else 443333347Speter { 444333347Speter /* Create a new change. Callers will set the other fields as needed. */ 445333347Speter change = apr_pcalloc(changes_pool, sizeof(*change)); 446333347Speter change->action = action; 447333347Speter change->changing_rev = SVN_INVALID_REVNUM; 448333347Speter 449333347Speter svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change); 450333347Speter } 451333347Speter 452333347Speter *change_p = change; 453333347Speter return SVN_NO_ERROR; 454333347Speter} 455333347Speter 456333347Speter/* Modify CHANGES so as to delete the subtree at RELPATH. 457333347Speter * 458333347Speter * Insert a new Ev1-style change record for RELPATH (or perhaps remove 459333347Speter * the existing record if this would have the same effect), and remove 460333347Speter * any change records for sub-paths under RELPATH. 461333347Speter * 462333347Speter * Follow Ev3 rules, although without knowing whether this delete is 463333347Speter * part of a move. Ev3 (incremental) "rm" operation says each node to 464333347Speter * be removed "MAY be a child of a copy but otherwise SHOULD NOT have 465333347Speter * been created or modified in this edit". "mv" operation says ... 466333347Speter */ 467333347Speterstatic svn_error_t * 468333347Speterdelete_subtree(apr_hash_t *changes, 469333347Speter const char *relpath, 470333347Speter svn_revnum_t deleting_rev) 471333347Speter{ 472333347Speter apr_pool_t *changes_pool = apr_hash_pool_get(changes); 473333347Speter apr_pool_t *scratch_pool = changes_pool; 474333347Speter change_node_t *change = svn_hash_gets(changes, relpath); 475333347Speter 476333347Speter if (change) 477333347Speter { 478333347Speter /* If this previous change was a non-replacing addition, there 479333347Speter is no longer any change to be made at this path. If it was 480333347Speter a replacement or a modification, it now becomes a delete. 481333347Speter (If it was a delete, this attempt to delete is an error.) */ 482333347Speter VERIFY(change->action != RESTRUCTURE_DELETE); 483333347Speter if (change->action == RESTRUCTURE_ADD && !change->deleting) 484333347Speter { 485333347Speter svn_hash_sets(changes, relpath, NULL); 486333347Speter change = NULL; 487333347Speter } 488333347Speter else 489333347Speter { 490333347Speter change->action = RESTRUCTURE_DELETE; 491333347Speter } 492333347Speter } 493333347Speter else 494333347Speter { 495333347Speter /* There was no change recorded at this path. Record a delete. */ 496333347Speter change = apr_pcalloc(changes_pool, sizeof(*change)); 497333347Speter change->action = RESTRUCTURE_DELETE; 498333347Speter change->changing_rev = SVN_INVALID_REVNUM; 499333347Speter change->deleting = TRUE; 500333347Speter change->deleting_rev = deleting_rev; 501333347Speter 502333347Speter svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change); 503333347Speter } 504333347Speter 505333347Speter /* Elide all child ops. */ 506333347Speter { 507333347Speter apr_hash_index_t *hi; 508333347Speter 509333347Speter for (hi = apr_hash_first(scratch_pool, changes); 510333347Speter hi; hi = apr_hash_next(hi)) 511333347Speter { 512333347Speter const char *this_relpath = apr_hash_this_key(hi); 513333347Speter const char *r = svn_relpath_skip_ancestor(relpath, this_relpath); 514333347Speter 515333347Speter if (r && r[0]) 516333347Speter { 517333347Speter svn_hash_sets(changes, this_relpath, NULL); 518333347Speter } 519333347Speter } 520333347Speter } 521333347Speter 522333347Speter return SVN_NO_ERROR; 523333347Speter} 524333347Speter 525333347Speter 526333347Speter/* 527333347Speter * =================================================================== 528333347Speter * Commit Editor converter to join a v1 driver to a v3 consumer 529333347Speter * =================================================================== 530333347Speter * 531333347Speter * ... 532333347Speter */ 533333347Speter 534333347Spetersvn_error_t * 535333347Spetersvn_branch__compat_delta_from_txn_for_commit( 536333347Speter const svn_delta_editor_t **deditor, 537333347Speter void **dedit_baton, 538333347Speter svn_branch__txn_t *edit_txn, 539333347Speter const char *repos_root_url, 540333347Speter const char *base_relpath, 541333347Speter svn_branch__compat_fetch_func_t fetch_func, 542333347Speter void *fetch_baton, 543333347Speter const svn_branch__compat_shim_connector_t *shim_connector, 544333347Speter apr_pool_t *result_pool, 545333347Speter apr_pool_t *scratch_pool) 546333347Speter{ 547333347Speter /* ### ... */ 548333347Speter 549333347Speter return SVN_NO_ERROR; 550333347Speter} 551333347Speter 552333347Spetersvn_error_t * 553333347Spetersvn_branch__compat_delta_from_txn_for_update( 554333347Speter const svn_delta_editor_t **deditor, 555333347Speter void **dedit_baton, 556333347Speter svn_branch__compat_update_editor3_t *update_editor, 557333347Speter const char *repos_root_url, 558333347Speter const char *base_repos_relpath, 559333347Speter svn_branch__compat_fetch_func_t fetch_func, 560333347Speter void *fetch_baton, 561333347Speter apr_pool_t *result_pool, 562333347Speter apr_pool_t *scratch_pool) 563333347Speter{ 564333347Speter svn_branch__compat_shim_connector_t *shim_connector 565333347Speter = apr_pcalloc(result_pool, sizeof(*shim_connector)); 566333347Speter 567333347Speter shim_connector->target_revision_func = update_editor->set_target_revision_func; 568333347Speter shim_connector->baton = update_editor->set_target_revision_baton; 569333347Speter#ifdef SHIM_WITH_ABS_PATHS 570333347Speter shim_connector->ev1_absolute_paths /*...*/; 571333347Speter#endif 572333347Speter 573333347Speter SVN_ERR(svn_branch__compat_delta_from_txn_for_commit( 574333347Speter deditor, dedit_baton, 575333347Speter update_editor->edit_txn, 576333347Speter repos_root_url, base_repos_relpath, 577333347Speter fetch_func, fetch_baton, 578333347Speter shim_connector, 579333347Speter result_pool, scratch_pool)); 580333347Speter /*SVN_ERR(svn_delta__get_debug_editor(deditor, dedit_baton, 581333347Speter *deditor, *dedit_baton, 582333347Speter "[UP>1] ", result_pool));*/ 583333347Speter 584333347Speter return SVN_NO_ERROR; 585333347Speter} 586333347Speter 587333347Speter 588333347Speter/* 589333347Speter * =================================================================== 590333347Speter * Commit Editor converter to join a v3 driver to a v1 consumer 591333347Speter * =================================================================== 592333347Speter * 593333347Speter * This editor buffers all the changes before driving the Ev1 at the end, 594333347Speter * since it needs to do a single depth-first traversal of the path space 595333347Speter * and this cannot be started until all moves are known. 596333347Speter * 597333347Speter * Moves are converted to copy-and-delete, with the copy being from 598333347Speter * the source peg rev. (### Should it request copy-from revision "-1"?) 599333347Speter * 600333347Speter * It works like this: 601333347Speter * 602333347Speter * +------+--------+ 603333347Speter * | path | change | 604333347Speter * Ev3 --> +------+--------+ --> Ev1 605333347Speter * | ... | ... | 606333347Speter * | ... | ... | 607333347Speter * 608333347Speter * 1. Ev3 changes are accumulated in a per-path table, EB->changes. 609333347Speter * 610333347Speter * 2. On Ev3 close-edit, walk through the table in a depth-first order, 611333347Speter * sending the equivalent Ev1 action for each change. 612333347Speter * 613333347Speter * TODO 614333347Speter * 615333347Speter * ### For changes inside a copied subtree, the calls to the "open dir" 616333347Speter * and "open file" Ev1 methods may be passing the wrong revision 617333347Speter * number: see comment in apply_change(). 618333347Speter * 619333347Speter * ### Have we got our rel-paths in order? Ev1, Ev3 and callbacks may 620333347Speter * all expect different paths. Are they relative to repos root or to 621333347Speter * some base path? Leading slash (unimplemented 'send_abs_paths' 622333347Speter * feature), etc. 623333347Speter * 624333347Speter * ### May be tidier for OPEN_ROOT_FUNC callback (see open_root_ev3()) 625333347Speter * not to actually open the root in advance, but instead just to 626333347Speter * remember the base revision that the driver wants us to specify 627333347Speter * when we do open it. 628333347Speter */ 629333347Speter 630333347Speter 631333347Speter 632333347Speter/* 633333347Speter * ======================================================================== 634333347Speter * Driving the Delta Editor 635333347Speter * ======================================================================== 636333347Speter */ 637333347Speter 638333347Speter/* Information needed for driving the delta editor. */ 639333347Speterstruct svn_branch__txn_priv_t 640333347Speter{ 641333347Speter /* The Ev1 "delta editor" */ 642333347Speter const svn_delta_editor_t *deditor; 643333347Speter void *dedit_baton; 644333347Speter 645333347Speter /* Callbacks */ 646333347Speter svn_branch__compat_fetch_func_t fetch_func; 647333347Speter void *fetch_baton; 648333347Speter 649333347Speter /* The Ev1 root directory baton if we have opened the root, else null. */ 650333347Speter void *ev1_root_dir_baton; 651333347Speter 652333347Speter#ifdef SHIM_WITH_ABS_PATHS 653333347Speter const svn_boolean_t *make_abs_paths; 654333347Speter#endif 655333347Speter 656333347Speter /* Repository root URL 657333347Speter ### Some code allows this to be null -- but is that valid? */ 658333347Speter const char *repos_root_url; 659333347Speter 660333347Speter /* Ev1 changes recorded so far: REPOS_RELPATH -> change_node_ev3_t */ 661333347Speter apr_hash_t *changes; 662333347Speter 663333347Speter /* The branching state on which the per-element API is working */ 664333347Speter svn_branch__txn_t *txn; 665333347Speter 666333347Speter apr_pool_t *edit_pool; 667333347Speter}; 668333347Speter 669333347Speter/* Get all the (Ev1) paths that have changes. 670333347Speter */ 671333347Speterstatic const apr_array_header_t * 672333347Speterget_unsorted_paths(apr_hash_t *changes, 673333347Speter apr_pool_t *scratch_pool) 674333347Speter{ 675333347Speter apr_array_header_t *paths = apr_array_make(scratch_pool, 0, sizeof(void *)); 676333347Speter apr_hash_index_t *hi; 677333347Speter 678333347Speter for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi)) 679333347Speter { 680333347Speter const char *this_path = apr_hash_this_key(hi); 681333347Speter APR_ARRAY_PUSH(paths, const char *) = this_path; 682333347Speter } 683333347Speter 684333347Speter return paths; 685333347Speter} 686333347Speter 687333347Speter#if 0 /* needed only for shim connector */ 688333347Speter/* */ 689333347Speterstatic svn_error_t * 690333347Speterset_target_revision_ev3(void *edit_baton, 691333347Speter svn_revnum_t target_revision, 692333347Speter apr_pool_t *scratch_pool) 693333347Speter{ 694333347Speter svn_branch__txn_priv_t *eb = edit_baton; 695333347Speter 696333347Speter SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision, 697333347Speter scratch_pool)); 698333347Speter 699333347Speter return SVN_NO_ERROR; 700333347Speter} 701333347Speter#endif 702333347Speter 703333347Speter/* */ 704333347Speterstatic svn_error_t * 705333347Speteropen_root_ev3(void *baton, 706333347Speter svn_revnum_t base_revision) 707333347Speter{ 708333347Speter svn_branch__txn_priv_t *eb = baton; 709333347Speter 710333347Speter SVN_ERR(eb->deditor->open_root(eb->dedit_baton, base_revision, 711333347Speter eb->edit_pool, &eb->ev1_root_dir_baton)); 712333347Speter return SVN_NO_ERROR; 713333347Speter} 714333347Speter 715333347Speter/* If RELPATH is a child of a copy, return the path of the copy root, 716333347Speter * else return NULL. 717333347Speter */ 718333347Speterstatic const char * 719333347Speterfind_enclosing_copy(apr_hash_t *changes, 720333347Speter const char *relpath, 721333347Speter apr_pool_t *result_pool) 722333347Speter{ 723333347Speter while (*relpath) 724333347Speter { 725333347Speter const change_node_t *change = svn_hash_gets(changes, relpath); 726333347Speter 727333347Speter if (change) 728333347Speter { 729333347Speter if (change->copyfrom_path) 730333347Speter return relpath; 731333347Speter if (change->action != RESTRUCTURE_NONE) 732333347Speter return NULL; 733333347Speter } 734333347Speter relpath = svn_relpath_dirname(relpath, result_pool); 735333347Speter } 736333347Speter 737333347Speter return NULL; 738333347Speter} 739333347Speter 740333347Speter/* Set *BASE_PROPS to the 'base' properties, against which any changes 741333347Speter * will be described, for the changed path described in CHANGES at 742333347Speter * REPOS_RELPATH. 743333347Speter * 744333347Speter * For a copied path, including a copy child path, fetch from the copy 745333347Speter * source path. For a plain add, return an empty set. For a delete, 746333347Speter * return NULL. 747333347Speter */ 748333347Speterstatic svn_error_t * 749333347Speterfetch_base_props(apr_hash_t **base_props, 750333347Speter apr_hash_t *changes, 751333347Speter const char *repos_relpath, 752333347Speter svn_branch__compat_fetch_func_t fetch_func, 753333347Speter void *fetch_baton, 754333347Speter apr_pool_t *result_pool, 755333347Speter apr_pool_t *scratch_pool) 756333347Speter{ 757333347Speter const change_node_t *change = svn_hash_gets(changes, repos_relpath); 758333347Speter const char *source_path = NULL; 759333347Speter svn_revnum_t source_rev; 760333347Speter 761333347Speter if (change->action == RESTRUCTURE_DELETE) 762333347Speter { 763333347Speter *base_props = NULL; 764333347Speter } 765333347Speter 766333347Speter else if (change->action == RESTRUCTURE_ADD && ! change->copyfrom_path) 767333347Speter { 768333347Speter *base_props = apr_hash_make(result_pool); 769333347Speter } 770333347Speter else if (change->copyfrom_path) 771333347Speter { 772333347Speter source_path = change->copyfrom_path; 773333347Speter source_rev = change->copyfrom_rev; 774333347Speter } 775333347Speter else /* RESTRUCTURE_NONE */ 776333347Speter { 777333347Speter /* It's an edit, but possibly to a copy child. Discover if it's a 778333347Speter copy child, & find the copy-from source. */ 779333347Speter 780333347Speter const char *copy_path 781333347Speter = find_enclosing_copy(changes, repos_relpath, scratch_pool); 782333347Speter 783333347Speter if (copy_path) 784333347Speter { 785333347Speter const change_node_t *enclosing_copy 786333347Speter = svn_hash_gets(changes, copy_path); 787333347Speter const char *remainder 788333347Speter = svn_relpath_skip_ancestor(copy_path, repos_relpath); 789333347Speter 790333347Speter source_path = svn_relpath_join(enclosing_copy->copyfrom_path, 791333347Speter remainder, scratch_pool); 792333347Speter source_rev = enclosing_copy->copyfrom_rev; 793333347Speter } 794333347Speter else 795333347Speter { 796333347Speter /* It's a plain edit (not a copy child path). */ 797333347Speter source_path = repos_relpath; 798333347Speter source_rev = change->changing_rev; 799333347Speter } 800333347Speter } 801333347Speter 802333347Speter if (source_path) 803333347Speter { 804333347Speter SVN_ERR(fetch_func(NULL, base_props, NULL, NULL, 805333347Speter fetch_baton, source_path, source_rev, 806333347Speter result_pool, scratch_pool)); 807333347Speter } 808333347Speter 809333347Speter return SVN_NO_ERROR; 810333347Speter} 811333347Speter 812333347Speter/* Send property changes to Ev1 for the CHANGE at REPOS_RELPATH. 813333347Speter * 814333347Speter * Ev1 requires exactly one prop-change call for each prop whose value 815333347Speter * has changed. Therefore we *have* to fetch the original props from the 816333347Speter * repository, provide them as OLD_PROPS, and calculate the changes. 817333347Speter */ 818333347Speterstatic svn_error_t * 819333347Speterdrive_ev1_props(const char *repos_relpath, 820333347Speter const change_node_t *change, 821333347Speter apr_hash_t *old_props, 822333347Speter const svn_delta_editor_t *deditor, 823333347Speter void *node_baton, 824333347Speter apr_pool_t *scratch_pool) 825333347Speter{ 826333347Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 827333347Speter apr_array_header_t *propdiffs; 828333347Speter int i; 829333347Speter 830333347Speter SVN_ERR_ASSERT(change->action != RESTRUCTURE_DELETE); 831333347Speter 832333347Speter /* If there are no property changes, then just exit. */ 833333347Speter if (change->props == NULL) 834333347Speter return SVN_NO_ERROR; 835333347Speter 836333347Speter SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool)); 837333347Speter 838333347Speter /* Apply property changes. These should be changes against the empty set 839333347Speter for a new node, or changes against the source node for a copied node. */ 840333347Speter for (i = 0; i < propdiffs->nelts; i++) 841333347Speter { 842333347Speter const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t); 843333347Speter 844333347Speter svn_pool_clear(iterpool); 845333347Speter 846333347Speter if (change->kind == svn_node_dir) 847333347Speter SVN_ERR(deditor->change_dir_prop(node_baton, 848333347Speter prop->name, prop->value, 849333347Speter iterpool)); 850333347Speter else 851333347Speter SVN_ERR(deditor->change_file_prop(node_baton, 852333347Speter prop->name, prop->value, 853333347Speter iterpool)); 854333347Speter } 855333347Speter 856333347Speter#ifdef SHIM_WITH_UNLOCK 857333347Speter /* Handle the funky unlock protocol. Note: only possibly on files. */ 858333347Speter if (change->unlock) 859333347Speter { 860333347Speter SVN_ERR_ASSERT(change->kind == svn_node_file); 861333347Speter SVN_ERR(deditor->change_file_prop(node_baton, 862333347Speter SVN_PROP_ENTRY_LOCK_TOKEN, NULL, 863333347Speter iterpool)); 864333347Speter } 865333347Speter#endif 866333347Speter 867333347Speter svn_pool_destroy(iterpool); 868333347Speter return SVN_NO_ERROR; 869333347Speter} 870333347Speter 871333347Speter/* Drive the Ev1 editor with the change recorded in EB->changes for the 872333347Speter * path EV1_RELPATH. 873333347Speter * 874333347Speter * Conforms to svn_delta_path_driver_cb_func_t. 875333347Speter */ 876333347Speterstatic svn_error_t * 877333347Speterapply_change(void **dir_baton, 878362181Sdim const svn_delta_editor_t *editor, 879362181Sdim void *edit_baton, 880333347Speter void *parent_baton, 881333347Speter void *callback_baton, 882333347Speter const char *ev1_relpath, 883333347Speter apr_pool_t *result_pool) 884333347Speter{ 885333347Speter apr_pool_t *scratch_pool = result_pool; 886333347Speter const svn_branch__txn_priv_t *eb = callback_baton; 887333347Speter const change_node_t *change = svn_hash_gets(eb->changes, ev1_relpath); 888333347Speter void *file_baton = NULL; 889333347Speter apr_hash_t *base_props; 890333347Speter 891333347Speter /* The callback should only be called for paths in CHANGES. */ 892333347Speter SVN_ERR_ASSERT(change != NULL); 893333347Speter 894333347Speter /* Typically, we are not creating new directory batons. */ 895333347Speter *dir_baton = NULL; 896333347Speter 897333347Speter SVN_ERR(fetch_base_props(&base_props, eb->changes, ev1_relpath, 898333347Speter eb->fetch_func, eb->fetch_baton, 899333347Speter scratch_pool, scratch_pool)); 900333347Speter 901333347Speter /* Are we editing the root of the tree? */ 902333347Speter if (parent_baton == NULL) 903333347Speter { 904333347Speter /* The root dir was already opened. */ 905333347Speter *dir_baton = eb->ev1_root_dir_baton; 906333347Speter 907333347Speter /* Only property edits are allowed on the root. */ 908333347Speter SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE); 909333347Speter SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props, 910362181Sdim editor, *dir_baton, scratch_pool)); 911333347Speter 912333347Speter /* No further action possible for the root. */ 913333347Speter return SVN_NO_ERROR; 914333347Speter } 915333347Speter 916333347Speter if (change->action == RESTRUCTURE_DELETE) 917333347Speter { 918362181Sdim SVN_ERR(editor->delete_entry(ev1_relpath, change->deleting_rev, 919362181Sdim parent_baton, scratch_pool)); 920333347Speter 921333347Speter /* No futher action possible for this node. */ 922333347Speter return SVN_NO_ERROR; 923333347Speter } 924333347Speter 925333347Speter /* If we're not deleting this node, then we should know its kind. */ 926333347Speter SVN_ERR_ASSERT(change->kind != svn_node_unknown); 927333347Speter 928333347Speter#ifdef SHIM_WITH_ADD_ABSENT 929333347Speter if (change->action == RESTRUCTURE_ADD_ABSENT) 930333347Speter { 931333347Speter if (change->kind == svn_node_dir) 932362181Sdim SVN_ERR(editor->absent_directory(ev1_relpath, parent_baton, 933362181Sdim scratch_pool)); 934333347Speter else if (change->kind == svn_node_file) 935362181Sdim SVN_ERR(editor->absent_file(ev1_relpath, parent_baton, 936362181Sdim scratch_pool)); 937333347Speter else 938333347Speter SVN_ERR_MALFUNCTION(); 939333347Speter 940333347Speter /* No further action possible for this node. */ 941333347Speter return SVN_NO_ERROR; 942333347Speter } 943333347Speter#endif 944333347Speter /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */ 945333347Speter 946333347Speter if (change->action == RESTRUCTURE_ADD) 947333347Speter { 948333347Speter const char *copyfrom_url = NULL; 949333347Speter svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 950333347Speter 951333347Speter /* Do we have an old node to delete first? If so, delete it. */ 952333347Speter if (change->deleting) 953362181Sdim SVN_ERR(editor->delete_entry(ev1_relpath, change->deleting_rev, 954362181Sdim parent_baton, scratch_pool)); 955333347Speter 956333347Speter /* If it's a copy, determine the copy source location. */ 957333347Speter if (change->copyfrom_path) 958333347Speter { 959333347Speter /* ### What's this about URL vs. fspath? REPOS_ROOT_URL isn't 960333347Speter optional, is it, at least in a commit editor? */ 961333347Speter if (eb->repos_root_url) 962333347Speter copyfrom_url = svn_path_url_add_component2(eb->repos_root_url, 963333347Speter change->copyfrom_path, 964333347Speter scratch_pool); 965333347Speter else 966333347Speter { 967333347Speter copyfrom_url = change->copyfrom_path; 968333347Speter 969333347Speter /* Make this an FS path by prepending "/" */ 970333347Speter if (copyfrom_url[0] != '/') 971333347Speter copyfrom_url = apr_pstrcat(scratch_pool, "/", 972333347Speter copyfrom_url, SVN_VA_NULL); 973333347Speter } 974333347Speter 975333347Speter copyfrom_rev = change->copyfrom_rev; 976333347Speter } 977333347Speter 978333347Speter if (change->kind == svn_node_dir) 979362181Sdim SVN_ERR(editor->add_directory(ev1_relpath, parent_baton, 980362181Sdim copyfrom_url, copyfrom_rev, 981362181Sdim result_pool, dir_baton)); 982333347Speter else if (change->kind == svn_node_file) 983362181Sdim SVN_ERR(editor->add_file(ev1_relpath, parent_baton, 984362181Sdim copyfrom_url, copyfrom_rev, 985362181Sdim result_pool, &file_baton)); 986333347Speter else 987333347Speter SVN_ERR_MALFUNCTION(); 988333347Speter } 989333347Speter else /* RESTRUCTURE_NONE */ 990333347Speter { 991333347Speter /* ### The code that inserts a "plain edit" change record sets 992333347Speter 'changing_rev' to the peg rev of the pegged part of the path, 993333347Speter even when the full path refers to a child of a copy. Should we 994333347Speter instead be using the copy source rev here, in that case? (Like 995333347Speter when we fetch the base properties.) */ 996333347Speter 997333347Speter if (change->kind == svn_node_dir) 998362181Sdim SVN_ERR(editor->open_directory(ev1_relpath, parent_baton, 999362181Sdim change->changing_rev, 1000362181Sdim result_pool, dir_baton)); 1001333347Speter else if (change->kind == svn_node_file) 1002362181Sdim SVN_ERR(editor->open_file(ev1_relpath, parent_baton, 1003362181Sdim change->changing_rev, 1004362181Sdim result_pool, &file_baton)); 1005333347Speter else 1006333347Speter SVN_ERR_MALFUNCTION(); 1007333347Speter } 1008333347Speter 1009333347Speter /* Apply any properties in CHANGE to the node. */ 1010333347Speter if (change->kind == svn_node_dir) 1011333347Speter SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props, 1012362181Sdim editor, *dir_baton, scratch_pool)); 1013333347Speter else 1014333347Speter SVN_ERR(drive_ev1_props(ev1_relpath, change, base_props, 1015362181Sdim editor, file_baton, scratch_pool)); 1016333347Speter 1017333347Speter /* Send the text content delta, if new text content is provided. */ 1018333347Speter if (change->contents_text) 1019333347Speter { 1020333347Speter svn_stream_t *read_stream; 1021333347Speter svn_txdelta_window_handler_t handler; 1022333347Speter void *handler_baton; 1023333347Speter 1024333347Speter read_stream = svn_stream_from_stringbuf(change->contents_text, 1025333347Speter scratch_pool); 1026333347Speter /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the 1027333347Speter ### shim code... */ 1028362181Sdim SVN_ERR(editor->apply_textdelta(file_baton, NULL, scratch_pool, 1029333347Speter &handler, &handler_baton)); 1030333347Speter /* ### it would be nice to send a true txdelta here, but whatever. */ 1031333347Speter SVN_ERR(svn_txdelta_send_stream(read_stream, handler, handler_baton, 1032333347Speter NULL, scratch_pool)); 1033333347Speter SVN_ERR(svn_stream_close(read_stream)); 1034333347Speter } 1035333347Speter 1036333347Speter if (file_baton) 1037333347Speter { 1038362181Sdim SVN_ERR(editor->close_file(file_baton, NULL, scratch_pool)); 1039333347Speter } 1040333347Speter 1041333347Speter return SVN_NO_ERROR; 1042333347Speter} 1043333347Speter 1044333347Speter/* 1045333347Speter * ======================================================================== 1046333347Speter * Old-repository storage paths for branch elements 1047333347Speter * ======================================================================== 1048333347Speter * 1049333347Speter * To support top-level branches, we map each top-level branch to its own 1050333347Speter * directory in the old repository, with each nested branch in a subdirectory: 1051333347Speter * 1052333347Speter * B0 => ^/top0/... 1053333347Speter * ^/top0/.../trunk/... <= B0.12 1054333347Speter * B1 => ^/top1/... 1055333347Speter * 1056333347Speter * It may be better to put each branch in its own directory: 1057333347Speter * 1058333347Speter * B0 => ^/B0/... 1059333347Speter * B0.12 => ^/B0.12/... 1060333347Speter * B1 => ^/B1/... 1061333347Speter * 1062333347Speter * (A branch root is not necessarily a directory, it could be a file.) 1063333347Speter */ 1064333347Speter 1065333347Speter/* Get the old-repository path for the storage of the root element of BRANCH. 1066333347Speter * 1067333347Speter * Currently, this is the same as the nested-branching hierarchical path 1068333347Speter * (and thus assumes there is only one top-level branch). 1069333347Speter */ 1070333347Speterstatic const char * 1071333347Speterbranch_get_storage_root_rrpath(const svn_branch__state_t *branch, 1072333347Speter apr_pool_t *result_pool) 1073333347Speter{ 1074333347Speter int top_branch_num = atoi(branch->bid + 1); 1075333347Speter const char *top_path = apr_psprintf(result_pool, "top%d", top_branch_num); 1076333347Speter const char *nested_path = svn_branch__get_root_rrpath(branch, result_pool); 1077333347Speter 1078333347Speter return svn_relpath_join(top_path, nested_path, result_pool); 1079333347Speter} 1080333347Speter 1081333347Speter/* Get the old-repository path for the storage of element EID of BRANCH. 1082333347Speter * 1083333347Speter * If the element EID doesn't exist in BRANCH, return NULL. 1084333347Speter */ 1085333347Speterstatic const char * 1086333347Speterbranch_get_storage_rrpath_by_eid(const svn_branch__state_t *branch, 1087333347Speter int eid, 1088333347Speter apr_pool_t *result_pool) 1089333347Speter{ 1090333347Speter const char *path = svn_branch__get_path_by_eid(branch, eid, result_pool); 1091333347Speter const char *rrpath = NULL; 1092333347Speter 1093333347Speter if (path) 1094333347Speter { 1095333347Speter rrpath = svn_relpath_join(branch_get_storage_root_rrpath(branch, 1096333347Speter result_pool), 1097333347Speter path, result_pool); 1098333347Speter } 1099333347Speter return rrpath; 1100333347Speter} 1101333347Speter 1102333347Speter/* Return, in *STORAGE_PATHREV_P, the storage-rrpath-rev for BRANCH_REF. 1103333347Speter */ 1104333347Speterstatic svn_error_t * 1105333347Speterstorage_pathrev_from_branch_ref(svn_pathrev_t *storage_pathrev_p, 1106333347Speter svn_element__branch_ref_t branch_ref, 1107333347Speter svn_branch__repos_t *repos, 1108333347Speter apr_pool_t *result_pool, 1109333347Speter apr_pool_t *scratch_pool) 1110333347Speter{ 1111333347Speter svn_branch__el_rev_id_t *el_rev; 1112333347Speter 1113333347Speter SVN_ERR(svn_branch__repos_find_el_rev_by_id(&el_rev, 1114333347Speter repos, 1115333347Speter branch_ref.rev, 1116333347Speter branch_ref.branch_id, 1117333347Speter branch_ref.eid, 1118333347Speter scratch_pool, scratch_pool)); 1119333347Speter 1120333347Speter storage_pathrev_p->rev = el_rev->rev; 1121333347Speter storage_pathrev_p->relpath 1122333347Speter = branch_get_storage_rrpath_by_eid(el_rev->branch, el_rev->eid, 1123333347Speter result_pool); 1124333347Speter 1125333347Speter return SVN_NO_ERROR; 1126333347Speter} 1127333347Speter 1128333347Speter/* 1129333347Speter * ======================================================================== 1130333347Speter * Editor for Commit (independent per-element changes; element-id addressing) 1131333347Speter * ======================================================================== 1132333347Speter */ 1133333347Speter 1134333347Speter/* */ 1135333347Speter#define PAYLOAD_IS_ONLY_BY_REFERENCE(payload) \ 1136333347Speter ((payload)->kind == svn_node_unknown) 1137333347Speter 1138333347Speter/* Fetch a payload as *PAYLOAD_P from its storage-pathrev PATH_REV. 1139333347Speter * Fetch names of immediate children of PATH_REV as *CHILDREN_NAMES. 1140333347Speter * Either of the outputs may be null if not wanted. 1141333347Speter */ 1142333347Speterstatic svn_error_t * 1143333347Speterpayload_fetch(svn_element__payload_t **payload_p, 1144333347Speter apr_hash_t **children_names, 1145333347Speter svn_branch__txn_priv_t *eb, 1146333347Speter const svn_pathrev_t *path_rev, 1147333347Speter apr_pool_t *result_pool, 1148333347Speter apr_pool_t *scratch_pool) 1149333347Speter{ 1150333347Speter svn_element__payload_t *payload 1151333347Speter = apr_pcalloc(result_pool, sizeof (*payload)); 1152333347Speter 1153333347Speter SVN_ERR(eb->fetch_func(&payload->kind, 1154333347Speter &payload->props, 1155333347Speter &payload->text, 1156333347Speter children_names, 1157333347Speter eb->fetch_baton, 1158333347Speter path_rev->relpath, path_rev->rev, 1159333347Speter result_pool, scratch_pool)); 1160333347Speter 1161333347Speter SVN_ERR_ASSERT(svn_element__payload_invariants(payload)); 1162333347Speter SVN_ERR_ASSERT(payload->kind == svn_node_dir 1163333347Speter || payload->kind == svn_node_file); 1164333347Speter if (payload_p) 1165333347Speter *payload_p = payload; 1166333347Speter return SVN_NO_ERROR; 1167333347Speter} 1168333347Speter 1169333347Speter/* Return the storage-pathrev of PAYLOAD as *STORAGE_PATHREV_P. 1170333347Speter * 1171333347Speter * Find the storage-pathrev from PAYLOAD->branch_ref. 1172333347Speter */ 1173333347Speterstatic svn_error_t * 1174333347Speterpayload_get_storage_pathrev(svn_pathrev_t *storage_pathrev_p, 1175333347Speter svn_element__payload_t *payload, 1176333347Speter svn_branch__repos_t *repos, 1177333347Speter apr_pool_t *result_pool) 1178333347Speter{ 1179333347Speter SVN_ERR_ASSERT(payload->branch_ref.branch_id /* && ... */); 1180333347Speter 1181333347Speter SVN_ERR(storage_pathrev_from_branch_ref(storage_pathrev_p, 1182333347Speter payload->branch_ref, repos, 1183333347Speter result_pool, result_pool)); 1184333347Speter return SVN_NO_ERROR; 1185333347Speter} 1186333347Speter 1187333347Spetersvn_error_t * 1188333347Spetersvn_branch__compat_fetch(svn_element__payload_t **payload_p, 1189333347Speter svn_branch__txn_t *txn, 1190333347Speter svn_element__branch_ref_t branch_ref, 1191333347Speter svn_branch__compat_fetch_func_t fetch_func, 1192333347Speter void *fetch_baton, 1193333347Speter apr_pool_t *result_pool, 1194333347Speter apr_pool_t *scratch_pool) 1195333347Speter{ 1196333347Speter svn_branch__txn_priv_t eb; 1197333347Speter svn_pathrev_t storage_pathrev; 1198333347Speter 1199333347Speter /* Simulate the existence of /top0 in r0. */ 1200333347Speter if (branch_ref.rev == 0 && branch_ref.eid == 0) 1201333347Speter { 1202333347Speter *payload_p = svn_element__payload_create_dir(apr_hash_make(result_pool), 1203333347Speter result_pool); 1204333347Speter return SVN_NO_ERROR; 1205333347Speter } 1206333347Speter 1207333347Speter eb.txn = txn; 1208333347Speter eb.fetch_func = fetch_func; 1209333347Speter eb.fetch_baton = fetch_baton; 1210333347Speter 1211333347Speter SVN_ERR(storage_pathrev_from_branch_ref(&storage_pathrev, 1212333347Speter branch_ref, txn->repos, 1213333347Speter scratch_pool, scratch_pool)); 1214333347Speter 1215333347Speter SVN_ERR(payload_fetch(payload_p, NULL, 1216333347Speter &eb, &storage_pathrev, result_pool, scratch_pool)); 1217333347Speter (*payload_p)->branch_ref = branch_ref; 1218333347Speter return SVN_NO_ERROR; 1219333347Speter} 1220333347Speter 1221333347Speter/* Fill in the actual payload, from its reference, if not already done. 1222333347Speter */ 1223333347Speterstatic svn_error_t * 1224333347Speterpayload_resolve(svn_element__payload_t *payload, 1225333347Speter svn_branch__txn_priv_t *eb, 1226333347Speter apr_pool_t *scratch_pool) 1227333347Speter{ 1228333347Speter svn_pathrev_t storage; 1229333347Speter 1230333347Speter SVN_ERR_ASSERT(svn_element__payload_invariants(payload)); 1231333347Speter 1232333347Speter if (! PAYLOAD_IS_ONLY_BY_REFERENCE(payload)) 1233333347Speter return SVN_NO_ERROR; 1234333347Speter 1235333347Speter SVN_ERR(payload_get_storage_pathrev(&storage, payload, 1236333347Speter eb->txn->repos, 1237333347Speter scratch_pool)); 1238333347Speter 1239333347Speter SVN_ERR(eb->fetch_func(&payload->kind, 1240333347Speter &payload->props, 1241333347Speter &payload->text, 1242333347Speter NULL, 1243333347Speter eb->fetch_baton, 1244333347Speter storage.relpath, storage.rev, 1245333347Speter payload->pool, scratch_pool)); 1246333347Speter 1247333347Speter SVN_ERR_ASSERT(svn_element__payload_invariants(payload)); 1248333347Speter SVN_ERR_ASSERT(! PAYLOAD_IS_ONLY_BY_REFERENCE(payload)); 1249333347Speter return SVN_NO_ERROR; 1250333347Speter} 1251333347Speter 1252333347Speter/* Update *PATHS, a hash of (storage_rrpath -> svn_branch__el_rev_id_t), 1253333347Speter * creating or filling in entries for all elements in BRANCH. 1254333347Speter */ 1255333347Speterstatic svn_error_t * 1256333347Speterconvert_branch_to_paths(apr_hash_t *paths, 1257333347Speter svn_branch__state_t *branch, 1258333347Speter apr_pool_t *result_pool, 1259333347Speter apr_pool_t *scratch_pool) 1260333347Speter{ 1261333347Speter apr_hash_index_t *hi; 1262333347Speter svn_element__tree_t *elements; 1263333347Speter 1264333347Speter /* assert(branch is at a sequence point); */ 1265333347Speter 1266333347Speter SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool)); 1267333347Speter for (hi = apr_hash_first(scratch_pool, elements->e_map); 1268333347Speter hi; hi = apr_hash_next(hi)) 1269333347Speter { 1270333347Speter int eid = svn_eid__hash_this_key(hi); 1271333347Speter svn_element__content_t *element = apr_hash_this_val(hi); 1272333347Speter const char *rrpath 1273333347Speter = branch_get_storage_rrpath_by_eid(branch, eid, result_pool); 1274333347Speter svn_branch__el_rev_id_t *ba; 1275333347Speter 1276333347Speter /* A subbranch-root element carries no payload; the corresponding 1277333347Speter inner branch root element will provide the payload for this path. */ 1278333347Speter if (element->payload->is_subbranch_root) 1279333347Speter continue; 1280333347Speter 1281333347Speter /* No other element should exist at this path, given that we avoid 1282333347Speter storing anything for a subbranch-root element. */ 1283333347Speter SVN_ERR_ASSERT(! svn_hash_gets(paths, rrpath)); 1284333347Speter 1285333347Speter /* Fill in the details. */ 1286333347Speter ba = svn_branch__el_rev_id_create(branch, eid, branch->txn->rev, 1287333347Speter result_pool); 1288333347Speter svn_hash_sets(paths, rrpath, ba); 1289333347Speter /*SVN_DBG(("branch-to-path[%d]: b%s e%d -> %s", 1290333347Speter i, svn_branch__get_id(branch, scratch_pool), eid, rrpath));*/ 1291333347Speter } 1292333347Speter return SVN_NO_ERROR; 1293333347Speter} 1294333347Speter 1295333347Speter/* Produce a mapping from paths to element ids, covering all elements in 1296333347Speter * BRANCH and all its sub-branches, recursively. 1297333347Speter * 1298333347Speter * Update *PATHS_UNION, a hash of (storage_rrpath -> svn_branch__el_rev_id_t), 1299333347Speter * creating or filling in entries for all elements in all branches at and 1300333347Speter * under BRANCH, recursively. 1301333347Speter */ 1302333347Speterstatic svn_error_t * 1303333347Speterconvert_branch_to_paths_r(apr_hash_t *paths_union, 1304333347Speter svn_branch__state_t *branch, 1305333347Speter apr_pool_t *result_pool, 1306333347Speter apr_pool_t *scratch_pool) 1307333347Speter{ 1308333347Speter apr_array_header_t *subbranches; 1309333347Speter int i; 1310333347Speter 1311333347Speter /*SVN_DBG(("[%d] branch={b%s e%d at '%s'}", idx, 1312333347Speter svn_branch__get_id(branch, scratch_pool), branch->root_eid, 1313333347Speter svn_branch__get_root_rrpath(branch, scratch_pool)));*/ 1314333347Speter SVN_ERR(convert_branch_to_paths(paths_union, branch, 1315333347Speter result_pool, scratch_pool)); 1316333347Speter 1317333347Speter SVN_ERR(svn_branch__get_immediate_subbranches(branch, &subbranches, 1318333347Speter scratch_pool, scratch_pool)); 1319333347Speter /* Rercurse into sub-branches */ 1320333347Speter for (i = 0; i < subbranches->nelts; i++) 1321333347Speter { 1322333347Speter svn_branch__state_t *b = APR_ARRAY_IDX(subbranches, i, void *); 1323333347Speter 1324333347Speter SVN_ERR(convert_branch_to_paths_r(paths_union, b, result_pool, 1325333347Speter scratch_pool)); 1326333347Speter } 1327333347Speter return SVN_NO_ERROR; 1328333347Speter} 1329333347Speter 1330333347Speter/* Return TRUE iff INITIAL_PAYLOAD and FINAL_PAYLOAD are both non-null 1331333347Speter * and have the same properties. 1332333347Speter */ 1333333347Speterstatic svn_boolean_t 1334333347Speterprops_equal(svn_element__payload_t *initial_payload, 1335333347Speter svn_element__payload_t *final_payload, 1336333347Speter apr_pool_t *scratch_pool) 1337333347Speter{ 1338333347Speter apr_array_header_t *prop_diffs; 1339333347Speter 1340333347Speter if (!initial_payload || !final_payload) 1341333347Speter return FALSE; 1342333347Speter 1343333347Speter svn_error_clear(svn_prop_diffs(&prop_diffs, 1344333347Speter initial_payload->props, 1345333347Speter final_payload->props, 1346333347Speter scratch_pool)); 1347333347Speter return (prop_diffs->nelts == 0); 1348333347Speter} 1349333347Speter 1350333347Speter/* Return TRUE iff INITIAL_PAYLOAD and FINAL_PAYLOAD are both file payload 1351333347Speter * and have the same text. 1352333347Speter */ 1353333347Speterstatic svn_boolean_t 1354333347Spetertext_equal(svn_element__payload_t *initial_payload, 1355333347Speter svn_element__payload_t *final_payload) 1356333347Speter{ 1357333347Speter if (!initial_payload || !final_payload 1358333347Speter || initial_payload->kind != svn_node_file 1359333347Speter || final_payload->kind != svn_node_file) 1360333347Speter { 1361333347Speter return FALSE; 1362333347Speter } 1363333347Speter 1364333347Speter return svn_stringbuf_compare(initial_payload->text, 1365333347Speter final_payload->text); 1366333347Speter} 1367333347Speter 1368333347Speter/* Return the copy-from location to be used if this is to be a copy; 1369333347Speter * otherwise return NULL. 1370333347Speter * 1371333347Speter * ### Currently this is indicated by payload-by-reference, which is 1372333347Speter * an inadequate indication. 1373333347Speter */ 1374333347Speterstatic svn_error_t * 1375333347Speterget_copy_from(svn_pathrev_t *copyfrom_pathrev_p, 1376333347Speter svn_element__payload_t *final_payload, 1377333347Speter svn_branch__txn_priv_t *eb, 1378333347Speter apr_pool_t *result_pool) 1379333347Speter{ 1380333347Speter if (final_payload->branch_ref.branch_id) 1381333347Speter { 1382333347Speter SVN_ERR(payload_get_storage_pathrev(copyfrom_pathrev_p, final_payload, 1383333347Speter eb->txn->repos, 1384333347Speter result_pool)); 1385333347Speter } 1386333347Speter else 1387333347Speter { 1388333347Speter copyfrom_pathrev_p->relpath = NULL; 1389333347Speter copyfrom_pathrev_p->rev = SVN_INVALID_REVNUM; 1390333347Speter } 1391333347Speter 1392333347Speter return SVN_NO_ERROR; 1393333347Speter} 1394333347Speter 1395333347Speter/* Return a hash whose keys are the names of the immediate children of 1396333347Speter * RRPATH in PATHS. 1397333347Speter */ 1398333347Speterstatic apr_hash_t * 1399333347Speterget_immediate_children_names(apr_hash_t *paths, 1400333347Speter const char *parent_rrpath, 1401333347Speter apr_pool_t *result_pool, 1402333347Speter apr_pool_t *scratch_pool) 1403333347Speter{ 1404333347Speter apr_hash_t *children = apr_hash_make(result_pool); 1405333347Speter apr_hash_index_t *hi; 1406333347Speter 1407333347Speter for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi)) 1408333347Speter { 1409333347Speter const char *this_rrpath = apr_hash_this_key(hi); 1410333347Speter 1411333347Speter if (this_rrpath[0] 1412333347Speter && strcmp(parent_rrpath, svn_relpath_dirname(this_rrpath, 1413333347Speter scratch_pool)) == 0) 1414333347Speter { 1415333347Speter svn_hash_sets(children, 1416333347Speter svn_relpath_basename(this_rrpath, result_pool), ""); 1417333347Speter } 1418333347Speter } 1419333347Speter 1420333347Speter return children; 1421333347Speter} 1422333347Speter 1423333347Speter/* Generate Ev1 instructions to edit from a current state to a final state 1424333347Speter * at RRPATH, recursing for child paths of RRPATH. 1425333347Speter * 1426333347Speter * The current state at RRPATH might not be the initial state because, 1427333347Speter * although neither RRPATH nor any sub-paths have been explicitly visited 1428333347Speter * before, the current state at RRPATH and its sub-paths might be the 1429333347Speter * result of a copy. 1430333347Speter * 1431333347Speter * PRED_LOC is the predecessor location of the node currently at RRPATH in 1432333347Speter * the Ev1 transaction, or NULL if there is no node currently at RRPATH. 1433333347Speter * If the node is copied, including a child of a copy, this is its copy-from 1434333347Speter * location, otherwise this is its location in the txn base revision. 1435333347Speter * (The current node cannot be a plain added node on entry to this function, 1436333347Speter * as the function must be called only once for each path and there is no 1437333347Speter * recursive add operation.) PRED_LOC identifies the node content that the 1438333347Speter * that the Ev1 edit needs to delete, replace, update or leave unchanged. 1439333347Speter * 1440333347Speter * Process a single hierarchy of nested branches, rooted in the top-level 1441333347Speter * branch TOP_BRANCH_NUM. 1442333347Speter */ 1443333347Speterstatic svn_error_t * 1444333347Speterdrive_changes_r(const char *rrpath, 1445333347Speter svn_pathrev_t *pred_loc, 1446333347Speter apr_hash_t *paths_final, 1447333347Speter const char *top_branch_id, 1448333347Speter svn_branch__txn_priv_t *eb, 1449333347Speter apr_pool_t *scratch_pool) 1450333347Speter{ 1451333347Speter /* The el-rev-id of the element that will finally exist at RRPATH. */ 1452333347Speter svn_branch__el_rev_id_t *final_el_rev = svn_hash_gets(paths_final, rrpath); 1453333347Speter svn_element__payload_t *final_payload; 1454333347Speter svn_pathrev_t final_copy_from; 1455333347Speter svn_boolean_t succession; 1456333347Speter 1457333347Speter /*SVN_DBG(("rrpath '%s' current=%s, final=e%d)", 1458333347Speter rrpath, 1459333347Speter pred_loc ? pathrev_str(*pred_loc, scratch_pool) : "<nil>", 1460333347Speter final_el_rev ? final_el_rev->eid : -1));*/ 1461333347Speter 1462333347Speter SVN_ERR_ASSERT(!pred_loc 1463333347Speter || (pred_loc->relpath && SVN_IS_VALID_REVNUM(pred_loc->rev))); 1464333347Speter 1465333347Speter if (final_el_rev) 1466333347Speter { 1467333347Speter svn_element__content_t *final_element; 1468333347Speter 1469333347Speter SVN_ERR(svn_branch__state_get_element(final_el_rev->branch, &final_element, 1470333347Speter final_el_rev->eid, scratch_pool)); 1471333347Speter /* A non-null FINAL address means an element exists there. */ 1472333347Speter SVN_ERR_ASSERT(final_element); 1473333347Speter 1474333347Speter final_payload = final_element->payload; 1475333347Speter 1476333347Speter /* Decide whether the state at this path should be a copy (incl. a 1477333347Speter copy-child) */ 1478333347Speter SVN_ERR(get_copy_from(&final_copy_from, final_payload, eb, scratch_pool)); 1479333347Speter /* It doesn't make sense to have a non-copy inside a copy */ 1480333347Speter /*SVN_ERR_ASSERT(!(parent_is_copy && !final_copy_from));*/ 1481333347Speter } 1482333347Speter else 1483333347Speter { 1484333347Speter final_payload = NULL; 1485333347Speter final_copy_from.relpath = NULL; 1486333347Speter } 1487333347Speter 1488333347Speter /* Succession means: 1489333347Speter for a copy (inc. child) -- copy-from same place as natural predecessor 1490333347Speter otherwise -- it's succession if it's the same element 1491333347Speter (which also implies the same kind) */ 1492333347Speter if (pred_loc && final_copy_from.relpath) 1493333347Speter { 1494333347Speter succession = pathrev_equal(pred_loc, &final_copy_from); 1495333347Speter } 1496333347Speter else if (pred_loc && final_el_rev) 1497333347Speter { 1498333347Speter svn_branch__el_rev_id_t *pred_el_rev; 1499333347Speter 1500333347Speter SVN_ERR(svn_branch__repos_find_el_rev_by_path_rev(&pred_el_rev, 1501333347Speter eb->txn->repos, 1502333347Speter pred_loc->rev, 1503333347Speter top_branch_id, 1504333347Speter pred_loc->relpath, 1505333347Speter scratch_pool, scratch_pool)); 1506333347Speter 1507333347Speter succession = (pred_el_rev->eid == final_el_rev->eid); 1508333347Speter } 1509333347Speter else 1510333347Speter { 1511333347Speter succession = FALSE; 1512333347Speter } 1513333347Speter 1514333347Speter /* If there's an initial node that isn't also going to be the final 1515333347Speter node at this path, then it's being deleted or replaced: delete it. */ 1516333347Speter if (pred_loc && !succession) 1517333347Speter { 1518333347Speter /* Issue an Ev1 delete unless this path is inside a path at which 1519333347Speter we've already issued a delete. */ 1520333347Speter if (check_existence(eb->changes, rrpath) != svn_tristate_false) 1521333347Speter { 1522333347Speter /*SVN_DBG(("ev1:del(%s)", rrpath));*/ 1523333347Speter /* ### We don't need "delete_subtree", we only need to insert a 1524333347Speter single delete operation, as we know we haven't 1525333347Speter inserted any changes inside this subtree. */ 1526333347Speter SVN_ERR(delete_subtree(eb->changes, rrpath, pred_loc->rev)); 1527333347Speter } 1528333347Speter else 1529333347Speter { 1530333347Speter /*SVN_DBG(("ev1:del(%s): parent is already deleted", rrpath))*/ 1531333347Speter } 1532333347Speter } 1533333347Speter 1534333347Speter /* If there's a final node, it's being added or modified. 1535333347Speter Or it's unchanged -- we do nothing in that case. */ 1536333347Speter if (final_el_rev) 1537333347Speter { 1538333347Speter svn_element__payload_t *current_payload = NULL; 1539333347Speter apr_hash_t *current_children = NULL; 1540333347Speter change_node_t *change = NULL; 1541333347Speter 1542333347Speter /* Get the full payload of the final node. If we have 1543333347Speter only a reference to the payload, fetch it in full. */ 1544333347Speter SVN_ERR_ASSERT(final_payload); 1545333347Speter SVN_ERR(payload_resolve(final_payload, eb, scratch_pool)); 1546333347Speter 1547333347Speter /* If the final node was also the initial node, it's being 1548333347Speter modified, otherwise it's being added (perhaps a replacement). */ 1549333347Speter if (succession) 1550333347Speter { 1551333347Speter /* Get full payload of the current node */ 1552333347Speter SVN_ERR(payload_fetch(¤t_payload, ¤t_children, 1553333347Speter eb, pred_loc, 1554333347Speter scratch_pool, scratch_pool)); 1555333347Speter 1556333347Speter /* If no changes to make, then skip this path */ 1557333347Speter if (svn_element__payload_equal(current_payload, 1558333347Speter final_payload, scratch_pool)) 1559333347Speter { 1560333347Speter /*SVN_DBG(("ev1:no-op(%s)", rrpath));*/ 1561333347Speter } 1562333347Speter else 1563333347Speter { 1564333347Speter /*SVN_DBG(("ev1:mod(%s)", rrpath));*/ 1565333347Speter SVN_ERR(insert_change(&change, eb->changes, rrpath, 1566333347Speter RESTRUCTURE_NONE)); 1567333347Speter change->changing_rev = pred_loc->rev; 1568333347Speter } 1569333347Speter } 1570333347Speter else /* add or copy/move */ 1571333347Speter { 1572333347Speter /*SVN_DBG(("ev1:add(%s)", rrpath));*/ 1573333347Speter SVN_ERR(insert_change(&change, eb->changes, rrpath, 1574333347Speter RESTRUCTURE_ADD)); 1575333347Speter 1576333347Speter /* If the node is to be copied (and possibly modified) ... */ 1577333347Speter if (final_copy_from.relpath) 1578333347Speter { 1579333347Speter change->copyfrom_rev = final_copy_from.rev; 1580333347Speter change->copyfrom_path = final_copy_from.relpath; 1581333347Speter 1582333347Speter /* Get full payload of the copy source node */ 1583333347Speter SVN_ERR(payload_fetch(¤t_payload, ¤t_children, 1584333347Speter eb, &final_copy_from, 1585333347Speter scratch_pool, scratch_pool)); 1586333347Speter } 1587333347Speter } 1588333347Speter 1589333347Speter if (change) 1590333347Speter { 1591333347Speter /* Copy the required content into the change record. Avoid no-op 1592333347Speter changes of props / text, not least to minimize clutter when 1593333347Speter debugging Ev1 operations. */ 1594333347Speter SVN_ERR_ASSERT(final_payload->kind == svn_node_dir 1595333347Speter || final_payload->kind == svn_node_file); 1596333347Speter change->kind = final_payload->kind; 1597333347Speter if (!props_equal(current_payload, final_payload, scratch_pool)) 1598333347Speter { 1599333347Speter change->props = final_payload->props; 1600333347Speter } 1601333347Speter if (final_payload->kind == svn_node_file 1602333347Speter && !text_equal(current_payload, final_payload)) 1603333347Speter { 1604333347Speter change->contents_text = final_payload->text; 1605333347Speter } 1606333347Speter } 1607333347Speter 1608333347Speter /* Recurse to process this directory's children */ 1609333347Speter if (final_payload->kind == svn_node_dir) 1610333347Speter { 1611333347Speter apr_hash_t *final_children; 1612333347Speter apr_hash_t *union_children; 1613333347Speter apr_hash_index_t *hi; 1614333347Speter 1615333347Speter final_children = get_immediate_children_names(paths_final, rrpath, 1616333347Speter scratch_pool, 1617333347Speter scratch_pool); 1618333347Speter union_children = (current_children 1619333347Speter ? hash_overlay(current_children, final_children) 1620333347Speter : final_children); 1621333347Speter for (hi = apr_hash_first(scratch_pool, union_children); 1622333347Speter hi; hi = apr_hash_next(hi)) 1623333347Speter { 1624333347Speter const char *name = apr_hash_this_key(hi); 1625333347Speter const char *this_rrpath = svn_relpath_join(rrpath, name, 1626333347Speter scratch_pool); 1627333347Speter svn_boolean_t child_in_current 1628333347Speter = current_children && svn_hash_gets(current_children, name); 1629333347Speter svn_pathrev_t *child_pred = NULL; 1630333347Speter 1631333347Speter if (child_in_current) 1632333347Speter { 1633333347Speter /* If the parent dir is copied, then this child has been 1634333347Speter copied along with it: predecessor is parent's copy-from 1635333347Speter location extended by the child's name. */ 1636333347Speter child_pred = apr_palloc(scratch_pool, sizeof(*child_pred)); 1637333347Speter if (final_copy_from.relpath) 1638333347Speter { 1639333347Speter child_pred->rev = final_copy_from.rev; 1640333347Speter child_pred->relpath 1641333347Speter = svn_relpath_join(final_copy_from.relpath, name, 1642333347Speter scratch_pool); 1643333347Speter } 1644333347Speter else 1645333347Speter { 1646333347Speter child_pred->rev = pred_loc->rev; 1647333347Speter child_pred->relpath = this_rrpath; 1648333347Speter } 1649333347Speter } 1650333347Speter 1651333347Speter SVN_ERR(drive_changes_r(this_rrpath, 1652333347Speter child_pred, 1653333347Speter paths_final, top_branch_id, 1654333347Speter eb, scratch_pool)); 1655333347Speter } 1656333347Speter } 1657333347Speter } 1658333347Speter 1659333347Speter return SVN_NO_ERROR; 1660333347Speter} 1661333347Speter 1662333347Speter/* 1663333347Speter * Drive svn_delta_editor_t (actions: add/copy/delete/modify) from 1664333347Speter * a before-and-after element mapping. 1665333347Speter */ 1666333347Speterstatic svn_error_t * 1667333347Speterdrive_changes(svn_branch__txn_priv_t *eb, 1668333347Speter apr_pool_t *scratch_pool) 1669333347Speter{ 1670333347Speter apr_array_header_t *branches; 1671333347Speter int i; 1672333347Speter const apr_array_header_t *paths; 1673333347Speter 1674333347Speter /* Convert the element mappings to an svn_delta_editor_t traversal. 1675333347Speter 1676333347Speter 1. find union of paths in initial and final states, across all branches. 1677333347Speter 2. traverse paths in depth-first order. 1678333347Speter 3. modify/delete/add/replace as needed at each path. 1679333347Speter */ 1680333347Speter 1681333347Speter /* Process one hierarchy of nested branches at a time. */ 1682333347Speter branches = svn_branch__txn_get_branches(eb->txn, scratch_pool); 1683333347Speter for (i = 0; i < branches->nelts; i++) 1684333347Speter { 1685333347Speter svn_branch__state_t *root_branch = APR_ARRAY_IDX(branches, i, void *); 1686333347Speter apr_hash_t *paths_final; 1687333347Speter 1688333347Speter const char *top_path = branch_get_storage_root_rrpath(root_branch, 1689333347Speter scratch_pool); 1690333347Speter svn_pathrev_t current; 1691333347Speter svn_branch__state_t *base_root_branch; 1692333347Speter svn_boolean_t branch_is_new; 1693333347Speter 1694333347Speter if (strchr(root_branch->bid, '.')) 1695333347Speter continue; /* that's not a root branch */ 1696333347Speter 1697333347Speter SVN_ERR(svn_branch__repos_get_branch_by_id(&base_root_branch, 1698333347Speter eb->txn->repos, 1699333347Speter eb->txn->base_rev, 1700333347Speter root_branch->bid, scratch_pool)); 1701333347Speter branch_is_new = !base_root_branch; 1702333347Speter 1703333347Speter paths_final = apr_hash_make(scratch_pool); 1704333347Speter SVN_ERR(convert_branch_to_paths_r(paths_final, 1705333347Speter root_branch, 1706333347Speter scratch_pool, scratch_pool)); 1707333347Speter 1708333347Speter current.rev = eb->txn->base_rev; 1709333347Speter current.relpath = top_path; 1710333347Speter 1711333347Speter /* Create the top-level storage node if the branch is new, or if this is 1712333347Speter the first commit to branch B0 which was created in r0 but had no 1713333347Speter storage node there. */ 1714333347Speter if (branch_is_new || current.rev == 0) 1715333347Speter { 1716333347Speter change_node_t *change; 1717333347Speter 1718333347Speter SVN_ERR(insert_change(&change, eb->changes, top_path, RESTRUCTURE_ADD)); 1719333347Speter change->kind = svn_node_dir; 1720333347Speter } 1721333347Speter 1722333347Speter SVN_ERR(drive_changes_r(top_path, ¤t, 1723333347Speter paths_final, svn_branch__get_id(root_branch, 1724333347Speter scratch_pool), 1725333347Speter eb, scratch_pool)); 1726333347Speter } 1727333347Speter 1728333347Speter /* If the driver has not explicitly opened the root directory via the 1729333347Speter start_edit (aka open_root) callback, do so now. */ 1730333347Speter if (eb->ev1_root_dir_baton == NULL) 1731333347Speter SVN_ERR(open_root_ev3(eb, SVN_INVALID_REVNUM)); 1732333347Speter 1733333347Speter /* Make the path driver visit the root dir of the edit. Otherwise, it 1734333347Speter will attempt an open_root() instead, which we already did. */ 1735333347Speter if (! svn_hash_gets(eb->changes, "")) 1736333347Speter { 1737333347Speter change_node_t *change; 1738333347Speter 1739333347Speter SVN_ERR(insert_change(&change, eb->changes, "", RESTRUCTURE_NONE)); 1740333347Speter change->kind = svn_node_dir; 1741333347Speter } 1742333347Speter 1743333347Speter /* Apply the appropriate Ev1 change to each Ev1-relative path. */ 1744333347Speter paths = get_unsorted_paths(eb->changes, scratch_pool); 1745362181Sdim SVN_ERR(svn_delta_path_driver3(eb->deditor, eb->dedit_baton, 1746333347Speter paths, TRUE /*sort*/, 1747333347Speter apply_change, (void *)eb, 1748333347Speter scratch_pool)); 1749333347Speter 1750333347Speter return SVN_NO_ERROR; 1751333347Speter} 1752333347Speter 1753333347Speter/* An #svn_branch__txn_t method. */ 1754333347Speterstatic apr_array_header_t * 1755333347Spetercompat_branch_txn_get_branches(const svn_branch__txn_t *txn, 1756333347Speter apr_pool_t *result_pool) 1757333347Speter{ 1758333347Speter /* Just forwarding: nothing more is needed. */ 1759333347Speter apr_array_header_t *branches 1760333347Speter = svn_branch__txn_get_branches(txn->priv->txn, 1761333347Speter result_pool); 1762333347Speter 1763333347Speter return branches; 1764333347Speter} 1765333347Speter 1766333347Speter/* An #svn_branch__txn_t method. */ 1767333347Speterstatic svn_error_t * 1768333347Spetercompat_branch_txn_delete_branch(svn_branch__txn_t *txn, 1769333347Speter const char *bid, 1770333347Speter apr_pool_t *scratch_pool) 1771333347Speter{ 1772333347Speter /* Just forwarding: nothing more is needed. */ 1773333347Speter SVN_ERR(svn_branch__txn_delete_branch(txn->priv->txn, 1774333347Speter bid, 1775333347Speter scratch_pool)); 1776333347Speter return SVN_NO_ERROR; 1777333347Speter} 1778333347Speter 1779333347Speter/* An #svn_branch__txn_t method. */ 1780333347Speterstatic svn_error_t * 1781333347Spetercompat_branch_txn_get_num_new_eids(const svn_branch__txn_t *txn, 1782333347Speter int *num_new_eids_p, 1783333347Speter apr_pool_t *scratch_pool) 1784333347Speter{ 1785333347Speter /* Just forwarding: nothing more is needed. */ 1786333347Speter SVN_ERR(svn_branch__txn_get_num_new_eids(txn->priv->txn, 1787333347Speter num_new_eids_p, 1788333347Speter scratch_pool)); 1789333347Speter return SVN_NO_ERROR; 1790333347Speter} 1791333347Speter 1792333347Speter/* An #svn_branch__txn_t method. */ 1793333347Speterstatic svn_error_t * 1794333347Spetercompat_branch_txn_new_eid(svn_branch__txn_t *txn, 1795333347Speter svn_branch__eid_t *eid_p, 1796333347Speter apr_pool_t *scratch_pool) 1797333347Speter{ 1798333347Speter /* Just forwarding: nothing more is needed. */ 1799333347Speter SVN_ERR(svn_branch__txn_new_eid(txn->priv->txn, 1800333347Speter eid_p, 1801333347Speter scratch_pool)); 1802333347Speter return SVN_NO_ERROR; 1803333347Speter} 1804333347Speter 1805333347Speter/* An #svn_branch__txn_t method. */ 1806333347Speterstatic svn_error_t * 1807333347Spetercompat_branch_txn_finalize_eids(svn_branch__txn_t *txn, 1808333347Speter apr_pool_t *scratch_pool) 1809333347Speter{ 1810333347Speter /* Just forwarding: nothing more is needed. */ 1811333347Speter SVN_ERR(svn_branch__txn_finalize_eids(txn->priv->txn, 1812333347Speter scratch_pool)); 1813333347Speter return SVN_NO_ERROR; 1814333347Speter} 1815333347Speter 1816333347Speter/* An #svn_branch__txn_t method. */ 1817333347Speterstatic svn_error_t * 1818333347Spetercompat_branch_txn_open_branch(svn_branch__txn_t *txn, 1819333347Speter svn_branch__state_t **new_branch_p, 1820333347Speter const char *new_branch_id, 1821333347Speter int root_eid, 1822333347Speter svn_branch__rev_bid_eid_t *tree_ref, 1823333347Speter apr_pool_t *result_pool, 1824333347Speter apr_pool_t *scratch_pool) 1825333347Speter{ 1826333347Speter /* Just forwarding: nothing more is needed. */ 1827333347Speter SVN_ERR(svn_branch__txn_open_branch(txn->priv->txn, 1828333347Speter new_branch_p, 1829333347Speter new_branch_id, root_eid, tree_ref, 1830333347Speter result_pool, 1831333347Speter scratch_pool)); 1832333347Speter return SVN_NO_ERROR; 1833333347Speter} 1834333347Speter 1835333347Speter/* An #svn_branch__txn_t method. */ 1836333347Speterstatic svn_error_t * 1837333347Spetercompat_branch_txn_serialize(svn_branch__txn_t *txn, 1838333347Speter svn_stream_t *stream, 1839333347Speter apr_pool_t *scratch_pool) 1840333347Speter{ 1841333347Speter /* Just forwarding: nothing more is needed. */ 1842333347Speter SVN_ERR(svn_branch__txn_serialize(txn->priv->txn, 1843333347Speter stream, 1844333347Speter scratch_pool)); 1845333347Speter return SVN_NO_ERROR; 1846333347Speter} 1847333347Speter 1848333347Speter/* An #svn_branch__txn_t method. */ 1849333347Speterstatic svn_error_t * 1850333347Spetercompat_branch_txn_sequence_point(svn_branch__txn_t *txn, 1851333347Speter apr_pool_t *scratch_pool) 1852333347Speter{ 1853333347Speter /* Just forwarding: nothing more is needed. */ 1854333347Speter SVN_ERR(svn_branch__txn_sequence_point(txn->priv->txn, 1855333347Speter scratch_pool)); 1856333347Speter return SVN_NO_ERROR; 1857333347Speter} 1858333347Speter 1859333347Speter/* An #svn_branch__txn_t method. */ 1860333347Speterstatic svn_error_t * 1861333347Spetercompat_branch_txn_complete(svn_branch__txn_t *txn, 1862333347Speter apr_pool_t *scratch_pool) 1863333347Speter{ 1864333347Speter svn_branch__txn_priv_t *eb = txn->priv; 1865333347Speter svn_error_t *err; 1866333347Speter 1867333347Speter /* Convert the transaction to a revision */ 1868333347Speter SVN_ERR(svn_branch__txn_sequence_point(txn->priv->txn, scratch_pool)); 1869333347Speter SVN_ERR(svn_branch__txn_finalize_eids(txn->priv->txn, scratch_pool)); 1870333347Speter 1871333347Speter err = drive_changes(eb, scratch_pool); 1872333347Speter 1873333347Speter if (!err) 1874333347Speter { 1875333347Speter err = svn_error_compose_create(err, eb->deditor->close_edit( 1876333347Speter eb->dedit_baton, 1877333347Speter scratch_pool)); 1878333347Speter } 1879333347Speter 1880333347Speter if (err) 1881333347Speter svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool)); 1882333347Speter 1883333347Speter SVN_ERR(svn_branch__txn_complete(txn->priv->txn, scratch_pool)); 1884333347Speter 1885333347Speter return err; 1886333347Speter} 1887333347Speter 1888333347Speter/* An #svn_branch__txn_t method. */ 1889333347Speterstatic svn_error_t * 1890333347Spetercompat_branch_txn_abort(svn_branch__txn_t *txn, 1891333347Speter apr_pool_t *scratch_pool) 1892333347Speter{ 1893333347Speter svn_branch__txn_priv_t *eb = txn->priv; 1894333347Speter 1895333347Speter SVN_ERR(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool)); 1896333347Speter 1897333347Speter SVN_ERR(svn_branch__txn_abort(txn->priv->txn, 1898333347Speter scratch_pool)); 1899333347Speter return SVN_NO_ERROR; 1900333347Speter} 1901333347Speter 1902333347Speter/* Baton for wrap_fetch_func. */ 1903333347Spetertypedef struct wrap_fetch_baton_t 1904333347Speter{ 1905333347Speter /* Wrapped fetcher */ 1906333347Speter svn_branch__compat_fetch_func_t fetch_func; 1907333347Speter void *fetch_baton; 1908333347Speter} wrap_fetch_baton_t; 1909333347Speter 1910333347Speter/* The purpose of this fetcher-wrapper is to make it appear that B0 1911333347Speter * was created (as an empty dir) in r0. 1912333347Speter */ 1913333347Speterstatic svn_error_t * 1914333347Speterwrap_fetch_func(svn_node_kind_t *kind, 1915333347Speter apr_hash_t **props, 1916333347Speter svn_stringbuf_t **file_text, 1917333347Speter apr_hash_t **children_names, 1918333347Speter void *baton, 1919333347Speter const char *repos_relpath, 1920333347Speter svn_revnum_t revision, 1921333347Speter apr_pool_t *result_pool, 1922333347Speter apr_pool_t *scratch_pool) 1923333347Speter{ 1924333347Speter wrap_fetch_baton_t *b = baton; 1925333347Speter 1926333347Speter if (revision == 0 && strcmp(repos_relpath, "top0") == 0) 1927333347Speter { 1928333347Speter if (kind) 1929333347Speter *kind = svn_node_dir; 1930333347Speter if (props) 1931333347Speter *props = apr_hash_make(result_pool); 1932333347Speter if (file_text) 1933333347Speter *file_text = NULL; 1934333347Speter if (children_names) 1935333347Speter *children_names = apr_hash_make(result_pool); 1936333347Speter } 1937333347Speter else 1938333347Speter { 1939333347Speter SVN_ERR(b->fetch_func(kind, props, file_text, children_names, 1940333347Speter b->fetch_baton, 1941333347Speter repos_relpath, revision, 1942333347Speter result_pool, scratch_pool)); 1943333347Speter } 1944333347Speter 1945333347Speter return SVN_NO_ERROR; 1946333347Speter} 1947333347Speter 1948333347Spetersvn_error_t * 1949333347Spetersvn_branch__compat_txn_from_delta_for_commit( 1950333347Speter svn_branch__txn_t **txn_p, 1951333347Speter svn_branch__compat_shim_connector_t **shim_connector, 1952333347Speter const svn_delta_editor_t *deditor, 1953333347Speter void *dedit_baton, 1954333347Speter svn_branch__txn_t *branching_txn, 1955333347Speter const char *repos_root_url, 1956333347Speter svn_branch__compat_fetch_func_t fetch_func, 1957333347Speter void *fetch_baton, 1958333347Speter svn_cancel_func_t cancel_func, 1959333347Speter void *cancel_baton, 1960333347Speter apr_pool_t *result_pool, 1961333347Speter apr_pool_t *scratch_pool) 1962333347Speter{ 1963333347Speter static const svn_branch__txn_vtable_t vtable = { 1964333347Speter {0}, 1965333347Speter compat_branch_txn_get_branches, 1966333347Speter compat_branch_txn_delete_branch, 1967333347Speter compat_branch_txn_get_num_new_eids, 1968333347Speter compat_branch_txn_new_eid, 1969333347Speter compat_branch_txn_open_branch, 1970333347Speter compat_branch_txn_finalize_eids, 1971333347Speter compat_branch_txn_serialize, 1972333347Speter compat_branch_txn_sequence_point, 1973333347Speter compat_branch_txn_complete, 1974333347Speter compat_branch_txn_abort 1975333347Speter }; 1976333347Speter svn_branch__txn_t *txn; 1977333347Speter svn_branch__txn_priv_t *eb = apr_pcalloc(result_pool, sizeof(*eb)); 1978333347Speter wrap_fetch_baton_t *wb = apr_pcalloc(result_pool, sizeof(*wb)); 1979333347Speter 1980333347Speter eb->deditor = deditor; 1981333347Speter eb->dedit_baton = dedit_baton; 1982333347Speter 1983333347Speter eb->repos_root_url = apr_pstrdup(result_pool, repos_root_url); 1984333347Speter 1985333347Speter eb->changes = apr_hash_make(result_pool); 1986333347Speter 1987333347Speter wb->fetch_func = fetch_func; 1988333347Speter wb->fetch_baton = fetch_baton; 1989333347Speter eb->fetch_func = wrap_fetch_func; 1990333347Speter eb->fetch_baton = wb; 1991333347Speter 1992333347Speter eb->edit_pool = result_pool; 1993333347Speter 1994333347Speter branching_txn = svn_branch__nested_txn_create(branching_txn, result_pool); 1995333347Speter 1996333347Speter eb->txn = branching_txn; 1997333347Speter 1998333347Speter txn = svn_branch__txn_create(&vtable, NULL, NULL, result_pool); 1999333347Speter txn->priv = eb; 2000333347Speter txn->repos = branching_txn->repos; 2001333347Speter txn->rev = branching_txn->rev; 2002333347Speter txn->base_rev = branching_txn->base_rev; 2003333347Speter *txn_p = txn; 2004333347Speter 2005333347Speter if (shim_connector) 2006333347Speter { 2007333347Speter#if 0 2008333347Speter *shim_connector = apr_palloc(result_pool, sizeof(**shim_connector)); 2009333347Speter#ifdef SHIM_WITH_ABS_PATHS 2010333347Speter (*shim_connector)->ev1_absolute_paths 2011333347Speter = apr_palloc(result_pool, sizeof(svn_boolean_t)); 2012333347Speter eb->make_abs_paths = (*shim_connector)->ev1_absolute_paths; 2013333347Speter#endif 2014333347Speter (*shim_connector)->target_revision_func = set_target_revision_ev3; 2015333347Speter (*shim_connector)->start_edit_func = open_root_ev3; 2016333347Speter#ifdef SHIM_WITH_UNLOCK 2017333347Speter (*shim_connector)->unlock_func = do_unlock; 2018333347Speter#endif 2019333347Speter (*shim_connector)->baton = eb; 2020333347Speter#endif 2021333347Speter } 2022333347Speter 2023333347Speter return SVN_NO_ERROR; 2024333347Speter} 2025333347Speter 2026333347Spetersvn_error_t * 2027333347Spetersvn_branch__compat_txn_from_delta_for_update( 2028333347Speter svn_branch__compat_update_editor3_t **update_editor_p, 2029333347Speter const svn_delta_editor_t *deditor, 2030333347Speter void *dedit_baton, 2031333347Speter svn_branch__txn_t *branching_txn, 2032333347Speter const char *repos_root_url, 2033333347Speter const char *base_repos_relpath, 2034333347Speter svn_branch__compat_fetch_func_t fetch_func, 2035333347Speter void *fetch_baton, 2036333347Speter svn_cancel_func_t cancel_func, 2037333347Speter void *cancel_baton, 2038333347Speter apr_pool_t *result_pool, 2039333347Speter apr_pool_t *scratch_pool) 2040333347Speter{ 2041333347Speter svn_branch__compat_update_editor3_t *update_editor 2042333347Speter = apr_pcalloc(result_pool, sizeof(*update_editor)); 2043333347Speter svn_branch__compat_shim_connector_t *shim_connector; 2044333347Speter 2045333347Speter /*(("svn_delta__ev3_from_delta_for_update(base='%s')...", 2046333347Speter base_repos_relpath));*/ 2047333347Speter 2048333347Speter /*SVN_ERR(svn_delta__get_debug_editor(&deditor, &dedit_baton, 2049333347Speter deditor, dedit_baton, 2050333347Speter "[1>UP] ", result_pool));*/ 2051333347Speter SVN_ERR(svn_branch__compat_txn_from_delta_for_commit( 2052333347Speter &update_editor->edit_txn, 2053333347Speter &shim_connector, 2054333347Speter deditor, dedit_baton, 2055333347Speter branching_txn, repos_root_url, 2056333347Speter fetch_func, fetch_baton, 2057333347Speter cancel_func, cancel_baton, 2058333347Speter result_pool, scratch_pool)); 2059333347Speter 2060333347Speter update_editor->set_target_revision_func = shim_connector->target_revision_func; 2061333347Speter update_editor->set_target_revision_baton = shim_connector->baton; 2062333347Speter /* shim_connector->start_edit_func = open_root_ev3; */ 2063333347Speter#ifdef SHIM_WITH_ABS_PATHS 2064333347Speter update_editor->ev1_absolute_paths /*...*/; 2065333347Speter#endif 2066333347Speter#ifdef SHIM_WITH_UNLOCK 2067333347Speter update_editor->unlock_func = do_unlock; 2068333347Speter#endif 2069333347Speter 2070333347Speter *update_editor_p = update_editor; 2071333347Speter return SVN_NO_ERROR; 2072333347Speter} 2073