1251881Speter/* 2251881Speter * reporter.c : `reporter' vtable routines for updates. 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#include "svn_dirent_uri.h" 25251881Speter#include "svn_hash.h" 26251881Speter#include "svn_path.h" 27251881Speter#include "svn_types.h" 28251881Speter#include "svn_error.h" 29251881Speter#include "svn_error_codes.h" 30251881Speter#include "svn_fs.h" 31251881Speter#include "svn_repos.h" 32251881Speter#include "svn_pools.h" 33251881Speter#include "svn_props.h" 34251881Speter#include "repos.h" 35251881Speter#include "svn_private_config.h" 36251881Speter 37251881Speter#include "private/svn_dep_compat.h" 38251881Speter#include "private/svn_fspath.h" 39251881Speter#include "private/svn_subr_private.h" 40251881Speter#include "private/svn_string_private.h" 41251881Speter 42251881Speter#define NUM_CACHED_SOURCE_ROOTS 4 43251881Speter 44251881Speter/* Theory of operation: we write report operations out to a spill-buffer 45251881Speter as we receive them. When the report is finished, we read the 46251881Speter operations back out again, using them to guide the progression of 47251881Speter the delta between the source and target revs. 48251881Speter 49251881Speter Spill-buffer content format: we use a simple ad-hoc format to store the 50251881Speter report operations. Each report operation is the concatention of 51251881Speter the following ("+/-" indicates the single character '+' or '-'; 52251881Speter <length> and <revnum> are written out as decimal strings): 53251881Speter 54251881Speter +/- '-' marks the end of the report 55251881Speter If previous is +: 56251881Speter <length>:<bytes> Length-counted path string 57251881Speter +/- '+' indicates the presence of link_path 58251881Speter If previous is +: 59251881Speter <length>:<bytes> Length-counted link_path string 60251881Speter +/- '+' indicates presence of revnum 61251881Speter If previous is +: 62251881Speter <revnum>: Revnum of set_path or link_path 63251881Speter +/- '+' indicates depth other than svn_depth_infinity 64251881Speter If previous is +: 65251881Speter <depth>: "X","E","F","M" => 66251881Speter svn_depth_{exclude,empty,files,immediates} 67251881Speter +/- '+' indicates start_empty field set 68251881Speter +/- '+' indicates presence of lock_token field. 69251881Speter If previous is +: 70251881Speter <length>:<bytes> Length-counted lock_token string 71251881Speter 72251881Speter Terminology: for brevity, this file frequently uses the prefixes 73251881Speter "s_" for source, "t_" for target, and "e_" for editor. Also, to 74251881Speter avoid overloading the word "target", we talk about the source 75251881Speter "anchor and operand", rather than the usual "anchor and target". */ 76251881Speter 77251881Speter/* Describes the state of a working copy subtree, as given by a 78251881Speter report. Because we keep a lookahead pathinfo, we need to allocate 79251881Speter each one of these things in a subpool of the report baton and free 80251881Speter it when done. */ 81251881Spetertypedef struct path_info_t 82251881Speter{ 83251881Speter const char *path; /* path, munged to be anchor-relative */ 84251881Speter const char *link_path; /* NULL for set_path or delete_path */ 85251881Speter svn_revnum_t rev; /* SVN_INVALID_REVNUM for delete_path */ 86251881Speter svn_depth_t depth; /* Depth of this path, meaningless for files */ 87251881Speter svn_boolean_t start_empty; /* Meaningless for delete_path */ 88251881Speter const char *lock_token; /* NULL if no token */ 89251881Speter apr_pool_t *pool; /* Container pool */ 90251881Speter} path_info_t; 91251881Speter 92251881Speter/* Describes the standard revision properties that are relevant for 93251881Speter reports. Since a particular revision will often show up more than 94251881Speter once in the report, we cache these properties for the time of the 95251881Speter report generation. */ 96251881Spetertypedef struct revision_info_t 97251881Speter{ 98251881Speter svn_revnum_t rev; /* revision number */ 99251881Speter svn_string_t* date; /* revision timestamp */ 100251881Speter svn_string_t* author; /* name of the revisions' author */ 101251881Speter} revision_info_t; 102251881Speter 103251881Speter/* A structure used by the routines within the `reporter' vtable, 104251881Speter driven by the client as it describes its working copy revisions. */ 105251881Spetertypedef struct report_baton_t 106251881Speter{ 107251881Speter /* Parameters remembered from svn_repos_begin_report3 */ 108251881Speter svn_repos_t *repos; 109251881Speter const char *fs_base; /* fspath corresponding to wc anchor */ 110251881Speter const char *s_operand; /* anchor-relative wc target (may be empty) */ 111251881Speter svn_revnum_t t_rev; /* Revnum which the edit will bring the wc to */ 112251881Speter const char *t_path; /* FS path the edit will bring the wc to */ 113251881Speter svn_boolean_t text_deltas; /* Whether to report text deltas */ 114251881Speter apr_size_t zero_copy_limit; /* Max item size that will be sent using 115251881Speter the zero-copy code path. */ 116251881Speter 117251881Speter /* If the client requested a specific depth, record it here; if the 118251881Speter client did not, then this is svn_depth_unknown, and the depth of 119251881Speter information transmitted from server to client will be governed 120251881Speter strictly by the path-associated depths recorded in the report. */ 121251881Speter svn_depth_t requested_depth; 122251881Speter 123251881Speter svn_boolean_t ignore_ancestry; 124251881Speter svn_boolean_t send_copyfrom_args; 125251881Speter svn_boolean_t is_switch; 126251881Speter const svn_delta_editor_t *editor; 127251881Speter void *edit_baton; 128251881Speter svn_repos_authz_func_t authz_read_func; 129251881Speter void *authz_read_baton; 130251881Speter 131251881Speter /* The spill-buffer holding the report. */ 132251881Speter svn_spillbuf_reader_t *reader; 133251881Speter 134251881Speter /* For the actual editor drive, we'll need a lookahead path info 135251881Speter entry, a cache of FS roots, and a pool to store them. */ 136251881Speter path_info_t *lookahead; 137251881Speter svn_fs_root_t *t_root; 138251881Speter svn_fs_root_t *s_roots[NUM_CACHED_SOURCE_ROOTS]; 139251881Speter 140251881Speter /* Cache for revision properties. This is used to eliminate redundant 141251881Speter revprop fetching. */ 142251881Speter apr_hash_t *revision_infos; 143251881Speter 144251881Speter /* This will not change. So, fetch it once and reuse it. */ 145251881Speter svn_string_t *repos_uuid; 146251881Speter apr_pool_t *pool; 147251881Speter} report_baton_t; 148251881Speter 149251881Speter/* The type of a function that accepts changes to an object's property 150251881Speter list. OBJECT is the object whose properties are being changed. 151251881Speter NAME is the name of the property to change. VALUE is the new value 152251881Speter for the property, or zero if the property should be deleted. */ 153251881Spetertypedef svn_error_t *proplist_change_fn_t(report_baton_t *b, void *object, 154251881Speter const char *name, 155251881Speter const svn_string_t *value, 156251881Speter apr_pool_t *pool); 157251881Speter 158251881Speterstatic svn_error_t *delta_dirs(report_baton_t *b, svn_revnum_t s_rev, 159251881Speter const char *s_path, const char *t_path, 160251881Speter void *dir_baton, const char *e_path, 161251881Speter svn_boolean_t start_empty, 162251881Speter svn_depth_t wc_depth, 163251881Speter svn_depth_t requested_depth, 164251881Speter apr_pool_t *pool); 165251881Speter 166251881Speter/* --- READING PREVIOUSLY STORED REPORT INFORMATION --- */ 167251881Speter 168251881Speterstatic svn_error_t * 169251881Speterread_number(apr_uint64_t *num, svn_spillbuf_reader_t *reader, apr_pool_t *pool) 170251881Speter{ 171251881Speter char c; 172251881Speter 173251881Speter *num = 0; 174251881Speter while (1) 175251881Speter { 176251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 177251881Speter if (c == ':') 178251881Speter break; 179251881Speter *num = *num * 10 + (c - '0'); 180251881Speter } 181251881Speter return SVN_NO_ERROR; 182251881Speter} 183251881Speter 184251881Speterstatic svn_error_t * 185251881Speterread_string(const char **str, svn_spillbuf_reader_t *reader, apr_pool_t *pool) 186251881Speter{ 187251881Speter apr_uint64_t len; 188251881Speter apr_size_t size; 189251881Speter apr_size_t amt; 190251881Speter char *buf; 191251881Speter 192251881Speter SVN_ERR(read_number(&len, reader, pool)); 193251881Speter 194251881Speter /* Len can never be less than zero. But could len be so large that 195251881Speter len + 1 wraps around and we end up passing 0 to apr_palloc(), 196251881Speter thus getting a pointer to no storage? Probably not (16 exabyte 197251881Speter string, anyone?) but let's be future-proof anyway. */ 198251881Speter if (len + 1 < len || len + 1 > APR_SIZE_MAX) 199251881Speter { 200251881Speter /* xgettext doesn't expand preprocessor definitions, so we must 201251881Speter pass translatable string to apr_psprintf() function to create 202251881Speter intermediate string with appropriate format specifier. */ 203251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, 204251881Speter apr_psprintf(pool, 205251881Speter _("Invalid length (%%%s) when " 206251881Speter "about to read a string"), 207251881Speter APR_UINT64_T_FMT), 208251881Speter len); 209251881Speter } 210251881Speter 211251881Speter size = (apr_size_t)len; 212251881Speter buf = apr_palloc(pool, size+1); 213251881Speter if (size > 0) 214251881Speter { 215251881Speter SVN_ERR(svn_spillbuf__reader_read(&amt, reader, buf, size, pool)); 216251881Speter SVN_ERR_ASSERT(amt == size); 217251881Speter } 218251881Speter buf[len] = 0; 219251881Speter *str = buf; 220251881Speter return SVN_NO_ERROR; 221251881Speter} 222251881Speter 223251881Speterstatic svn_error_t * 224251881Speterread_rev(svn_revnum_t *rev, svn_spillbuf_reader_t *reader, apr_pool_t *pool) 225251881Speter{ 226251881Speter char c; 227251881Speter apr_uint64_t num; 228251881Speter 229251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 230251881Speter if (c == '+') 231251881Speter { 232251881Speter SVN_ERR(read_number(&num, reader, pool)); 233251881Speter *rev = (svn_revnum_t) num; 234251881Speter } 235251881Speter else 236251881Speter *rev = SVN_INVALID_REVNUM; 237251881Speter return SVN_NO_ERROR; 238251881Speter} 239251881Speter 240251881Speter/* Read a single character to set *DEPTH (having already read '+') 241251881Speter from READER. PATH is the path to which the depth applies, and is 242251881Speter used for error reporting only. */ 243251881Speterstatic svn_error_t * 244251881Speterread_depth(svn_depth_t *depth, svn_spillbuf_reader_t *reader, const char *path, 245251881Speter apr_pool_t *pool) 246251881Speter{ 247251881Speter char c; 248251881Speter 249251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 250251881Speter switch (c) 251251881Speter { 252251881Speter case 'X': 253251881Speter *depth = svn_depth_exclude; 254251881Speter break; 255251881Speter case 'E': 256251881Speter *depth = svn_depth_empty; 257251881Speter break; 258251881Speter case 'F': 259251881Speter *depth = svn_depth_files; 260251881Speter break; 261251881Speter case 'M': 262251881Speter *depth = svn_depth_immediates; 263251881Speter break; 264251881Speter 265251881Speter /* Note that we do not tolerate explicit representation of 266251881Speter svn_depth_infinity here, because that's not how 267251881Speter write_path_info() writes it. */ 268251881Speter default: 269251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, 270251881Speter _("Invalid depth (%c) for path '%s'"), c, path); 271251881Speter } 272251881Speter 273251881Speter return SVN_NO_ERROR; 274251881Speter} 275251881Speter 276251881Speter/* Read a report operation *PI out of READER. Set *PI to NULL if we 277251881Speter have reached the end of the report. */ 278251881Speterstatic svn_error_t * 279251881Speterread_path_info(path_info_t **pi, 280251881Speter svn_spillbuf_reader_t *reader, 281251881Speter apr_pool_t *pool) 282251881Speter{ 283251881Speter char c; 284251881Speter 285251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 286251881Speter if (c == '-') 287251881Speter { 288251881Speter *pi = NULL; 289251881Speter return SVN_NO_ERROR; 290251881Speter } 291251881Speter 292251881Speter *pi = apr_palloc(pool, sizeof(**pi)); 293251881Speter SVN_ERR(read_string(&(*pi)->path, reader, pool)); 294251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 295251881Speter if (c == '+') 296251881Speter SVN_ERR(read_string(&(*pi)->link_path, reader, pool)); 297251881Speter else 298251881Speter (*pi)->link_path = NULL; 299251881Speter SVN_ERR(read_rev(&(*pi)->rev, reader, pool)); 300251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 301251881Speter if (c == '+') 302251881Speter SVN_ERR(read_depth(&((*pi)->depth), reader, (*pi)->path, pool)); 303251881Speter else 304251881Speter (*pi)->depth = svn_depth_infinity; 305251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 306251881Speter (*pi)->start_empty = (c == '+'); 307251881Speter SVN_ERR(svn_spillbuf__reader_getc(&c, reader, pool)); 308251881Speter if (c == '+') 309251881Speter SVN_ERR(read_string(&(*pi)->lock_token, reader, pool)); 310251881Speter else 311251881Speter (*pi)->lock_token = NULL; 312251881Speter (*pi)->pool = pool; 313251881Speter return SVN_NO_ERROR; 314251881Speter} 315251881Speter 316251881Speter/* Return true if PI's path is a child of PREFIX (which has length PLEN). */ 317251881Speterstatic svn_boolean_t 318251881Speterrelevant(path_info_t *pi, const char *prefix, apr_size_t plen) 319251881Speter{ 320251881Speter return (pi && strncmp(pi->path, prefix, plen) == 0 && 321251881Speter (!*prefix || pi->path[plen] == '/')); 322251881Speter} 323251881Speter 324251881Speter/* Fetch the next pathinfo from B->reader for a descendant of 325251881Speter PREFIX. If the next pathinfo is for an immediate child of PREFIX, 326251881Speter set *ENTRY to the path component of the report information and 327251881Speter *INFO to the path information for that entry. If the next pathinfo 328251881Speter is for a grandchild or other more remote descendant of PREFIX, set 329251881Speter *ENTRY to the immediate child corresponding to that descendant and 330251881Speter set *INFO to NULL. If the next pathinfo is not for a descendant of 331251881Speter PREFIX, or if we reach the end of the report, set both *ENTRY and 332251881Speter *INFO to NULL. 333251881Speter 334251881Speter At all times, B->lookahead is presumed to be the next pathinfo not 335251881Speter yet returned as an immediate child, or NULL if we have reached the 336251881Speter end of the report. Because we use a lookahead element, we can't 337251881Speter rely on the usual nested pool lifetimes, so allocate each pathinfo 338251881Speter in a subpool of the report baton's pool. The caller should delete 339251881Speter (*INFO)->pool when it is done with the information. */ 340251881Speterstatic svn_error_t * 341251881Speterfetch_path_info(report_baton_t *b, const char **entry, path_info_t **info, 342251881Speter const char *prefix, apr_pool_t *pool) 343251881Speter{ 344251881Speter apr_size_t plen = strlen(prefix); 345251881Speter const char *relpath, *sep; 346251881Speter apr_pool_t *subpool; 347251881Speter 348251881Speter if (!relevant(b->lookahead, prefix, plen)) 349251881Speter { 350251881Speter /* No more entries relevant to prefix. */ 351251881Speter *entry = NULL; 352251881Speter *info = NULL; 353251881Speter } 354251881Speter else 355251881Speter { 356251881Speter /* Take a look at the prefix-relative part of the path. */ 357251881Speter relpath = b->lookahead->path + (*prefix ? plen + 1 : 0); 358251881Speter sep = strchr(relpath, '/'); 359251881Speter if (sep) 360251881Speter { 361251881Speter /* Return the immediate child part; do not advance. */ 362251881Speter *entry = apr_pstrmemdup(pool, relpath, sep - relpath); 363251881Speter *info = NULL; 364251881Speter } 365251881Speter else 366251881Speter { 367251881Speter /* This is an immediate child; return it and advance. */ 368251881Speter *entry = relpath; 369251881Speter *info = b->lookahead; 370251881Speter subpool = svn_pool_create(b->pool); 371251881Speter SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); 372251881Speter } 373251881Speter } 374251881Speter return SVN_NO_ERROR; 375251881Speter} 376251881Speter 377251881Speter/* Skip all path info entries relevant to *PREFIX. Call this when the 378251881Speter editor drive skips a directory. */ 379251881Speterstatic svn_error_t * 380251881Speterskip_path_info(report_baton_t *b, const char *prefix) 381251881Speter{ 382251881Speter apr_size_t plen = strlen(prefix); 383251881Speter apr_pool_t *subpool; 384251881Speter 385251881Speter while (relevant(b->lookahead, prefix, plen)) 386251881Speter { 387251881Speter svn_pool_destroy(b->lookahead->pool); 388251881Speter subpool = svn_pool_create(b->pool); 389251881Speter SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); 390251881Speter } 391251881Speter return SVN_NO_ERROR; 392251881Speter} 393251881Speter 394251881Speter/* Return true if there is at least one path info entry relevant to *PREFIX. */ 395251881Speterstatic svn_boolean_t 396251881Speterany_path_info(report_baton_t *b, const char *prefix) 397251881Speter{ 398251881Speter return relevant(b->lookahead, prefix, strlen(prefix)); 399251881Speter} 400251881Speter 401251881Speter/* --- DRIVING THE EDITOR ONCE THE REPORT IS FINISHED --- */ 402251881Speter 403251881Speter/* While driving the editor, the target root will remain constant, but 404251881Speter we may have to jump around between source roots depending on the 405251881Speter state of the working copy. If we were to open a root each time we 406251881Speter revisit a rev, we would get no benefit from node-id caching; on the 407251881Speter other hand, if we hold open all the roots we ever visit, we'll use 408251881Speter an unbounded amount of memory. As a compromise, we maintain a 409251881Speter fixed-size LRU cache of source roots. get_source_root retrieves a 410251881Speter root from the cache, using POOL to allocate the new root if 411251881Speter necessary. Be careful not to hold onto the root for too long, 412251881Speter particularly after recursing, since another call to get_source_root 413251881Speter can close it. */ 414251881Speterstatic svn_error_t * 415251881Speterget_source_root(report_baton_t *b, svn_fs_root_t **s_root, svn_revnum_t rev) 416251881Speter{ 417251881Speter int i; 418251881Speter svn_fs_root_t *root, *prev = NULL; 419251881Speter 420251881Speter /* Look for the desired root in the cache, sliding all the unmatched 421251881Speter entries backwards a slot to make room for the right one. */ 422251881Speter for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++) 423251881Speter { 424251881Speter root = b->s_roots[i]; 425251881Speter b->s_roots[i] = prev; 426251881Speter if (root && svn_fs_revision_root_revision(root) == rev) 427251881Speter break; 428251881Speter prev = root; 429251881Speter } 430251881Speter 431251881Speter /* If we didn't find it, throw out the oldest root and open a new one. */ 432251881Speter if (i == NUM_CACHED_SOURCE_ROOTS) 433251881Speter { 434251881Speter if (prev) 435251881Speter svn_fs_close_root(prev); 436251881Speter SVN_ERR(svn_fs_revision_root(&root, b->repos->fs, rev, b->pool)); 437251881Speter } 438251881Speter 439251881Speter /* Assign the desired root to the first cache slot and hand it back. */ 440251881Speter b->s_roots[0] = root; 441251881Speter *s_root = root; 442251881Speter return SVN_NO_ERROR; 443251881Speter} 444251881Speter 445251881Speter/* Call the directory property-setting function of B->editor to set 446251881Speter the property NAME to VALUE on DIR_BATON. */ 447251881Speterstatic svn_error_t * 448251881Speterchange_dir_prop(report_baton_t *b, void *dir_baton, const char *name, 449251881Speter const svn_string_t *value, apr_pool_t *pool) 450251881Speter{ 451251881Speter return svn_error_trace(b->editor->change_dir_prop(dir_baton, name, value, 452251881Speter pool)); 453251881Speter} 454251881Speter 455251881Speter/* Call the file property-setting function of B->editor to set the 456251881Speter property NAME to VALUE on FILE_BATON. */ 457251881Speterstatic svn_error_t * 458251881Speterchange_file_prop(report_baton_t *b, void *file_baton, const char *name, 459251881Speter const svn_string_t *value, apr_pool_t *pool) 460251881Speter{ 461251881Speter return svn_error_trace(b->editor->change_file_prop(file_baton, name, value, 462251881Speter pool)); 463251881Speter} 464251881Speter 465251881Speter/* For the report B, return the relevant revprop data of revision REV in 466251881Speter REVISION_INFO. The revision info will be allocated in b->pool. 467251881Speter Temporaries get allocated on SCRATCH_POOL. */ 468251881Speterstatic svn_error_t * 469251881Speterget_revision_info(report_baton_t *b, 470251881Speter svn_revnum_t rev, 471251881Speter revision_info_t** revision_info, 472251881Speter apr_pool_t *scratch_pool) 473251881Speter{ 474251881Speter apr_hash_t *r_props; 475251881Speter svn_string_t *cdate, *author; 476251881Speter revision_info_t* info; 477251881Speter 478251881Speter /* Try to find the info in the report's cache */ 479251881Speter info = apr_hash_get(b->revision_infos, &rev, sizeof(rev)); 480251881Speter if (!info) 481251881Speter { 482251881Speter /* Info is not available, yet. 483251881Speter Get all revprops. */ 484251881Speter SVN_ERR(svn_fs_revision_proplist(&r_props, 485251881Speter b->repos->fs, 486251881Speter rev, 487251881Speter scratch_pool)); 488251881Speter 489251881Speter /* Extract the committed-date. */ 490251881Speter cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE); 491251881Speter 492251881Speter /* Extract the last-author. */ 493251881Speter author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR); 494251881Speter 495251881Speter /* Create a result object */ 496251881Speter info = apr_palloc(b->pool, sizeof(*info)); 497251881Speter info->rev = rev; 498251881Speter info->date = cdate ? svn_string_dup(cdate, b->pool) : NULL; 499251881Speter info->author = author ? svn_string_dup(author, b->pool) : NULL; 500251881Speter 501251881Speter /* Cache it */ 502251881Speter apr_hash_set(b->revision_infos, &info->rev, sizeof(rev), info); 503251881Speter } 504251881Speter 505251881Speter *revision_info = info; 506251881Speter return SVN_NO_ERROR; 507251881Speter} 508251881Speter 509251881Speter 510251881Speter/* Generate the appropriate property editing calls to turn the 511251881Speter properties of S_REV/S_PATH into those of B->t_root/T_PATH. If 512251881Speter S_PATH is NULL, this is an add, so assume the target starts with no 513251881Speter properties. Pass OBJECT on to the editor function wrapper 514251881Speter CHANGE_FN. */ 515251881Speterstatic svn_error_t * 516251881Speterdelta_proplists(report_baton_t *b, svn_revnum_t s_rev, const char *s_path, 517251881Speter const char *t_path, const char *lock_token, 518251881Speter proplist_change_fn_t *change_fn, 519251881Speter void *object, apr_pool_t *pool) 520251881Speter{ 521251881Speter svn_fs_root_t *s_root; 522251881Speter apr_hash_t *s_props = NULL, *t_props; 523251881Speter apr_array_header_t *prop_diffs; 524251881Speter int i; 525251881Speter svn_revnum_t crev; 526251881Speter revision_info_t *revision_info; 527251881Speter svn_boolean_t changed; 528251881Speter const svn_prop_t *pc; 529251881Speter svn_lock_t *lock; 530251881Speter apr_hash_index_t *hi; 531251881Speter 532251881Speter /* Fetch the created-rev and send entry props. */ 533251881Speter SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool)); 534251881Speter if (SVN_IS_VALID_REVNUM(crev)) 535251881Speter { 536251881Speter /* convert committed-rev to string */ 537251881Speter char buf[SVN_INT64_BUFFER_SIZE]; 538251881Speter svn_string_t cr_str; 539251881Speter cr_str.data = buf; 540251881Speter cr_str.len = svn__i64toa(buf, crev); 541251881Speter 542251881Speter /* Transmit the committed-rev. */ 543251881Speter SVN_ERR(change_fn(b, object, 544251881Speter SVN_PROP_ENTRY_COMMITTED_REV, &cr_str, pool)); 545251881Speter 546251881Speter SVN_ERR(get_revision_info(b, crev, &revision_info, pool)); 547251881Speter 548251881Speter /* Transmit the committed-date. */ 549251881Speter if (revision_info->date || s_path) 550251881Speter SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_COMMITTED_DATE, 551251881Speter revision_info->date, pool)); 552251881Speter 553251881Speter /* Transmit the last-author. */ 554251881Speter if (revision_info->author || s_path) 555251881Speter SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LAST_AUTHOR, 556251881Speter revision_info->author, pool)); 557251881Speter 558251881Speter /* Transmit the UUID. */ 559251881Speter SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_UUID, 560251881Speter b->repos_uuid, pool)); 561251881Speter } 562251881Speter 563251881Speter /* Update lock properties. */ 564251881Speter if (lock_token) 565251881Speter { 566251881Speter SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool)); 567251881Speter 568251881Speter /* Delete a defunct lock. */ 569251881Speter if (! lock || strcmp(lock_token, lock->token) != 0) 570251881Speter SVN_ERR(change_fn(b, object, SVN_PROP_ENTRY_LOCK_TOKEN, 571251881Speter NULL, pool)); 572251881Speter } 573251881Speter 574251881Speter if (s_path) 575251881Speter { 576251881Speter SVN_ERR(get_source_root(b, &s_root, s_rev)); 577251881Speter 578251881Speter /* Is this deltification worth our time? */ 579251881Speter SVN_ERR(svn_fs_props_changed(&changed, b->t_root, t_path, s_root, 580251881Speter s_path, pool)); 581251881Speter if (! changed) 582251881Speter return SVN_NO_ERROR; 583251881Speter 584251881Speter /* If so, go ahead and get the source path's properties. */ 585251881Speter SVN_ERR(svn_fs_node_proplist(&s_props, s_root, s_path, pool)); 586251881Speter } 587251881Speter 588251881Speter /* Get the target path's properties */ 589251881Speter SVN_ERR(svn_fs_node_proplist(&t_props, b->t_root, t_path, pool)); 590251881Speter 591251881Speter if (s_props && apr_hash_count(s_props)) 592251881Speter { 593251881Speter /* Now transmit the differences. */ 594251881Speter SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool)); 595251881Speter for (i = 0; i < prop_diffs->nelts; i++) 596251881Speter { 597251881Speter pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); 598251881Speter SVN_ERR(change_fn(b, object, pc->name, pc->value, pool)); 599251881Speter } 600251881Speter } 601251881Speter else if (apr_hash_count(t_props)) 602251881Speter { 603251881Speter /* So source, i.e. all new. Transmit all target props. */ 604251881Speter for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi)) 605251881Speter { 606251881Speter const void *key; 607251881Speter void *val; 608251881Speter 609251881Speter apr_hash_this(hi, &key, NULL, &val); 610251881Speter SVN_ERR(change_fn(b, object, key, val, pool)); 611251881Speter } 612251881Speter } 613251881Speter 614251881Speter return SVN_NO_ERROR; 615251881Speter} 616251881Speter 617251881Speter/* Baton type to be passed into send_zero_copy_delta. 618251881Speter */ 619251881Spetertypedef struct zero_copy_baton_t 620251881Speter{ 621251881Speter /* don't process data larger than this limit */ 622251881Speter apr_size_t zero_copy_limit; 623251881Speter 624251881Speter /* window handler and baton to send the data to */ 625251881Speter svn_txdelta_window_handler_t dhandler; 626251881Speter void *dbaton; 627251881Speter 628251881Speter /* return value: will be set to TRUE, if the data was processed. */ 629251881Speter svn_boolean_t zero_copy_succeeded; 630251881Speter} zero_copy_baton_t; 631251881Speter 632251881Speter/* Implement svn_fs_process_contents_func_t. If LEN is smaller than the 633251881Speter * limit given in *BATON, send the CONTENTS as an delta windows to the 634251881Speter * handler given in BATON and set the ZERO_COPY_SUCCEEDED flag in that 635251881Speter * BATON. Otherwise, reset it to FALSE. 636251881Speter * Use POOL for temporary allocations. 637251881Speter */ 638251881Speterstatic svn_error_t * 639251881Spetersend_zero_copy_delta(const unsigned char *contents, 640251881Speter apr_size_t len, 641251881Speter void *baton, 642251881Speter apr_pool_t *pool) 643251881Speter{ 644251881Speter zero_copy_baton_t *zero_copy_baton = baton; 645251881Speter 646251881Speter /* if the item is too large, the caller must revert to traditional 647251881Speter streaming code. */ 648251881Speter if (len > zero_copy_baton->zero_copy_limit) 649251881Speter { 650251881Speter zero_copy_baton->zero_copy_succeeded = FALSE; 651251881Speter return SVN_NO_ERROR; 652251881Speter } 653251881Speter 654251881Speter SVN_ERR(svn_txdelta_send_contents(contents, len, 655251881Speter zero_copy_baton->dhandler, 656251881Speter zero_copy_baton->dbaton, pool)); 657251881Speter 658251881Speter /* all fine now */ 659251881Speter zero_copy_baton->zero_copy_succeeded = TRUE; 660251881Speter return SVN_NO_ERROR; 661251881Speter} 662251881Speter 663251881Speter 664251881Speter/* Make the appropriate edits on FILE_BATON to change its contents and 665251881Speter properties from those in S_REV/S_PATH to those in B->t_root/T_PATH, 666251881Speter possibly using LOCK_TOKEN to determine if the client's lock on the file 667251881Speter is defunct. */ 668251881Speterstatic svn_error_t * 669251881Speterdelta_files(report_baton_t *b, void *file_baton, svn_revnum_t s_rev, 670251881Speter const char *s_path, const char *t_path, const char *lock_token, 671251881Speter apr_pool_t *pool) 672251881Speter{ 673251881Speter svn_boolean_t changed; 674251881Speter svn_fs_root_t *s_root = NULL; 675251881Speter svn_txdelta_stream_t *dstream = NULL; 676251881Speter svn_checksum_t *s_checksum; 677251881Speter const char *s_hex_digest = NULL; 678251881Speter svn_txdelta_window_handler_t dhandler; 679251881Speter void *dbaton; 680251881Speter 681251881Speter /* Compare the files' property lists. */ 682251881Speter SVN_ERR(delta_proplists(b, s_rev, s_path, t_path, lock_token, 683251881Speter change_file_prop, file_baton, pool)); 684251881Speter 685251881Speter if (s_path) 686251881Speter { 687251881Speter SVN_ERR(get_source_root(b, &s_root, s_rev)); 688251881Speter 689251881Speter /* We're not interested in the theoretical difference between "has 690251881Speter contents which have not changed with respect to" and "has the same 691251881Speter actual contents as" when sending text-deltas. If we know the 692251881Speter delta is an empty one, we avoiding sending it in either case. */ 693251881Speter SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path, 694251881Speter s_root, s_path, pool)); 695251881Speter 696251881Speter if (!changed) 697251881Speter return SVN_NO_ERROR; 698251881Speter 699251881Speter SVN_ERR(svn_fs_file_checksum(&s_checksum, svn_checksum_md5, s_root, 700251881Speter s_path, TRUE, pool)); 701251881Speter s_hex_digest = svn_checksum_to_cstring(s_checksum, pool); 702251881Speter } 703251881Speter 704251881Speter /* Send the delta stream if desired, or just a NULL window if not. */ 705251881Speter SVN_ERR(b->editor->apply_textdelta(file_baton, s_hex_digest, pool, 706251881Speter &dhandler, &dbaton)); 707251881Speter 708251881Speter if (dhandler != svn_delta_noop_window_handler) 709251881Speter { 710251881Speter if (b->text_deltas) 711251881Speter { 712251881Speter /* if we send deltas against empty streams, we may use our 713251881Speter zero-copy code. */ 714251881Speter if (b->zero_copy_limit > 0 && s_path == NULL) 715251881Speter { 716251881Speter zero_copy_baton_t baton; 717251881Speter svn_boolean_t called = FALSE; 718251881Speter 719251881Speter baton.zero_copy_limit = b->zero_copy_limit; 720251881Speter baton.dhandler = dhandler; 721251881Speter baton.dbaton = dbaton; 722251881Speter baton.zero_copy_succeeded = FALSE; 723251881Speter SVN_ERR(svn_fs_try_process_file_contents(&called, 724251881Speter b->t_root, t_path, 725251881Speter send_zero_copy_delta, 726251881Speter &baton, pool)); 727251881Speter 728251881Speter /* data has been available and small enough, 729251881Speter i.e. been processed? */ 730251881Speter if (called && baton.zero_copy_succeeded) 731251881Speter return SVN_NO_ERROR; 732251881Speter } 733251881Speter 734251881Speter SVN_ERR(svn_fs_get_file_delta_stream(&dstream, s_root, s_path, 735251881Speter b->t_root, t_path, pool)); 736251881Speter SVN_ERR(svn_txdelta_send_txstream(dstream, dhandler, dbaton, pool)); 737251881Speter } 738251881Speter else 739251881Speter SVN_ERR(dhandler(NULL, dbaton)); 740251881Speter } 741251881Speter 742251881Speter return SVN_NO_ERROR; 743251881Speter} 744251881Speter 745251881Speter/* Determine if the user is authorized to view B->t_root/PATH. */ 746251881Speterstatic svn_error_t * 747251881Spetercheck_auth(report_baton_t *b, svn_boolean_t *allowed, const char *path, 748251881Speter apr_pool_t *pool) 749251881Speter{ 750251881Speter if (b->authz_read_func) 751251881Speter return svn_error_trace(b->authz_read_func(allowed, b->t_root, path, 752251881Speter b->authz_read_baton, pool)); 753251881Speter *allowed = TRUE; 754251881Speter return SVN_NO_ERROR; 755251881Speter} 756251881Speter 757251881Speter/* Create a dirent in *ENTRY for the given ROOT and PATH. We use this to 758251881Speter replace the source or target dirent when a report pathinfo tells us to 759251881Speter change paths or revisions. */ 760251881Speterstatic svn_error_t * 761251881Speterfake_dirent(const svn_fs_dirent_t **entry, svn_fs_root_t *root, 762251881Speter const char *path, apr_pool_t *pool) 763251881Speter{ 764251881Speter svn_node_kind_t kind; 765251881Speter svn_fs_dirent_t *ent; 766251881Speter 767251881Speter SVN_ERR(svn_fs_check_path(&kind, root, path, pool)); 768251881Speter if (kind == svn_node_none) 769251881Speter *entry = NULL; 770251881Speter else 771251881Speter { 772251881Speter ent = apr_palloc(pool, sizeof(**entry)); 773251881Speter /* ### All callers should be updated to pass just one of these 774251881Speter formats */ 775251881Speter ent->name = (*path == '/') ? svn_fspath__basename(path, pool) 776251881Speter : svn_relpath_basename(path, pool); 777251881Speter SVN_ERR(svn_fs_node_id(&ent->id, root, path, pool)); 778251881Speter ent->kind = kind; 779251881Speter *entry = ent; 780251881Speter } 781251881Speter return SVN_NO_ERROR; 782251881Speter} 783251881Speter 784251881Speter 785251881Speter/* Given REQUESTED_DEPTH, WC_DEPTH and the current entry's KIND, 786251881Speter determine whether we need to send the whole entry, not just deltas. 787251881Speter Please refer to delta_dirs' docstring for an explanation of the 788251881Speter conditionals below. */ 789251881Speterstatic svn_boolean_t 790251881Speteris_depth_upgrade(svn_depth_t wc_depth, 791251881Speter svn_depth_t requested_depth, 792251881Speter svn_node_kind_t kind) 793251881Speter{ 794251881Speter if (requested_depth == svn_depth_unknown 795251881Speter || requested_depth <= wc_depth 796251881Speter || wc_depth == svn_depth_immediates) 797251881Speter return FALSE; 798251881Speter 799251881Speter if (kind == svn_node_file 800251881Speter && wc_depth == svn_depth_files) 801251881Speter return FALSE; 802251881Speter 803251881Speter if (kind == svn_node_dir 804251881Speter && wc_depth == svn_depth_empty 805251881Speter && requested_depth == svn_depth_files) 806251881Speter return FALSE; 807251881Speter 808251881Speter return TRUE; 809251881Speter} 810251881Speter 811251881Speter 812251881Speter/* Call the B->editor's add_file() function to create PATH as a child 813251881Speter of PARENT_BATON, returning a new baton in *NEW_FILE_BATON. 814251881Speter However, make an attempt to send 'copyfrom' arguments if they're 815251881Speter available, by examining the closest copy of the original file 816251881Speter O_PATH within B->t_root. If any copyfrom args are discovered, 817251881Speter return those in *COPYFROM_PATH and *COPYFROM_REV; otherwise leave 818251881Speter those return args untouched. */ 819251881Speterstatic svn_error_t * 820251881Speteradd_file_smartly(report_baton_t *b, 821251881Speter const char *path, 822251881Speter void *parent_baton, 823251881Speter const char *o_path, 824251881Speter void **new_file_baton, 825251881Speter const char **copyfrom_path, 826251881Speter svn_revnum_t *copyfrom_rev, 827251881Speter apr_pool_t *pool) 828251881Speter{ 829251881Speter /* ### TODO: use a subpool to do this work, clear it at the end? */ 830251881Speter svn_fs_t *fs = svn_repos_fs(b->repos); 831251881Speter svn_fs_root_t *closest_copy_root = NULL; 832251881Speter const char *closest_copy_path = NULL; 833251881Speter 834251881Speter /* Pre-emptively assume no copyfrom args exist. */ 835251881Speter *copyfrom_path = NULL; 836251881Speter *copyfrom_rev = SVN_INVALID_REVNUM; 837251881Speter 838251881Speter if (b->send_copyfrom_args) 839251881Speter { 840251881Speter /* Find the destination of the nearest 'copy event' which may have 841251881Speter caused o_path@t_root to exist. svn_fs_closest_copy only returns paths 842251881Speter starting with '/', so make sure o_path always starts with a '/' 843251881Speter too. */ 844251881Speter if (*o_path != '/') 845251881Speter o_path = apr_pstrcat(pool, "/", o_path, (char *)NULL); 846251881Speter 847251881Speter SVN_ERR(svn_fs_closest_copy(&closest_copy_root, &closest_copy_path, 848251881Speter b->t_root, o_path, pool)); 849251881Speter if (closest_copy_root != NULL) 850251881Speter { 851251881Speter /* If the destination of the copy event is the same path as 852251881Speter o_path, then we've found something interesting that should 853251881Speter have 'copyfrom' history. */ 854251881Speter if (strcmp(closest_copy_path, o_path) == 0) 855251881Speter { 856251881Speter SVN_ERR(svn_fs_copied_from(copyfrom_rev, copyfrom_path, 857251881Speter closest_copy_root, closest_copy_path, 858251881Speter pool)); 859251881Speter if (b->authz_read_func) 860251881Speter { 861251881Speter svn_boolean_t allowed; 862251881Speter svn_fs_root_t *copyfrom_root; 863251881Speter SVN_ERR(svn_fs_revision_root(©from_root, fs, 864251881Speter *copyfrom_rev, pool)); 865251881Speter SVN_ERR(b->authz_read_func(&allowed, copyfrom_root, 866251881Speter *copyfrom_path, b->authz_read_baton, 867251881Speter pool)); 868251881Speter if (! allowed) 869251881Speter { 870251881Speter *copyfrom_path = NULL; 871251881Speter *copyfrom_rev = SVN_INVALID_REVNUM; 872251881Speter } 873251881Speter } 874251881Speter } 875251881Speter } 876251881Speter } 877251881Speter 878251881Speter return svn_error_trace(b->editor->add_file(path, parent_baton, 879251881Speter *copyfrom_path, *copyfrom_rev, 880251881Speter pool, new_file_baton)); 881251881Speter} 882251881Speter 883251881Speter 884251881Speter/* Emit a series of editing operations to transform a source entry to 885251881Speter a target entry. 886251881Speter 887251881Speter S_REV and S_PATH specify the source entry. S_ENTRY contains the 888251881Speter already-looked-up information about the node-revision existing at 889251881Speter that location. S_PATH and S_ENTRY may be NULL if the entry does 890251881Speter not exist in the source. S_PATH may be non-NULL and S_ENTRY may be 891251881Speter NULL if the caller expects INFO to modify the source to an existing 892251881Speter location. 893251881Speter 894251881Speter B->t_root and T_PATH specify the target entry. T_ENTRY contains 895251881Speter the already-looked-up information about the node-revision existing 896251881Speter at that location. T_PATH and T_ENTRY may be NULL if the entry does 897251881Speter not exist in the target. 898251881Speter 899251881Speter DIR_BATON and E_PATH contain the parameters which should be passed 900251881Speter to the editor calls--DIR_BATON for the parent directory baton and 901251881Speter E_PATH for the pathname. (E_PATH is the anchor-relative working 902251881Speter copy pathname, which may differ from the source and target 903251881Speter pathnames if the report contains a link_path.) 904251881Speter 905251881Speter INFO contains the report information for this working copy path, or 906251881Speter NULL if there is none. This function will internally modify the 907251881Speter source and target entries as appropriate based on the report 908251881Speter information. 909251881Speter 910251881Speter WC_DEPTH and REQUESTED_DEPTH are propagated to delta_dirs() if 911251881Speter necessary. Refer to delta_dirs' docstring to find out what 912251881Speter should happen for various combinations of WC_DEPTH/REQUESTED_DEPTH. */ 913251881Speterstatic svn_error_t * 914251881Speterupdate_entry(report_baton_t *b, svn_revnum_t s_rev, const char *s_path, 915251881Speter const svn_fs_dirent_t *s_entry, const char *t_path, 916251881Speter const svn_fs_dirent_t *t_entry, void *dir_baton, 917251881Speter const char *e_path, path_info_t *info, svn_depth_t wc_depth, 918251881Speter svn_depth_t requested_depth, apr_pool_t *pool) 919251881Speter{ 920251881Speter svn_fs_root_t *s_root; 921251881Speter svn_boolean_t allowed, related; 922251881Speter void *new_baton; 923251881Speter svn_checksum_t *checksum; 924251881Speter const char *hex_digest; 925251881Speter 926251881Speter /* For non-switch operations, follow link_path in the target. */ 927251881Speter if (info && info->link_path && !b->is_switch) 928251881Speter { 929251881Speter t_path = info->link_path; 930251881Speter SVN_ERR(fake_dirent(&t_entry, b->t_root, t_path, pool)); 931251881Speter } 932251881Speter 933251881Speter if (info && !SVN_IS_VALID_REVNUM(info->rev)) 934251881Speter { 935251881Speter /* Delete this entry in the source. */ 936251881Speter s_path = NULL; 937251881Speter s_entry = NULL; 938251881Speter } 939251881Speter else if (info && s_path) 940251881Speter { 941251881Speter /* Follow the rev and possibly path in this entry. */ 942251881Speter s_path = (info->link_path) ? info->link_path : s_path; 943251881Speter s_rev = info->rev; 944251881Speter SVN_ERR(get_source_root(b, &s_root, s_rev)); 945251881Speter SVN_ERR(fake_dirent(&s_entry, s_root, s_path, pool)); 946251881Speter } 947251881Speter 948251881Speter /* Don't let the report carry us somewhere nonexistent. */ 949251881Speter if (s_path && !s_entry) 950251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 951251881Speter _("Working copy path '%s' does not exist in " 952251881Speter "repository"), e_path); 953251881Speter 954251881Speter /* If the source and target both exist and are of the same kind, 955251881Speter then find out whether they're related. If they're exactly the 956251881Speter same, then we don't have to do anything (unless the report has 957251881Speter changes to the source). If we're ignoring ancestry, then any two 958251881Speter nodes of the same type are related enough for us. */ 959251881Speter related = FALSE; 960251881Speter if (s_entry && t_entry && s_entry->kind == t_entry->kind) 961251881Speter { 962251881Speter int distance = svn_fs_compare_ids(s_entry->id, t_entry->id); 963251881Speter if (distance == 0 && !any_path_info(b, e_path) 964251881Speter && (requested_depth <= wc_depth || t_entry->kind == svn_node_file)) 965251881Speter { 966251881Speter if (!info) 967251881Speter return SVN_NO_ERROR; 968251881Speter 969251881Speter if (!info->start_empty) 970251881Speter { 971251881Speter svn_lock_t *lock; 972251881Speter 973251881Speter if (!info->lock_token) 974251881Speter return SVN_NO_ERROR; 975251881Speter 976251881Speter SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool)); 977251881Speter if (lock && (strcmp(lock->token, info->lock_token) == 0)) 978251881Speter return SVN_NO_ERROR; 979251881Speter } 980251881Speter } 981251881Speter 982251881Speter related = (distance != -1 || b->ignore_ancestry); 983251881Speter } 984251881Speter 985251881Speter /* If there's a source and it's not related to the target, nuke it. */ 986251881Speter if (s_entry && !related) 987251881Speter { 988251881Speter svn_revnum_t deleted_rev; 989251881Speter 990251881Speter SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), t_path, 991251881Speter s_rev, b->t_rev, &deleted_rev, 992251881Speter pool)); 993251881Speter 994251881Speter if (!SVN_IS_VALID_REVNUM(deleted_rev)) 995251881Speter { 996251881Speter /* Two possibilities: either the thing doesn't exist in S_REV; or 997251881Speter it wasn't deleted between S_REV and B->T_REV. In the first case, 998251881Speter I think we should leave DELETED_REV as SVN_INVALID_REVNUM, but 999251881Speter in the second, it should be set to B->T_REV-1 for the call to 1000251881Speter delete_entry() below. */ 1001251881Speter svn_node_kind_t kind; 1002251881Speter 1003251881Speter SVN_ERR(svn_fs_check_path(&kind, b->t_root, t_path, pool)); 1004251881Speter if (kind != svn_node_none) 1005251881Speter deleted_rev = b->t_rev - 1; 1006251881Speter } 1007251881Speter 1008251881Speter SVN_ERR(b->editor->delete_entry(e_path, deleted_rev, dir_baton, 1009251881Speter pool)); 1010251881Speter s_path = NULL; 1011251881Speter } 1012251881Speter 1013251881Speter /* If there's no target, we have nothing more to do. */ 1014251881Speter if (!t_entry) 1015251881Speter return svn_error_trace(skip_path_info(b, e_path)); 1016251881Speter 1017251881Speter /* Check if the user is authorized to find out about the target. */ 1018251881Speter SVN_ERR(check_auth(b, &allowed, t_path, pool)); 1019251881Speter if (!allowed) 1020251881Speter { 1021251881Speter if (t_entry->kind == svn_node_dir) 1022251881Speter SVN_ERR(b->editor->absent_directory(e_path, dir_baton, pool)); 1023251881Speter else 1024251881Speter SVN_ERR(b->editor->absent_file(e_path, dir_baton, pool)); 1025251881Speter return svn_error_trace(skip_path_info(b, e_path)); 1026251881Speter } 1027251881Speter 1028251881Speter if (t_entry->kind == svn_node_dir) 1029251881Speter { 1030251881Speter if (related) 1031251881Speter SVN_ERR(b->editor->open_directory(e_path, dir_baton, s_rev, pool, 1032251881Speter &new_baton)); 1033251881Speter else 1034251881Speter SVN_ERR(b->editor->add_directory(e_path, dir_baton, NULL, 1035251881Speter SVN_INVALID_REVNUM, pool, 1036251881Speter &new_baton)); 1037251881Speter 1038251881Speter SVN_ERR(delta_dirs(b, s_rev, s_path, t_path, new_baton, e_path, 1039251881Speter info ? info->start_empty : FALSE, 1040251881Speter wc_depth, requested_depth, pool)); 1041251881Speter return svn_error_trace(b->editor->close_directory(new_baton, pool)); 1042251881Speter } 1043251881Speter else 1044251881Speter { 1045251881Speter if (related) 1046251881Speter { 1047251881Speter SVN_ERR(b->editor->open_file(e_path, dir_baton, s_rev, pool, 1048251881Speter &new_baton)); 1049251881Speter SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path, 1050251881Speter info ? info->lock_token : NULL, pool)); 1051251881Speter } 1052251881Speter else 1053251881Speter { 1054251881Speter svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 1055251881Speter const char *copyfrom_path = NULL; 1056251881Speter SVN_ERR(add_file_smartly(b, e_path, dir_baton, t_path, &new_baton, 1057251881Speter ©from_path, ©from_rev, pool)); 1058251881Speter if (! copyfrom_path) 1059251881Speter /* Send txdelta between empty file (s_path@s_rev doesn't 1060251881Speter exist) and added file (t_path@t_root). */ 1061251881Speter SVN_ERR(delta_files(b, new_baton, s_rev, s_path, t_path, 1062251881Speter info ? info->lock_token : NULL, pool)); 1063251881Speter else 1064251881Speter /* Send txdelta between copied file (copyfrom_path@copyfrom_rev) 1065251881Speter and added file (tpath@t_root). */ 1066251881Speter SVN_ERR(delta_files(b, new_baton, copyfrom_rev, copyfrom_path, 1067251881Speter t_path, info ? info->lock_token : NULL, pool)); 1068251881Speter } 1069251881Speter 1070251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, b->t_root, 1071251881Speter t_path, TRUE, pool)); 1072251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 1073251881Speter return svn_error_trace(b->editor->close_file(new_baton, hex_digest, 1074251881Speter pool)); 1075251881Speter } 1076251881Speter} 1077251881Speter 1078251881Speter/* A helper macro for when we have to recurse into subdirectories. */ 1079251881Speter#define DEPTH_BELOW_HERE(depth) ((depth) == svn_depth_immediates) ? \ 1080251881Speter svn_depth_empty : (depth) 1081251881Speter 1082251881Speter/* Emit edits within directory DIR_BATON (with corresponding path 1083251881Speter E_PATH) with the changes from the directory S_REV/S_PATH to the 1084251881Speter directory B->t_rev/T_PATH. S_PATH may be NULL if the entry does 1085251881Speter not exist in the source. 1086251881Speter 1087251881Speter WC_DEPTH is this path's depth as reported by set_path/link_path. 1088251881Speter REQUESTED_DEPTH is derived from the depth set by 1089251881Speter svn_repos_begin_report(). 1090251881Speter 1091251881Speter When iterating over this directory's entries, the following tables 1092251881Speter describe what happens for all possible combinations 1093251881Speter of WC_DEPTH/REQUESTED_DEPTH (rows represent WC_DEPTH, columns 1094251881Speter represent REQUESTED_DEPTH): 1095251881Speter 1096251881Speter Legend: 1097251881Speter X: ignore this entry (it's either below the requested depth, or 1098251881Speter if the requested depth is svn_depth_unknown, below the working 1099251881Speter copy depth) 1100251881Speter o: handle this entry normally 1101251881Speter U: handle the entry as if it were a newly added repository path 1102251881Speter (the client is upgrading to a deeper wc and doesn't currently 1103251881Speter have this entry, but it should be there after the upgrade, so we 1104251881Speter need to send the whole thing, not just deltas) 1105251881Speter 1106251881Speter For files: 1107251881Speter ______________________________________________________________ 1108251881Speter | req. depth| unknown | empty | files | immediates | infinity | 1109251881Speter |wc. depth | | | | | | 1110251881Speter |___________|_________|_______|_______|____________|__________| 1111251881Speter |empty | X | X | U | U | U | 1112251881Speter |___________|_________|_______|_______|____________|__________| 1113251881Speter |files | o | X | o | o | o | 1114251881Speter |___________|_________|_______|_______|____________|__________| 1115251881Speter |immediates | o | X | o | o | o | 1116251881Speter |___________|_________|_______|_______|____________|__________| 1117251881Speter |infinity | o | X | o | o | o | 1118251881Speter |___________|_________|_______|_______|____________|__________| 1119251881Speter 1120251881Speter For directories: 1121251881Speter ______________________________________________________________ 1122251881Speter | req. depth| unknown | empty | files | immediates | infinity | 1123251881Speter |wc. depth | | | | | | 1124251881Speter |___________|_________|_______|_______|____________|__________| 1125251881Speter |empty | X | X | X | U | U | 1126251881Speter |___________|_________|_______|_______|____________|__________| 1127251881Speter |files | X | X | X | U | U | 1128251881Speter |___________|_________|_______|_______|____________|__________| 1129251881Speter |immediates | o | X | X | o | o | 1130251881Speter |___________|_________|_______|_______|____________|__________| 1131251881Speter |infinity | o | X | X | o | o | 1132251881Speter |___________|_________|_______|_______|____________|__________| 1133251881Speter 1134251881Speter These rules are enforced by the is_depth_upgrade() function and by 1135251881Speter various other checks below. 1136251881Speter*/ 1137251881Speterstatic svn_error_t * 1138251881Speterdelta_dirs(report_baton_t *b, svn_revnum_t s_rev, const char *s_path, 1139251881Speter const char *t_path, void *dir_baton, const char *e_path, 1140251881Speter svn_boolean_t start_empty, svn_depth_t wc_depth, 1141251881Speter svn_depth_t requested_depth, apr_pool_t *pool) 1142251881Speter{ 1143251881Speter svn_fs_root_t *s_root; 1144251881Speter apr_hash_t *s_entries = NULL, *t_entries; 1145251881Speter apr_hash_index_t *hi; 1146262253Speter apr_pool_t *subpool = svn_pool_create(pool); 1147262253Speter apr_pool_t *iterpool; 1148251881Speter const char *name, *s_fullpath, *t_fullpath, *e_fullpath; 1149251881Speter path_info_t *info; 1150251881Speter 1151251881Speter /* Compare the property lists. If we're starting empty, pass a NULL 1152251881Speter source path so that we add all the properties. 1153251881Speter 1154251881Speter When we support directory locks, we must pass the lock token here. */ 1155251881Speter SVN_ERR(delta_proplists(b, s_rev, start_empty ? NULL : s_path, t_path, 1156262253Speter NULL, change_dir_prop, dir_baton, subpool)); 1157262253Speter svn_pool_clear(subpool); 1158251881Speter 1159251881Speter if (requested_depth > svn_depth_empty 1160251881Speter || requested_depth == svn_depth_unknown) 1161251881Speter { 1162251881Speter /* Get the list of entries in each of source and target. */ 1163251881Speter if (s_path && !start_empty) 1164251881Speter { 1165251881Speter SVN_ERR(get_source_root(b, &s_root, s_rev)); 1166262253Speter SVN_ERR(svn_fs_dir_entries(&s_entries, s_root, s_path, subpool)); 1167251881Speter } 1168262253Speter SVN_ERR(svn_fs_dir_entries(&t_entries, b->t_root, t_path, subpool)); 1169251881Speter 1170251881Speter /* Iterate over the report information for this directory. */ 1171262253Speter iterpool = svn_pool_create(pool); 1172251881Speter 1173251881Speter while (1) 1174251881Speter { 1175251881Speter const svn_fs_dirent_t *s_entry, *t_entry; 1176251881Speter 1177262253Speter svn_pool_clear(iterpool); 1178262253Speter SVN_ERR(fetch_path_info(b, &name, &info, e_path, iterpool)); 1179251881Speter if (!name) 1180251881Speter break; 1181251881Speter 1182251881Speter /* Invalid revnum means we should delete, unless this is 1183251881Speter just an excluded subpath. */ 1184251881Speter if (info 1185251881Speter && !SVN_IS_VALID_REVNUM(info->rev) 1186251881Speter && info->depth != svn_depth_exclude) 1187251881Speter { 1188251881Speter /* We want to perform deletes before non-replacement adds, 1189251881Speter for graceful handling of case-only renames on 1190251881Speter case-insensitive client filesystems. So, if the report 1191251881Speter item is a delete, remove the entry from the source hash, 1192251881Speter but don't update the entry yet. */ 1193251881Speter if (s_entries) 1194251881Speter svn_hash_sets(s_entries, name, NULL); 1195251881Speter continue; 1196251881Speter } 1197251881Speter 1198262253Speter e_fullpath = svn_relpath_join(e_path, name, iterpool); 1199262253Speter t_fullpath = svn_fspath__join(t_path, name, iterpool); 1200251881Speter t_entry = svn_hash_gets(t_entries, name); 1201262253Speter s_fullpath = s_path ? svn_fspath__join(s_path, name, iterpool) : NULL; 1202251881Speter s_entry = s_entries ? 1203251881Speter svn_hash_gets(s_entries, name) : NULL; 1204251881Speter 1205251881Speter /* The only special cases here are 1206251881Speter 1207251881Speter - When requested_depth is files but the reported path is 1208251881Speter a directory. This is technically a client error, but we 1209251881Speter handle it anyway, by skipping the entry. 1210251881Speter 1211251881Speter - When the reported depth is svn_depth_exclude. 1212251881Speter */ 1213251881Speter if ((! info || info->depth != svn_depth_exclude) 1214251881Speter && (requested_depth != svn_depth_files 1215251881Speter || ((! t_entry || t_entry->kind != svn_node_dir) 1216251881Speter && (! s_entry || s_entry->kind != svn_node_dir)))) 1217251881Speter SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath, 1218251881Speter t_entry, dir_baton, e_fullpath, info, 1219251881Speter info ? info->depth 1220251881Speter : DEPTH_BELOW_HERE(wc_depth), 1221262253Speter DEPTH_BELOW_HERE(requested_depth), iterpool)); 1222251881Speter 1223251881Speter /* Don't revisit this name in the target or source entries. */ 1224251881Speter svn_hash_sets(t_entries, name, NULL); 1225251881Speter if (s_entries 1226251881Speter /* Keep the entry for later process if it is reported as 1227251881Speter excluded and got deleted in repos. */ 1228251881Speter && (! info || info->depth != svn_depth_exclude || t_entry)) 1229251881Speter svn_hash_sets(s_entries, name, NULL); 1230251881Speter 1231251881Speter /* pathinfo entries live in their own subpools due to lookahead, 1232251881Speter so we need to clear each one out as we finish with it. */ 1233251881Speter if (info) 1234251881Speter svn_pool_destroy(info->pool); 1235251881Speter } 1236251881Speter 1237251881Speter /* Remove any deleted entries. Do this before processing the 1238251881Speter target, for graceful handling of case-only renames. */ 1239251881Speter if (s_entries) 1240251881Speter { 1241262253Speter for (hi = apr_hash_first(subpool, s_entries); 1242251881Speter hi; 1243251881Speter hi = apr_hash_next(hi)) 1244251881Speter { 1245251881Speter const svn_fs_dirent_t *s_entry; 1246251881Speter 1247262253Speter svn_pool_clear(iterpool); 1248251881Speter s_entry = svn__apr_hash_index_val(hi); 1249251881Speter 1250251881Speter if (svn_hash_gets(t_entries, s_entry->name) == NULL) 1251251881Speter { 1252251881Speter svn_revnum_t deleted_rev; 1253251881Speter 1254251881Speter if (s_entry->kind == svn_node_file 1255251881Speter && wc_depth < svn_depth_files) 1256251881Speter continue; 1257251881Speter 1258251881Speter if (s_entry->kind == svn_node_dir 1259251881Speter && (wc_depth < svn_depth_immediates 1260251881Speter || requested_depth == svn_depth_files)) 1261251881Speter continue; 1262251881Speter 1263251881Speter /* There is no corresponding target entry, so delete. */ 1264262253Speter e_fullpath = svn_relpath_join(e_path, s_entry->name, iterpool); 1265251881Speter SVN_ERR(svn_repos_deleted_rev(svn_fs_root_fs(b->t_root), 1266251881Speter svn_fspath__join(t_path, 1267251881Speter s_entry->name, 1268262253Speter iterpool), 1269251881Speter s_rev, b->t_rev, 1270262253Speter &deleted_rev, iterpool)); 1271251881Speter 1272251881Speter SVN_ERR(b->editor->delete_entry(e_fullpath, 1273251881Speter deleted_rev, 1274262253Speter dir_baton, iterpool)); 1275251881Speter } 1276251881Speter } 1277251881Speter } 1278251881Speter 1279251881Speter /* Loop over the dirents in the target. */ 1280262253Speter for (hi = apr_hash_first(subpool, t_entries); 1281262253Speter hi; 1282262253Speter hi = apr_hash_next(hi)) 1283251881Speter { 1284251881Speter const svn_fs_dirent_t *s_entry, *t_entry; 1285251881Speter 1286262253Speter svn_pool_clear(iterpool); 1287251881Speter t_entry = svn__apr_hash_index_val(hi); 1288251881Speter 1289251881Speter if (is_depth_upgrade(wc_depth, requested_depth, t_entry->kind)) 1290251881Speter { 1291251881Speter /* We're making the working copy deeper, pretend the source 1292251881Speter doesn't exist. */ 1293251881Speter s_entry = NULL; 1294251881Speter s_fullpath = NULL; 1295251881Speter } 1296251881Speter else 1297251881Speter { 1298251881Speter if (t_entry->kind == svn_node_file 1299251881Speter && requested_depth == svn_depth_unknown 1300251881Speter && wc_depth < svn_depth_files) 1301251881Speter continue; 1302251881Speter 1303251881Speter if (t_entry->kind == svn_node_dir 1304251881Speter && (wc_depth < svn_depth_immediates 1305251881Speter || requested_depth == svn_depth_files)) 1306251881Speter continue; 1307251881Speter 1308251881Speter /* Look for an entry with the same name 1309251881Speter in the source dirents. */ 1310251881Speter s_entry = s_entries ? 1311251881Speter svn_hash_gets(s_entries, t_entry->name) 1312251881Speter : NULL; 1313251881Speter s_fullpath = s_entry ? 1314262253Speter svn_fspath__join(s_path, t_entry->name, iterpool) : NULL; 1315251881Speter } 1316251881Speter 1317251881Speter /* Compose the report, editor, and target paths for this entry. */ 1318262253Speter e_fullpath = svn_relpath_join(e_path, t_entry->name, iterpool); 1319262253Speter t_fullpath = svn_fspath__join(t_path, t_entry->name, iterpool); 1320251881Speter 1321251881Speter SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, t_fullpath, 1322251881Speter t_entry, dir_baton, e_fullpath, NULL, 1323251881Speter DEPTH_BELOW_HERE(wc_depth), 1324251881Speter DEPTH_BELOW_HERE(requested_depth), 1325262253Speter iterpool)); 1326251881Speter } 1327251881Speter 1328251881Speter 1329251881Speter /* Destroy iteration subpool. */ 1330262253Speter svn_pool_destroy(iterpool); 1331251881Speter } 1332262253Speter 1333262253Speter svn_pool_destroy(subpool); 1334262253Speter 1335251881Speter return SVN_NO_ERROR; 1336251881Speter} 1337251881Speter 1338251881Speterstatic svn_error_t * 1339251881Speterdrive(report_baton_t *b, svn_revnum_t s_rev, path_info_t *info, 1340251881Speter apr_pool_t *pool) 1341251881Speter{ 1342251881Speter const char *t_anchor, *s_fullpath; 1343251881Speter svn_boolean_t allowed, info_is_set_path; 1344251881Speter svn_fs_root_t *s_root; 1345251881Speter const svn_fs_dirent_t *s_entry, *t_entry; 1346251881Speter void *root_baton; 1347251881Speter 1348251881Speter /* Compute the target path corresponding to the working copy anchor, 1349251881Speter and check its authorization. */ 1350251881Speter t_anchor = *b->s_operand ? svn_fspath__dirname(b->t_path, pool) : b->t_path; 1351251881Speter SVN_ERR(check_auth(b, &allowed, t_anchor, pool)); 1352251881Speter if (!allowed) 1353251881Speter return svn_error_create 1354251881Speter (SVN_ERR_AUTHZ_ROOT_UNREADABLE, NULL, 1355251881Speter _("Not authorized to open root of edit operation")); 1356251881Speter 1357251881Speter /* Collect information about the source and target nodes. */ 1358251881Speter s_fullpath = svn_fspath__join(b->fs_base, b->s_operand, pool); 1359251881Speter SVN_ERR(get_source_root(b, &s_root, s_rev)); 1360251881Speter SVN_ERR(fake_dirent(&s_entry, s_root, s_fullpath, pool)); 1361251881Speter SVN_ERR(fake_dirent(&t_entry, b->t_root, b->t_path, pool)); 1362251881Speter 1363251881Speter /* If the operand is a locally added file or directory, it won't 1364251881Speter exist in the source, so accept that. */ 1365251881Speter info_is_set_path = (SVN_IS_VALID_REVNUM(info->rev) && !info->link_path); 1366251881Speter if (info_is_set_path && !s_entry) 1367251881Speter s_fullpath = NULL; 1368251881Speter 1369251881Speter /* Check if the target path exists first. */ 1370251881Speter if (!*b->s_operand && !(t_entry)) 1371251881Speter return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL, 1372251881Speter _("Target path '%s' does not exist"), 1373251881Speter b->t_path); 1374251881Speter 1375251881Speter /* If the anchor is the operand, the source and target must be dirs. 1376251881Speter Check this before opening the root to avoid modifying the wc. */ 1377251881Speter else if (!*b->s_operand && (!s_entry || s_entry->kind != svn_node_dir 1378251881Speter || t_entry->kind != svn_node_dir)) 1379251881Speter return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, NULL, 1380251881Speter _("Cannot replace a directory from within")); 1381251881Speter 1382251881Speter SVN_ERR(b->editor->set_target_revision(b->edit_baton, b->t_rev, pool)); 1383251881Speter SVN_ERR(b->editor->open_root(b->edit_baton, s_rev, pool, &root_baton)); 1384251881Speter 1385251881Speter /* If the anchor is the operand, diff the two directories; otherwise 1386251881Speter update the operand within the anchor directory. */ 1387251881Speter if (!*b->s_operand) 1388251881Speter SVN_ERR(delta_dirs(b, s_rev, s_fullpath, b->t_path, root_baton, 1389251881Speter "", info->start_empty, info->depth, b->requested_depth, 1390251881Speter pool)); 1391251881Speter else 1392251881Speter SVN_ERR(update_entry(b, s_rev, s_fullpath, s_entry, b->t_path, 1393251881Speter t_entry, root_baton, b->s_operand, info, 1394251881Speter info->depth, b->requested_depth, pool)); 1395251881Speter 1396251881Speter return svn_error_trace(b->editor->close_directory(root_baton, pool)); 1397251881Speter} 1398251881Speter 1399251881Speter/* Initialize the baton fields for editor-driving, and drive the editor. */ 1400251881Speterstatic svn_error_t * 1401251881Speterfinish_report(report_baton_t *b, apr_pool_t *pool) 1402251881Speter{ 1403251881Speter path_info_t *info; 1404251881Speter apr_pool_t *subpool; 1405251881Speter svn_revnum_t s_rev; 1406251881Speter int i; 1407251881Speter 1408251881Speter /* Save our pool to manage the lookahead and fs_root cache with. */ 1409251881Speter b->pool = pool; 1410251881Speter 1411251881Speter /* Add the end marker. */ 1412251881Speter SVN_ERR(svn_spillbuf__reader_write(b->reader, "-", 1, pool)); 1413251881Speter 1414251881Speter /* Read the first pathinfo from the report and verify that it is a top-level 1415251881Speter set_path entry. */ 1416251881Speter SVN_ERR(read_path_info(&info, b->reader, pool)); 1417251881Speter if (!info || strcmp(info->path, b->s_operand) != 0 1418251881Speter || info->link_path || !SVN_IS_VALID_REVNUM(info->rev)) 1419251881Speter return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, 1420251881Speter _("Invalid report for top level of working copy")); 1421251881Speter s_rev = info->rev; 1422251881Speter 1423251881Speter /* Initialize the lookahead pathinfo. */ 1424251881Speter subpool = svn_pool_create(pool); 1425251881Speter SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); 1426251881Speter 1427251881Speter if (b->lookahead && strcmp(b->lookahead->path, b->s_operand) == 0) 1428251881Speter { 1429251881Speter /* If the operand of the wc operation is switched or deleted, 1430251881Speter then info above is just a place-holder, and the only thing we 1431251881Speter have to do is pass the revision it contains to open_root. 1432251881Speter The next pathinfo actually describes the target. */ 1433251881Speter if (!*b->s_operand) 1434251881Speter return svn_error_create(SVN_ERR_REPOS_BAD_REVISION_REPORT, NULL, 1435251881Speter _("Two top-level reports with no target")); 1436251881Speter /* If the client issued a set-path followed by a delete-path, we need 1437251881Speter to respect the depth set by the initial set-path. */ 1438251881Speter if (! SVN_IS_VALID_REVNUM(b->lookahead->rev)) 1439251881Speter { 1440251881Speter b->lookahead->depth = info->depth; 1441251881Speter } 1442251881Speter info = b->lookahead; 1443251881Speter SVN_ERR(read_path_info(&b->lookahead, b->reader, subpool)); 1444251881Speter } 1445251881Speter 1446251881Speter /* Open the target root and initialize the source root cache. */ 1447251881Speter SVN_ERR(svn_fs_revision_root(&b->t_root, b->repos->fs, b->t_rev, pool)); 1448251881Speter for (i = 0; i < NUM_CACHED_SOURCE_ROOTS; i++) 1449251881Speter b->s_roots[i] = NULL; 1450251881Speter 1451251881Speter { 1452251881Speter svn_error_t *err = svn_error_trace(drive(b, s_rev, info, pool)); 1453251881Speter 1454251881Speter if (err == SVN_NO_ERROR) 1455251881Speter return svn_error_trace(b->editor->close_edit(b->edit_baton, pool)); 1456251881Speter 1457251881Speter return svn_error_trace( 1458251881Speter svn_error_compose_create(err, 1459251881Speter b->editor->abort_edit(b->edit_baton, 1460251881Speter pool))); 1461251881Speter } 1462251881Speter} 1463251881Speter 1464251881Speter/* --- COLLECTING THE REPORT INFORMATION --- */ 1465251881Speter 1466251881Speter/* Record a report operation into the spill buffer. Return an error 1467251881Speter if DEPTH is svn_depth_unknown. */ 1468251881Speterstatic svn_error_t * 1469251881Speterwrite_path_info(report_baton_t *b, const char *path, const char *lpath, 1470251881Speter svn_revnum_t rev, svn_depth_t depth, 1471251881Speter svn_boolean_t start_empty, 1472251881Speter const char *lock_token, apr_pool_t *pool) 1473251881Speter{ 1474251881Speter const char *lrep, *rrep, *drep, *ltrep, *rep; 1475251881Speter 1476251881Speter /* Munge the path to be anchor-relative, so that we can use edit paths 1477251881Speter as report paths. */ 1478251881Speter path = svn_relpath_join(b->s_operand, path, pool); 1479251881Speter 1480251881Speter lrep = lpath ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s", 1481251881Speter strlen(lpath), lpath) : "-"; 1482251881Speter rrep = (SVN_IS_VALID_REVNUM(rev)) ? 1483251881Speter apr_psprintf(pool, "+%ld:", rev) : "-"; 1484251881Speter 1485251881Speter if (depth == svn_depth_exclude) 1486251881Speter drep = "+X"; 1487251881Speter else if (depth == svn_depth_empty) 1488251881Speter drep = "+E"; 1489251881Speter else if (depth == svn_depth_files) 1490251881Speter drep = "+F"; 1491251881Speter else if (depth == svn_depth_immediates) 1492251881Speter drep = "+M"; 1493251881Speter else if (depth == svn_depth_infinity) 1494251881Speter drep = "-"; 1495251881Speter else 1496251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1497251881Speter _("Unsupported report depth '%s'"), 1498251881Speter svn_depth_to_word(depth)); 1499251881Speter 1500251881Speter ltrep = lock_token ? apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s", 1501251881Speter strlen(lock_token), lock_token) : "-"; 1502251881Speter rep = apr_psprintf(pool, "+%" APR_SIZE_T_FMT ":%s%s%s%s%c%s", 1503251881Speter strlen(path), path, lrep, rrep, drep, 1504251881Speter start_empty ? '+' : '-', ltrep); 1505251881Speter return svn_error_trace( 1506251881Speter svn_spillbuf__reader_write(b->reader, rep, strlen(rep), pool)); 1507251881Speter} 1508251881Speter 1509251881Spetersvn_error_t * 1510251881Spetersvn_repos_set_path3(void *baton, const char *path, svn_revnum_t rev, 1511251881Speter svn_depth_t depth, svn_boolean_t start_empty, 1512251881Speter const char *lock_token, apr_pool_t *pool) 1513251881Speter{ 1514251881Speter return svn_error_trace( 1515251881Speter write_path_info(baton, path, NULL, rev, depth, start_empty, 1516251881Speter lock_token, pool)); 1517251881Speter} 1518251881Speter 1519251881Spetersvn_error_t * 1520251881Spetersvn_repos_link_path3(void *baton, const char *path, const char *link_path, 1521251881Speter svn_revnum_t rev, svn_depth_t depth, 1522251881Speter svn_boolean_t start_empty, 1523251881Speter const char *lock_token, apr_pool_t *pool) 1524251881Speter{ 1525251881Speter if (depth == svn_depth_exclude) 1526251881Speter return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 1527251881Speter _("Depth 'exclude' not supported for link")); 1528251881Speter 1529251881Speter return svn_error_trace( 1530251881Speter write_path_info(baton, path, link_path, rev, depth, 1531251881Speter start_empty, lock_token, pool)); 1532251881Speter} 1533251881Speter 1534251881Spetersvn_error_t * 1535251881Spetersvn_repos_delete_path(void *baton, const char *path, apr_pool_t *pool) 1536251881Speter{ 1537251881Speter /* We pass svn_depth_infinity because deletion of a path always 1538251881Speter deletes everything underneath it. */ 1539251881Speter return svn_error_trace( 1540251881Speter write_path_info(baton, path, NULL, SVN_INVALID_REVNUM, 1541251881Speter svn_depth_infinity, FALSE, NULL, pool)); 1542251881Speter} 1543251881Speter 1544251881Spetersvn_error_t * 1545251881Spetersvn_repos_finish_report(void *baton, apr_pool_t *pool) 1546251881Speter{ 1547251881Speter report_baton_t *b = baton; 1548251881Speter 1549251881Speter return svn_error_trace(finish_report(b, pool)); 1550251881Speter} 1551251881Speter 1552251881Spetersvn_error_t * 1553251881Spetersvn_repos_abort_report(void *baton, apr_pool_t *pool) 1554251881Speter{ 1555251881Speter return SVN_NO_ERROR; 1556251881Speter} 1557251881Speter 1558251881Speter/* --- BEGINNING THE REPORT --- */ 1559251881Speter 1560251881Speter 1561251881Spetersvn_error_t * 1562251881Spetersvn_repos_begin_report3(void **report_baton, 1563251881Speter svn_revnum_t revnum, 1564251881Speter svn_repos_t *repos, 1565251881Speter const char *fs_base, 1566251881Speter const char *s_operand, 1567251881Speter const char *switch_path, 1568251881Speter svn_boolean_t text_deltas, 1569251881Speter svn_depth_t depth, 1570251881Speter svn_boolean_t ignore_ancestry, 1571251881Speter svn_boolean_t send_copyfrom_args, 1572251881Speter const svn_delta_editor_t *editor, 1573251881Speter void *edit_baton, 1574251881Speter svn_repos_authz_func_t authz_read_func, 1575251881Speter void *authz_read_baton, 1576251881Speter apr_size_t zero_copy_limit, 1577251881Speter apr_pool_t *pool) 1578251881Speter{ 1579251881Speter report_baton_t *b; 1580251881Speter const char *uuid; 1581251881Speter 1582251881Speter if (depth == svn_depth_exclude) 1583251881Speter return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 1584251881Speter _("Request depth 'exclude' not supported")); 1585251881Speter 1586251881Speter SVN_ERR(svn_fs_get_uuid(repos->fs, &uuid, pool)); 1587251881Speter 1588251881Speter /* Build a reporter baton. Copy strings in case the caller doesn't 1589251881Speter keep track of them. */ 1590251881Speter b = apr_palloc(pool, sizeof(*b)); 1591251881Speter b->repos = repos; 1592251881Speter b->fs_base = svn_fspath__canonicalize(fs_base, pool); 1593251881Speter b->s_operand = apr_pstrdup(pool, s_operand); 1594251881Speter b->t_rev = revnum; 1595251881Speter b->t_path = switch_path ? svn_fspath__canonicalize(switch_path, pool) 1596251881Speter : svn_fspath__join(b->fs_base, s_operand, pool); 1597251881Speter b->text_deltas = text_deltas; 1598251881Speter b->zero_copy_limit = zero_copy_limit; 1599251881Speter b->requested_depth = depth; 1600251881Speter b->ignore_ancestry = ignore_ancestry; 1601251881Speter b->send_copyfrom_args = send_copyfrom_args; 1602251881Speter b->is_switch = (switch_path != NULL); 1603251881Speter b->editor = editor; 1604251881Speter b->edit_baton = edit_baton; 1605251881Speter b->authz_read_func = authz_read_func; 1606251881Speter b->authz_read_baton = authz_read_baton; 1607251881Speter b->revision_infos = apr_hash_make(pool); 1608251881Speter b->pool = pool; 1609251881Speter b->reader = svn_spillbuf__reader_create(1000 /* blocksize */, 1610251881Speter 1000000 /* maxsize */, 1611251881Speter pool); 1612251881Speter b->repos_uuid = svn_string_create(uuid, pool); 1613251881Speter 1614251881Speter /* Hand reporter back to client. */ 1615251881Speter *report_baton = b; 1616251881Speter return SVN_NO_ERROR; 1617251881Speter} 1618