1251881Speter/* 2251881Speter * delta.c: an editor driver for expressing differences between two trees 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter#include <apr_hash.h> 26251881Speter 27251881Speter#include "svn_hash.h" 28251881Speter#include "svn_types.h" 29251881Speter#include "svn_delta.h" 30251881Speter#include "svn_fs.h" 31251881Speter#include "svn_checksum.h" 32251881Speter#include "svn_path.h" 33251881Speter#include "svn_repos.h" 34251881Speter#include "svn_pools.h" 35251881Speter#include "svn_props.h" 36251881Speter#include "svn_private_config.h" 37251881Speter#include "repos.h" 38251881Speter 39251881Speter 40251881Speter 41251881Speter/* THINGS TODO: Currently the code herein gives only a slight nod to 42251881Speter fully supporting directory deltas that involve renames, copies, and 43251881Speter such. */ 44251881Speter 45251881Speter 46251881Speter/* Some datatypes and declarations used throughout the file. */ 47251881Speter 48251881Speter 49251881Speter/* Parameters which remain constant throughout a delta traversal. 50251881Speter At the top of the recursion, we initialize one of these structures. 51251881Speter Then we pass it down to every call. This way, functions invoked 52251881Speter deep in the recursion can get access to this traversal's global 53251881Speter parameters, without using global variables. */ 54251881Speterstruct context { 55251881Speter const svn_delta_editor_t *editor; 56251881Speter const char *edit_base_path; 57251881Speter svn_fs_root_t *source_root; 58251881Speter svn_fs_root_t *target_root; 59251881Speter svn_repos_authz_func_t authz_read_func; 60251881Speter void *authz_read_baton; 61251881Speter svn_boolean_t text_deltas; 62251881Speter svn_boolean_t entry_props; 63251881Speter svn_boolean_t ignore_ancestry; 64251881Speter}; 65251881Speter 66251881Speter 67251881Speter/* The type of a function that accepts changes to an object's property 68251881Speter list. OBJECT is the object whose properties are being changed. 69251881Speter NAME is the name of the property to change. VALUE is the new value 70251881Speter for the property, or zero if the property should be deleted. */ 71251881Spetertypedef svn_error_t *proplist_change_fn_t(struct context *c, 72251881Speter void *object, 73251881Speter const char *name, 74251881Speter const svn_string_t *value, 75251881Speter apr_pool_t *pool); 76251881Speter 77251881Speter 78251881Speter 79251881Speter/* Some prototypes for functions used throughout. See each individual 80251881Speter function for information about what it does. */ 81251881Speter 82251881Speter 83251881Speter/* Retrieving the base revision from the path/revision hash. */ 84251881Speterstatic svn_revnum_t get_path_revision(svn_fs_root_t *root, 85251881Speter const char *path, 86251881Speter apr_pool_t *pool); 87251881Speter 88251881Speter 89251881Speter/* proplist_change_fn_t property changing functions. */ 90251881Speterstatic svn_error_t *change_dir_prop(struct context *c, 91251881Speter void *object, 92251881Speter const char *path, 93251881Speter const svn_string_t *value, 94251881Speter apr_pool_t *pool); 95251881Speter 96251881Speterstatic svn_error_t *change_file_prop(struct context *c, 97251881Speter void *object, 98251881Speter const char *path, 99251881Speter const svn_string_t *value, 100251881Speter apr_pool_t *pool); 101251881Speter 102251881Speter 103251881Speter/* Constructing deltas for properties of files and directories. */ 104251881Speterstatic svn_error_t *delta_proplists(struct context *c, 105251881Speter const char *source_path, 106251881Speter const char *target_path, 107251881Speter proplist_change_fn_t *change_fn, 108251881Speter void *object, 109251881Speter apr_pool_t *pool); 110251881Speter 111251881Speter 112251881Speter/* Constructing deltas for file constents. */ 113251881Speterstatic svn_error_t *send_text_delta(struct context *c, 114251881Speter void *file_baton, 115251881Speter const char *base_checksum, 116251881Speter svn_txdelta_stream_t *delta_stream, 117251881Speter apr_pool_t *pool); 118251881Speter 119251881Speterstatic svn_error_t *delta_files(struct context *c, 120251881Speter void *file_baton, 121251881Speter const char *source_path, 122251881Speter const char *target_path, 123251881Speter apr_pool_t *pool); 124251881Speter 125251881Speter 126251881Speter/* Generic directory deltafication routines. */ 127251881Speterstatic svn_error_t *delete(struct context *c, 128251881Speter void *dir_baton, 129251881Speter const char *edit_path, 130251881Speter apr_pool_t *pool); 131251881Speter 132251881Speterstatic svn_error_t *add_file_or_dir(struct context *c, 133251881Speter void *dir_baton, 134251881Speter svn_depth_t depth, 135251881Speter const char *target_path, 136251881Speter const char *edit_path, 137251881Speter svn_node_kind_t tgt_kind, 138251881Speter apr_pool_t *pool); 139251881Speter 140251881Speterstatic svn_error_t *replace_file_or_dir(struct context *c, 141251881Speter void *dir_baton, 142251881Speter svn_depth_t depth, 143251881Speter const char *source_path, 144251881Speter const char *target_path, 145251881Speter const char *edit_path, 146251881Speter svn_node_kind_t tgt_kind, 147251881Speter apr_pool_t *pool); 148251881Speter 149251881Speterstatic svn_error_t *absent_file_or_dir(struct context *c, 150251881Speter void *dir_baton, 151251881Speter const char *edit_path, 152251881Speter svn_node_kind_t tgt_kind, 153251881Speter apr_pool_t *pool); 154251881Speter 155251881Speterstatic svn_error_t *delta_dirs(struct context *c, 156251881Speter void *dir_baton, 157251881Speter svn_depth_t depth, 158251881Speter const char *source_path, 159251881Speter const char *target_path, 160251881Speter const char *edit_path, 161251881Speter apr_pool_t *pool); 162251881Speter 163251881Speter 164251881Speter 165251881Speter#define MAYBE_DEMOTE_DEPTH(depth) \ 166251881Speter (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \ 167251881Speter ? svn_depth_empty \ 168251881Speter : (depth)) 169251881Speter 170251881Speter 171251881Speter/* Return the error 'SVN_ERR_AUTHZ_ROOT_UNREADABLE' if PATH in ROOT is 172251881Speter * unreadable according to AUTHZ_READ_FUNC with AUTHZ_READ_BATON. 173251881Speter * 174251881Speter * PATH should be the implicit root path of an editor drive, that is, 175251881Speter * the path used by editor->open_root(). 176251881Speter */ 177251881Speterstatic svn_error_t * 178251881Speterauthz_root_check(svn_fs_root_t *root, 179251881Speter const char *path, 180251881Speter svn_repos_authz_func_t authz_read_func, 181251881Speter void *authz_read_baton, 182251881Speter apr_pool_t *pool) 183251881Speter{ 184251881Speter svn_boolean_t allowed; 185251881Speter 186251881Speter if (authz_read_func) 187251881Speter { 188251881Speter SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool)); 189251881Speter 190251881Speter if (! allowed) 191251881Speter return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0, 192251881Speter _("Unable to open root of edit")); 193251881Speter } 194251881Speter 195251881Speter return SVN_NO_ERROR; 196251881Speter} 197251881Speter 198251881Speter 199251881Speter/* Public interface to computing directory deltas. */ 200251881Spetersvn_error_t * 201251881Spetersvn_repos_dir_delta2(svn_fs_root_t *src_root, 202251881Speter const char *src_parent_dir, 203251881Speter const char *src_entry, 204251881Speter svn_fs_root_t *tgt_root, 205251881Speter const char *tgt_fullpath, 206251881Speter const svn_delta_editor_t *editor, 207251881Speter void *edit_baton, 208251881Speter svn_repos_authz_func_t authz_read_func, 209251881Speter void *authz_read_baton, 210251881Speter svn_boolean_t text_deltas, 211251881Speter svn_depth_t depth, 212251881Speter svn_boolean_t entry_props, 213251881Speter svn_boolean_t ignore_ancestry, 214251881Speter apr_pool_t *pool) 215251881Speter{ 216251881Speter void *root_baton = NULL; 217251881Speter struct context c; 218251881Speter const char *src_fullpath; 219251881Speter svn_node_kind_t src_kind, tgt_kind; 220251881Speter svn_revnum_t rootrev; 221299742Sdim svn_fs_node_relation_t relation; 222251881Speter const char *authz_root_path; 223251881Speter 224251881Speter /* SRC_PARENT_DIR must be valid. */ 225251881Speter if (src_parent_dir) 226251881Speter src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool); 227251881Speter else 228299742Sdim return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0, 229299742Sdim "Invalid source parent directory '(null)'"); 230251881Speter 231251881Speter /* TGT_FULLPATH must be valid. */ 232251881Speter if (tgt_fullpath) 233251881Speter tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool); 234251881Speter else 235251881Speter return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0, 236251881Speter _("Invalid target path")); 237251881Speter 238251881Speter if (depth == svn_depth_exclude) 239251881Speter return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 240251881Speter _("Delta depth 'exclude' not supported")); 241251881Speter 242251881Speter /* Calculate the fs path implicitly used for editor->open_root, so 243251881Speter we can do an authz check on that path first. */ 244251881Speter if (*src_entry) 245251881Speter authz_root_path = svn_relpath_dirname(tgt_fullpath, pool); 246251881Speter else 247251881Speter authz_root_path = tgt_fullpath; 248251881Speter 249251881Speter /* Construct the full path of the source item. */ 250251881Speter src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool); 251251881Speter 252251881Speter /* Get the node kinds for the source and target paths. */ 253251881Speter SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool)); 254251881Speter SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool)); 255251881Speter 256251881Speter /* If neither of our paths exists, we don't really have anything to do. */ 257251881Speter if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none)) 258251881Speter goto cleanup; 259251881Speter 260251881Speter /* If either the source or the target is a non-directory, we 261251881Speter require that a SRC_ENTRY be supplied. */ 262251881Speter if ((! *src_entry) && ((src_kind != svn_node_dir) 263251881Speter || tgt_kind != svn_node_dir)) 264251881Speter return svn_error_create 265251881Speter (SVN_ERR_FS_PATH_SYNTAX, 0, 266251881Speter _("Invalid editor anchoring; at least one of the " 267251881Speter "input paths is not a directory and there was no source entry")); 268251881Speter 269251881Speter /* Set the global target revision if one can be determined. */ 270251881Speter if (svn_fs_is_revision_root(tgt_root)) 271251881Speter { 272251881Speter SVN_ERR(editor->set_target_revision 273251881Speter (edit_baton, svn_fs_revision_root_revision(tgt_root), pool)); 274251881Speter } 275251881Speter else if (svn_fs_is_txn_root(tgt_root)) 276251881Speter { 277251881Speter SVN_ERR(editor->set_target_revision 278251881Speter (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool)); 279251881Speter } 280251881Speter 281251881Speter /* Setup our pseudo-global structure here. We need these variables 282251881Speter throughout the deltafication process, so pass them around by 283251881Speter reference to all the helper functions. */ 284251881Speter c.editor = editor; 285251881Speter c.source_root = src_root; 286251881Speter c.target_root = tgt_root; 287251881Speter c.authz_read_func = authz_read_func; 288251881Speter c.authz_read_baton = authz_read_baton; 289251881Speter c.text_deltas = text_deltas; 290251881Speter c.entry_props = entry_props; 291251881Speter c.ignore_ancestry = ignore_ancestry; 292251881Speter 293251881Speter /* Get our editor root's revision. */ 294251881Speter rootrev = get_path_revision(src_root, src_parent_dir, pool); 295251881Speter 296251881Speter /* If one or the other of our paths doesn't exist, we have to handle 297251881Speter those cases specially. */ 298251881Speter if (tgt_kind == svn_node_none) 299251881Speter { 300251881Speter /* Caller thinks that target still exists, but it doesn't. 301251881Speter So transform their source path to "nothing" by deleting it. */ 302251881Speter SVN_ERR(authz_root_check(tgt_root, authz_root_path, 303251881Speter authz_read_func, authz_read_baton, pool)); 304251881Speter SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 305251881Speter SVN_ERR(delete(&c, root_baton, src_entry, pool)); 306251881Speter goto cleanup; 307251881Speter } 308251881Speter if (src_kind == svn_node_none) 309251881Speter { 310251881Speter /* The source path no longer exists, but the target does. 311251881Speter So transform "nothing" into "something" by adding. */ 312251881Speter SVN_ERR(authz_root_check(tgt_root, authz_root_path, 313251881Speter authz_read_func, authz_read_baton, pool)); 314251881Speter SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 315251881Speter SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath, 316251881Speter src_entry, tgt_kind, pool)); 317251881Speter goto cleanup; 318251881Speter } 319251881Speter 320251881Speter /* Get and compare the node IDs for the source and target. */ 321299742Sdim SVN_ERR(svn_fs_node_relation(&relation, tgt_root, tgt_fullpath, 322299742Sdim src_root, src_fullpath, pool)); 323251881Speter 324299742Sdim if (relation == svn_fs_node_unchanged) 325251881Speter { 326251881Speter /* They are the same node! No-op (you gotta love those). */ 327251881Speter goto cleanup; 328251881Speter } 329251881Speter else if (*src_entry) 330251881Speter { 331251881Speter /* If the nodes have different kinds, we must delete the one and 332251881Speter add the other. Also, if they are completely unrelated and 333251881Speter our caller is interested in relatedness, we do the same thing. */ 334251881Speter if ((src_kind != tgt_kind) 335299742Sdim || ((relation == svn_fs_node_unrelated) && (! ignore_ancestry))) 336251881Speter { 337251881Speter SVN_ERR(authz_root_check(tgt_root, authz_root_path, 338251881Speter authz_read_func, authz_read_baton, pool)); 339251881Speter SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 340251881Speter SVN_ERR(delete(&c, root_baton, src_entry, pool)); 341251881Speter SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath, 342251881Speter src_entry, tgt_kind, pool)); 343251881Speter } 344251881Speter /* Otherwise, we just replace the one with the other. */ 345251881Speter else 346251881Speter { 347251881Speter SVN_ERR(authz_root_check(tgt_root, authz_root_path, 348251881Speter authz_read_func, authz_read_baton, pool)); 349251881Speter SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 350251881Speter SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath, 351251881Speter tgt_fullpath, src_entry, 352251881Speter tgt_kind, pool)); 353251881Speter } 354251881Speter } 355251881Speter else 356251881Speter { 357251881Speter /* There is no entry given, so delta the whole parent directory. */ 358251881Speter SVN_ERR(authz_root_check(tgt_root, authz_root_path, 359251881Speter authz_read_func, authz_read_baton, pool)); 360251881Speter SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 361251881Speter SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath, 362251881Speter tgt_fullpath, "", pool)); 363251881Speter } 364251881Speter 365251881Speter cleanup: 366251881Speter 367251881Speter /* Make sure we close the root directory if we opened one above. */ 368251881Speter if (root_baton) 369251881Speter SVN_ERR(editor->close_directory(root_baton, pool)); 370251881Speter 371251881Speter /* Close the edit. */ 372251881Speter return editor->close_edit(edit_baton, pool); 373251881Speter} 374251881Speter 375251881Speter 376251881Speter/* Retrieving the base revision from the path/revision hash. */ 377251881Speter 378251881Speter 379251881Speterstatic svn_revnum_t 380251881Speterget_path_revision(svn_fs_root_t *root, 381251881Speter const char *path, 382251881Speter apr_pool_t *pool) 383251881Speter{ 384251881Speter svn_revnum_t revision; 385251881Speter svn_error_t *err; 386251881Speter 387251881Speter /* Easy out -- if ROOT is a revision root, we can use the revision 388251881Speter that it's a root of. */ 389251881Speter if (svn_fs_is_revision_root(root)) 390251881Speter return svn_fs_revision_root_revision(root); 391251881Speter 392251881Speter /* Else, this must be a transaction root, so ask the filesystem in 393251881Speter what revision this path was created. */ 394251881Speter if ((err = svn_fs_node_created_rev(&revision, root, path, pool))) 395251881Speter { 396251881Speter revision = SVN_INVALID_REVNUM; 397251881Speter svn_error_clear(err); 398251881Speter } 399251881Speter 400251881Speter /* If we don't get back a valid revision, this path is mutable in 401251881Speter the transaction. We should probably examine the node on which it 402251881Speter is based, doable by querying for the node-id of the path, and 403251881Speter then examining that node-id's predecessor. ### This predecessor 404251881Speter determination isn't exposed via the FS public API right now, so 405251881Speter for now, we'll just return the SVN_INVALID_REVNUM. */ 406251881Speter return revision; 407251881Speter} 408251881Speter 409251881Speter 410251881Speter/* proplist_change_fn_t property changing functions. */ 411251881Speter 412251881Speter 413251881Speter/* Call the directory property-setting function of C->editor to set 414251881Speter the property NAME to given VALUE on the OBJECT passed to this 415251881Speter function. */ 416251881Speterstatic svn_error_t * 417251881Speterchange_dir_prop(struct context *c, 418251881Speter void *object, 419251881Speter const char *name, 420251881Speter const svn_string_t *value, 421251881Speter apr_pool_t *pool) 422251881Speter{ 423251881Speter return c->editor->change_dir_prop(object, name, value, pool); 424251881Speter} 425251881Speter 426251881Speter 427251881Speter/* Call the file property-setting function of C->editor to set the 428251881Speter property NAME to given VALUE on the OBJECT passed to this 429251881Speter function. */ 430251881Speterstatic svn_error_t * 431251881Speterchange_file_prop(struct context *c, 432251881Speter void *object, 433251881Speter const char *name, 434251881Speter const svn_string_t *value, 435251881Speter apr_pool_t *pool) 436251881Speter{ 437251881Speter return c->editor->change_file_prop(object, name, value, pool); 438251881Speter} 439251881Speter 440251881Speter 441251881Speter 442251881Speter 443251881Speter/* Constructing deltas for properties of files and directories. */ 444251881Speter 445251881Speter 446251881Speter/* Generate the appropriate property editing calls to turn the 447251881Speter properties of SOURCE_PATH into those of TARGET_PATH. If 448251881Speter SOURCE_PATH is NULL, this is an add, so assume the target starts 449251881Speter with no properties. Pass OBJECT on to the editor function wrapper 450251881Speter CHANGE_FN. */ 451251881Speterstatic svn_error_t * 452251881Speterdelta_proplists(struct context *c, 453251881Speter const char *source_path, 454251881Speter const char *target_path, 455251881Speter proplist_change_fn_t *change_fn, 456251881Speter void *object, 457251881Speter apr_pool_t *pool) 458251881Speter{ 459251881Speter apr_hash_t *s_props = 0; 460251881Speter apr_hash_t *t_props = 0; 461251881Speter apr_pool_t *subpool; 462251881Speter apr_array_header_t *prop_diffs; 463251881Speter int i; 464251881Speter 465251881Speter SVN_ERR_ASSERT(target_path); 466251881Speter 467251881Speter /* Make a subpool for local allocations. */ 468251881Speter subpool = svn_pool_create(pool); 469251881Speter 470251881Speter /* If we're supposed to send entry props for all non-deleted items, 471251881Speter here we go! */ 472251881Speter if (c->entry_props) 473251881Speter { 474251881Speter svn_revnum_t committed_rev = SVN_INVALID_REVNUM; 475251881Speter svn_string_t *cr_str = NULL; 476251881Speter svn_string_t *committed_date = NULL; 477251881Speter svn_string_t *last_author = NULL; 478251881Speter 479251881Speter /* Get the CR and two derivative props. ### check for error returns. */ 480251881Speter SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root, 481251881Speter target_path, subpool)); 482251881Speter if (SVN_IS_VALID_REVNUM(committed_rev)) 483251881Speter { 484251881Speter svn_fs_t *fs = svn_fs_root_fs(c->target_root); 485251881Speter apr_hash_t *r_props; 486251881Speter const char *uuid; 487251881Speter 488251881Speter /* Transmit the committed-rev. */ 489251881Speter cr_str = svn_string_createf(subpool, "%ld", 490251881Speter committed_rev); 491251881Speter SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV, 492251881Speter cr_str, subpool)); 493251881Speter 494251881Speter SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev, 495251881Speter pool)); 496251881Speter 497251881Speter /* Transmit the committed-date. */ 498251881Speter committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE); 499251881Speter if (committed_date || source_path) 500251881Speter { 501251881Speter SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE, 502251881Speter committed_date, subpool)); 503251881Speter } 504251881Speter 505251881Speter /* Transmit the last-author. */ 506251881Speter last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR); 507251881Speter if (last_author || source_path) 508251881Speter { 509251881Speter SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR, 510251881Speter last_author, subpool)); 511251881Speter } 512251881Speter 513251881Speter /* Transmit the UUID. */ 514251881Speter SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool)); 515251881Speter SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID, 516251881Speter svn_string_create(uuid, subpool), 517251881Speter subpool)); 518251881Speter } 519251881Speter } 520251881Speter 521251881Speter if (source_path) 522251881Speter { 523251881Speter svn_boolean_t changed; 524251881Speter 525251881Speter /* Is this deltification worth our time? */ 526299742Sdim SVN_ERR(svn_fs_props_different(&changed, c->target_root, target_path, 527299742Sdim c->source_root, source_path, subpool)); 528251881Speter if (! changed) 529251881Speter goto cleanup; 530251881Speter 531251881Speter /* If so, go ahead and get the source path's properties. */ 532251881Speter SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root, 533251881Speter source_path, subpool)); 534251881Speter } 535251881Speter else 536251881Speter { 537251881Speter s_props = apr_hash_make(subpool); 538251881Speter } 539251881Speter 540251881Speter /* Get the target path's properties */ 541251881Speter SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root, 542251881Speter target_path, subpool)); 543251881Speter 544251881Speter /* Now transmit the differences. */ 545251881Speter SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool)); 546251881Speter for (i = 0; i < prop_diffs->nelts; i++) 547251881Speter { 548251881Speter const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); 549251881Speter SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool)); 550251881Speter } 551251881Speter 552251881Speter cleanup: 553251881Speter /* Destroy local subpool. */ 554251881Speter svn_pool_destroy(subpool); 555251881Speter 556251881Speter return SVN_NO_ERROR; 557251881Speter} 558251881Speter 559251881Speter 560251881Speter 561251881Speter 562251881Speter/* Constructing deltas for file contents. */ 563251881Speter 564251881Speter 565251881Speter/* Change the contents of FILE_BATON in C->editor, according to the 566251881Speter text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to 567251881Speter C->editor->apply_textdelta. */ 568251881Speterstatic svn_error_t * 569251881Spetersend_text_delta(struct context *c, 570251881Speter void *file_baton, 571251881Speter const char *base_checksum, 572251881Speter svn_txdelta_stream_t *delta_stream, 573251881Speter apr_pool_t *pool) 574251881Speter{ 575251881Speter svn_txdelta_window_handler_t delta_handler; 576251881Speter void *delta_handler_baton; 577251881Speter 578251881Speter /* Get a handler that will apply the delta to the file. */ 579251881Speter SVN_ERR(c->editor->apply_textdelta 580251881Speter (file_baton, base_checksum, pool, 581251881Speter &delta_handler, &delta_handler_baton)); 582251881Speter 583251881Speter if (c->text_deltas && delta_stream) 584251881Speter { 585251881Speter /* Deliver the delta stream to the file. */ 586251881Speter return svn_txdelta_send_txstream(delta_stream, 587251881Speter delta_handler, 588251881Speter delta_handler_baton, 589251881Speter pool); 590251881Speter } 591251881Speter else 592251881Speter { 593251881Speter /* The caller doesn't want text delta data. Just send a single 594251881Speter NULL window. */ 595251881Speter return delta_handler(NULL, delta_handler_baton); 596251881Speter } 597251881Speter} 598251881Speter 599251881Spetersvn_error_t * 600251881Spetersvn_repos__compare_files(svn_boolean_t *changed_p, 601251881Speter svn_fs_root_t *root1, 602251881Speter const char *path1, 603251881Speter svn_fs_root_t *root2, 604251881Speter const char *path2, 605251881Speter apr_pool_t *pool) 606251881Speter{ 607299742Sdim return svn_error_trace(svn_fs_contents_different(changed_p, root1, path1, 608299742Sdim root2, path2, pool)); 609251881Speter} 610251881Speter 611251881Speter 612251881Speter/* Make the appropriate edits on FILE_BATON to change its contents and 613251881Speter properties from those in SOURCE_PATH to those in TARGET_PATH. */ 614251881Speterstatic svn_error_t * 615251881Speterdelta_files(struct context *c, 616251881Speter void *file_baton, 617251881Speter const char *source_path, 618251881Speter const char *target_path, 619251881Speter apr_pool_t *pool) 620251881Speter{ 621251881Speter apr_pool_t *subpool; 622251881Speter svn_boolean_t changed = TRUE; 623251881Speter 624251881Speter SVN_ERR_ASSERT(target_path); 625251881Speter 626251881Speter /* Make a subpool for local allocations. */ 627251881Speter subpool = svn_pool_create(pool); 628251881Speter 629251881Speter /* Compare the files' property lists. */ 630251881Speter SVN_ERR(delta_proplists(c, source_path, target_path, 631251881Speter change_file_prop, file_baton, subpool)); 632251881Speter 633251881Speter if (source_path) 634251881Speter { 635299742Sdim SVN_ERR(svn_fs_contents_different(&changed, 636251881Speter c->target_root, target_path, 637251881Speter c->source_root, source_path, 638251881Speter subpool)); 639251881Speter } 640251881Speter else 641251881Speter { 642251881Speter /* If there isn't a source path, this is an add, which 643251881Speter necessarily has textual mods. */ 644251881Speter } 645251881Speter 646251881Speter /* If there is a change, and the context indicates that we should 647251881Speter care about it, then hand it off to a delta stream. */ 648251881Speter if (changed) 649251881Speter { 650251881Speter svn_txdelta_stream_t *delta_stream = NULL; 651251881Speter svn_checksum_t *source_checksum; 652251881Speter const char *source_hex_digest = NULL; 653251881Speter 654251881Speter if (c->text_deltas) 655251881Speter { 656251881Speter /* Get a delta stream turning an empty file into one having 657251881Speter TARGET_PATH's contents. */ 658251881Speter SVN_ERR(svn_fs_get_file_delta_stream 659251881Speter (&delta_stream, 660251881Speter source_path ? c->source_root : NULL, 661251881Speter source_path ? source_path : NULL, 662251881Speter c->target_root, target_path, subpool)); 663251881Speter } 664251881Speter 665251881Speter if (source_path) 666251881Speter { 667251881Speter SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5, 668251881Speter c->source_root, source_path, TRUE, 669251881Speter subpool)); 670251881Speter 671251881Speter source_hex_digest = svn_checksum_to_cstring(source_checksum, 672251881Speter subpool); 673251881Speter } 674251881Speter 675251881Speter SVN_ERR(send_text_delta(c, file_baton, source_hex_digest, 676251881Speter delta_stream, subpool)); 677251881Speter } 678251881Speter 679251881Speter /* Cleanup. */ 680251881Speter svn_pool_destroy(subpool); 681251881Speter 682251881Speter return SVN_NO_ERROR; 683251881Speter} 684251881Speter 685251881Speter 686251881Speter 687251881Speter 688251881Speter/* Generic directory deltafication routines. */ 689251881Speter 690251881Speter 691251881Speter/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */ 692251881Speterstatic svn_error_t * 693251881Speterdelete(struct context *c, 694251881Speter void *dir_baton, 695251881Speter const char *edit_path, 696251881Speter apr_pool_t *pool) 697251881Speter{ 698251881Speter return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM, 699251881Speter dir_baton, pool); 700251881Speter} 701251881Speter 702251881Speter 703251881Speter/* If authorized, emit a delta to create the entry named TARGET_ENTRY 704251881Speter at the location EDIT_PATH. If not authorized, indicate that 705251881Speter EDIT_PATH is absent. Pass DIR_BATON through to editor functions 706251881Speter that require it. DEPTH is the depth from this point downward. */ 707251881Speterstatic svn_error_t * 708251881Speteradd_file_or_dir(struct context *c, void *dir_baton, 709251881Speter svn_depth_t depth, 710251881Speter const char *target_path, 711251881Speter const char *edit_path, 712251881Speter svn_node_kind_t tgt_kind, 713251881Speter apr_pool_t *pool) 714251881Speter{ 715251881Speter struct context *context = c; 716251881Speter svn_boolean_t allowed; 717251881Speter 718251881Speter SVN_ERR_ASSERT(target_path && edit_path); 719251881Speter 720251881Speter if (c->authz_read_func) 721251881Speter { 722251881Speter SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path, 723251881Speter c->authz_read_baton, pool)); 724251881Speter if (!allowed) 725251881Speter return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool); 726251881Speter } 727251881Speter 728251881Speter if (tgt_kind == svn_node_dir) 729251881Speter { 730251881Speter void *subdir_baton; 731251881Speter 732251881Speter SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL, 733251881Speter SVN_INVALID_REVNUM, pool, 734251881Speter &subdir_baton)); 735251881Speter SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth), 736251881Speter NULL, target_path, edit_path, pool)); 737251881Speter return context->editor->close_directory(subdir_baton, pool); 738251881Speter } 739251881Speter else 740251881Speter { 741251881Speter void *file_baton; 742251881Speter svn_checksum_t *checksum; 743251881Speter 744251881Speter SVN_ERR(context->editor->add_file(edit_path, dir_baton, 745251881Speter NULL, SVN_INVALID_REVNUM, pool, 746251881Speter &file_baton)); 747251881Speter SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool)); 748251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 749251881Speter context->target_root, target_path, 750251881Speter TRUE, pool)); 751251881Speter return context->editor->close_file 752251881Speter (file_baton, svn_checksum_to_cstring(checksum, pool), pool); 753251881Speter } 754251881Speter} 755251881Speter 756251881Speter 757251881Speter/* If authorized, emit a delta to modify EDIT_PATH with the changes 758251881Speter from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that 759251881Speter EDIT_PATH is absent. Pass DIR_BATON through to editor functions 760251881Speter that require it. DEPTH is the depth from this point downward. */ 761251881Speterstatic svn_error_t * 762251881Speterreplace_file_or_dir(struct context *c, 763251881Speter void *dir_baton, 764251881Speter svn_depth_t depth, 765251881Speter const char *source_path, 766251881Speter const char *target_path, 767251881Speter const char *edit_path, 768251881Speter svn_node_kind_t tgt_kind, 769251881Speter apr_pool_t *pool) 770251881Speter{ 771251881Speter svn_revnum_t base_revision = SVN_INVALID_REVNUM; 772251881Speter svn_boolean_t allowed; 773251881Speter 774251881Speter SVN_ERR_ASSERT(target_path && source_path && edit_path); 775251881Speter 776251881Speter if (c->authz_read_func) 777251881Speter { 778251881Speter SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path, 779251881Speter c->authz_read_baton, pool)); 780251881Speter if (!allowed) 781251881Speter return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool); 782251881Speter } 783251881Speter 784251881Speter /* Get the base revision for the entry from the hash. */ 785251881Speter base_revision = get_path_revision(c->source_root, source_path, pool); 786251881Speter 787251881Speter if (tgt_kind == svn_node_dir) 788251881Speter { 789251881Speter void *subdir_baton; 790251881Speter 791251881Speter SVN_ERR(c->editor->open_directory(edit_path, dir_baton, 792251881Speter base_revision, pool, 793251881Speter &subdir_baton)); 794251881Speter SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth), 795251881Speter source_path, target_path, edit_path, pool)); 796251881Speter return c->editor->close_directory(subdir_baton, pool); 797251881Speter } 798251881Speter else 799251881Speter { 800251881Speter void *file_baton; 801251881Speter svn_checksum_t *checksum; 802251881Speter 803251881Speter SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision, 804251881Speter pool, &file_baton)); 805251881Speter SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool)); 806251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 807251881Speter c->target_root, target_path, TRUE, 808251881Speter pool)); 809251881Speter return c->editor->close_file 810251881Speter (file_baton, svn_checksum_to_cstring(checksum, pool), pool); 811251881Speter } 812251881Speter} 813251881Speter 814251881Speter 815251881Speter/* In directory DIR_BATON, indicate that EDIT_PATH (relative to the 816251881Speter edit root) is absent by invoking C->editor->absent_directory or 817251881Speter C->editor->absent_file (depending on TGT_KIND). */ 818251881Speterstatic svn_error_t * 819251881Speterabsent_file_or_dir(struct context *c, 820251881Speter void *dir_baton, 821251881Speter const char *edit_path, 822251881Speter svn_node_kind_t tgt_kind, 823251881Speter apr_pool_t *pool) 824251881Speter{ 825251881Speter SVN_ERR_ASSERT(edit_path); 826251881Speter 827251881Speter if (tgt_kind == svn_node_dir) 828251881Speter return c->editor->absent_directory(edit_path, dir_baton, pool); 829251881Speter else 830251881Speter return c->editor->absent_file(edit_path, dir_baton, pool); 831251881Speter} 832251881Speter 833251881Speter 834251881Speter/* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that 835251881Speter DIR_BATON represents the directory we're constructing to the editor 836251881Speter in the context C. */ 837251881Speterstatic svn_error_t * 838251881Speterdelta_dirs(struct context *c, 839251881Speter void *dir_baton, 840251881Speter svn_depth_t depth, 841251881Speter const char *source_path, 842251881Speter const char *target_path, 843251881Speter const char *edit_path, 844251881Speter apr_pool_t *pool) 845251881Speter{ 846251881Speter apr_hash_t *s_entries = 0, *t_entries = 0; 847251881Speter apr_hash_index_t *hi; 848251881Speter apr_pool_t *subpool; 849251881Speter 850251881Speter SVN_ERR_ASSERT(target_path); 851251881Speter 852251881Speter /* Compare the property lists. */ 853251881Speter SVN_ERR(delta_proplists(c, source_path, target_path, 854251881Speter change_dir_prop, dir_baton, pool)); 855251881Speter 856251881Speter /* Get the list of entries in each of source and target. */ 857251881Speter SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root, 858251881Speter target_path, pool)); 859251881Speter if (source_path) 860251881Speter SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root, 861251881Speter source_path, pool)); 862251881Speter 863251881Speter /* Make a subpool for local allocations. */ 864251881Speter subpool = svn_pool_create(pool); 865251881Speter 866251881Speter /* Loop over the hash of entries in the target, searching for its 867251881Speter partner in the source. If we find the matching partner entry, 868251881Speter use editor calls to replace the one in target with a new version 869251881Speter if necessary, then remove that entry from the source entries 870251881Speter hash. If we can't find a related node in the source, we use 871251881Speter editor calls to add the entry as a new item in the target. 872251881Speter Having handled all the entries that exist in target, any entries 873251881Speter still remaining the source entries hash represent entries that no 874251881Speter longer exist in target. Use editor calls to delete those entries 875251881Speter from the target tree. */ 876251881Speter for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi)) 877251881Speter { 878299742Sdim const void *key = apr_hash_this_key(hi); 879299742Sdim apr_ssize_t klen = apr_hash_this_key_len(hi); 880299742Sdim const svn_fs_dirent_t *t_entry = apr_hash_this_val(hi); 881299742Sdim const svn_fs_dirent_t *s_entry; 882251881Speter const char *t_fullpath; 883251881Speter const char *e_fullpath; 884251881Speter const char *s_fullpath; 885251881Speter svn_node_kind_t tgt_kind; 886251881Speter 887251881Speter /* Clear out our subpool for the next iteration... */ 888251881Speter svn_pool_clear(subpool); 889251881Speter 890251881Speter tgt_kind = t_entry->kind; 891251881Speter t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool); 892251881Speter e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool); 893251881Speter 894251881Speter /* Can we find something with the same name in the source 895251881Speter entries hash? */ 896251881Speter if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0)) 897251881Speter { 898251881Speter svn_node_kind_t src_kind; 899251881Speter 900251881Speter s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool); 901251881Speter src_kind = s_entry->kind; 902251881Speter 903251881Speter if (depth == svn_depth_infinity 904251881Speter || src_kind != svn_node_dir 905251881Speter || (src_kind == svn_node_dir 906251881Speter && depth == svn_depth_immediates)) 907251881Speter { 908251881Speter /* Use svn_fs_compare_ids() to compare our current 909251881Speter source and target ids. 910251881Speter 911251881Speter 0: means they are the same id, and this is a noop. 912251881Speter -1: means they are unrelated, so we have to delete the 913251881Speter old one and add the new one. 914251881Speter 1: means the nodes are related through ancestry, so go 915251881Speter ahead and do the replace directly. */ 916251881Speter int distance = svn_fs_compare_ids(s_entry->id, t_entry->id); 917251881Speter if (distance == 0) 918251881Speter { 919251881Speter /* no-op */ 920251881Speter } 921251881Speter else if ((src_kind != tgt_kind) 922251881Speter || ((distance == -1) && (! c->ignore_ancestry))) 923251881Speter { 924251881Speter SVN_ERR(delete(c, dir_baton, e_fullpath, subpool)); 925251881Speter SVN_ERR(add_file_or_dir(c, dir_baton, 926251881Speter MAYBE_DEMOTE_DEPTH(depth), 927251881Speter t_fullpath, e_fullpath, tgt_kind, 928251881Speter subpool)); 929251881Speter } 930251881Speter else 931251881Speter { 932251881Speter SVN_ERR(replace_file_or_dir(c, dir_baton, 933251881Speter MAYBE_DEMOTE_DEPTH(depth), 934251881Speter s_fullpath, t_fullpath, 935251881Speter e_fullpath, tgt_kind, 936251881Speter subpool)); 937251881Speter } 938251881Speter } 939251881Speter 940251881Speter /* Remove the entry from the source_hash. */ 941251881Speter svn_hash_sets(s_entries, key, NULL); 942251881Speter } 943251881Speter else 944251881Speter { 945251881Speter if (depth == svn_depth_infinity 946251881Speter || tgt_kind != svn_node_dir 947251881Speter || (tgt_kind == svn_node_dir 948251881Speter && depth == svn_depth_immediates)) 949251881Speter { 950251881Speter SVN_ERR(add_file_or_dir(c, dir_baton, 951251881Speter MAYBE_DEMOTE_DEPTH(depth), 952251881Speter t_fullpath, e_fullpath, tgt_kind, 953251881Speter subpool)); 954251881Speter } 955251881Speter } 956251881Speter } 957251881Speter 958251881Speter /* All that is left in the source entries hash are things that need 959251881Speter to be deleted. Delete them. */ 960251881Speter if (s_entries) 961251881Speter { 962251881Speter for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi)) 963251881Speter { 964299742Sdim const svn_fs_dirent_t *s_entry = apr_hash_this_val(hi); 965251881Speter const char *e_fullpath; 966251881Speter svn_node_kind_t src_kind; 967251881Speter 968251881Speter /* Clear out our subpool for the next iteration... */ 969251881Speter svn_pool_clear(subpool); 970251881Speter 971251881Speter src_kind = s_entry->kind; 972251881Speter e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool); 973251881Speter 974251881Speter /* Do we actually want to delete the dir if we're non-recursive? */ 975251881Speter if (depth == svn_depth_infinity 976251881Speter || src_kind != svn_node_dir 977251881Speter || (src_kind == svn_node_dir 978251881Speter && depth == svn_depth_immediates)) 979251881Speter { 980251881Speter SVN_ERR(delete(c, dir_baton, e_fullpath, subpool)); 981251881Speter } 982251881Speter } 983251881Speter } 984251881Speter 985251881Speter /* Destroy local allocation subpool. */ 986251881Speter svn_pool_destroy(subpool); 987251881Speter 988251881Speter return SVN_NO_ERROR; 989251881Speter} 990