1/* 2 * depth_filter_editor.c -- provide a svn_delta_editor_t which wraps 3 * another editor and provides depth-based filtering 4 * 5 * ==================================================================== 6 * Licensed to the Apache Software Foundation (ASF) under one 7 * or more contributor license agreements. See the NOTICE file 8 * distributed with this work for additional information 9 * regarding copyright ownership. The ASF licenses this file 10 * to you under the Apache License, Version 2.0 (the 11 * "License"); you may not use this file except in compliance 12 * with the License. You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, 17 * software distributed under the License is distributed on an 18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 * KIND, either express or implied. See the License for the 20 * specific language governing permissions and limitations 21 * under the License. 22 * ==================================================================== 23 */ 24 25#include "svn_delta.h" 26 27 28/*** Batons, and the Toys That Create Them ***/ 29 30struct edit_baton 31{ 32 /* The editor/baton we're wrapping. */ 33 const svn_delta_editor_t *wrapped_editor; 34 void *wrapped_edit_baton; 35 36 /* The depth to which we are limiting the drive of the wrapped 37 editor/baton. */ 38 svn_depth_t requested_depth; 39 40 /* Does the wrapped editor/baton have an explicit target (in the 41 anchor/target sense of the word)? */ 42 svn_boolean_t has_target; 43}; 44 45struct node_baton 46{ 47 /* TRUE iff this node was filtered out -- that is, not allowed to 48 pass through to the wrapped editor -- by virtue of not appearing 49 at a depth in the tree that was "inside" the requested depth. Of 50 course, any children of this node will be deeper still, and so 51 will also be filtered out for the same reason. */ 52 svn_boolean_t filtered; 53 54 /* Pointer to the edit_baton. */ 55 void *edit_baton; 56 57 /* The real node baton we're wrapping. May be a directory or file 58 baton; we don't care. */ 59 void *wrapped_baton; 60 61 /* The calculated depth (in terms of counted, stacked, integral 62 deepnesses) of this node. If the node is a directory, this value 63 is 1 greater than the value of the same on its parent directory; 64 if a file, it is equal to its parent directory's depth value. */ 65 int dir_depth; 66}; 67 68/* Allocate and return a new node_baton structure, populated via the 69 the input to this helper function. */ 70static struct node_baton * 71make_node_baton(void *edit_baton, 72 svn_boolean_t filtered, 73 int dir_depth, 74 apr_pool_t *pool) 75{ 76 struct node_baton *b = apr_palloc(pool, sizeof(*b)); 77 b->edit_baton = edit_baton; 78 b->wrapped_baton = NULL; 79 b->filtered = filtered; 80 b->dir_depth = dir_depth; 81 return b; 82} 83 84/* Return TRUE iff changes to immediate children of the directory 85 identified by PB, when those children are of node kind KIND, are 86 allowed by the requested depth which this editor is trying to 87 preserve. EB is the edit baton. */ 88static svn_boolean_t 89okay_to_edit(struct edit_baton *eb, 90 struct node_baton *pb, 91 svn_node_kind_t kind) 92{ 93 int effective_depth; 94 95 /* If we've already filter out the parent directory, we necessarily 96 are filtering out its children, too. */ 97 if (pb->filtered) 98 return FALSE; 99 100 /* Calculate the effective depth of the parent directory. 101 102 NOTE: "Depth" in this sense is not the same as the Subversion 103 magic depth keywords. Here, we're talking about a literal, 104 integral, stacked depth of directories. 105 106 The root of the edit is generally depth=1, subdirectories thereof 107 depth=2, and so on. But if we have an edit target -- which means 108 that the real target of the edit operation isn't the root 109 directory, but is instead some immediate child thereof -- we have 110 to adjust our calculated effected depth such that the target 111 itself is depth=1 (as are its siblings, which we trust aren't 112 present in the edit at all), immediate subdirectories thereof are 113 depth=2, and so on. 114 */ 115 effective_depth = pb->dir_depth - (eb->has_target ? 1 : 0); 116 switch (eb->requested_depth) 117 { 118 case svn_depth_empty: 119 return (effective_depth <= 0); 120 case svn_depth_files: 121 return ((effective_depth <= 0) 122 || (kind == svn_node_file && effective_depth == 1)); 123 case svn_depth_immediates: 124 return (effective_depth <= 1); 125 case svn_depth_unknown: 126 case svn_depth_exclude: 127 case svn_depth_infinity: 128 /* Shouldn't reach; see svn_delta_depth_filter_editor() */ 129 default: 130 SVN_ERR_MALFUNCTION_NO_RETURN(); 131 } 132} 133 134 135/*** Editor Functions ***/ 136 137static svn_error_t * 138set_target_revision(void *edit_baton, 139 svn_revnum_t target_revision, 140 apr_pool_t *pool) 141{ 142 struct edit_baton *eb = edit_baton; 143 144 /* Nothing depth-y to filter here. */ 145 return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, 146 target_revision, pool); 147} 148 149static svn_error_t * 150open_root(void *edit_baton, 151 svn_revnum_t base_revision, 152 apr_pool_t *pool, 153 void **root_baton) 154{ 155 struct edit_baton *eb = edit_baton; 156 struct node_baton *b; 157 158 /* The root node always gets through cleanly. */ 159 b = make_node_baton(edit_baton, FALSE, 1, pool); 160 SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision, 161 pool, &b->wrapped_baton)); 162 163 *root_baton = b; 164 return SVN_NO_ERROR; 165} 166 167static svn_error_t * 168delete_entry(const char *path, 169 svn_revnum_t base_revision, 170 void *parent_baton, 171 apr_pool_t *pool) 172{ 173 struct node_baton *pb = parent_baton; 174 struct edit_baton *eb = pb->edit_baton; 175 176 /* ### FIXME: We don't know the type of the entry, which ordinarily 177 doesn't matter, but is a key (*the* key, in fact) distinction 178 between depth "files" and depths "immediates". If the server is 179 telling us to delete a subdirectory and our requested depth was 180 "immediates", that's fine; if our requested depth was "files", 181 though, this deletion shouldn't survive filtering. For now, 182 we'll claim to our helper function that the to-be-deleted thing 183 is a file because that's the conservative route to take. */ 184 if (okay_to_edit(eb, pb, svn_node_file)) 185 SVN_ERR(eb->wrapped_editor->delete_entry(path, base_revision, 186 pb->wrapped_baton, pool)); 187 188 return SVN_NO_ERROR; 189} 190 191static svn_error_t * 192add_directory(const char *path, 193 void *parent_baton, 194 const char *copyfrom_path, 195 svn_revnum_t copyfrom_revision, 196 apr_pool_t *pool, 197 void **child_baton) 198{ 199 struct node_baton *pb = parent_baton; 200 struct edit_baton *eb = pb->edit_baton; 201 struct node_baton *b = NULL; 202 203 /* Check for sufficient depth. */ 204 if (okay_to_edit(eb, pb, svn_node_dir)) 205 { 206 b = make_node_baton(eb, FALSE, pb->dir_depth + 1, pool); 207 SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_baton, 208 copyfrom_path, 209 copyfrom_revision, 210 pool, &b->wrapped_baton)); 211 } 212 else 213 { 214 b = make_node_baton(eb, TRUE, pb->dir_depth + 1, pool); 215 } 216 217 *child_baton = b; 218 return SVN_NO_ERROR; 219} 220 221static svn_error_t * 222open_directory(const char *path, 223 void *parent_baton, 224 svn_revnum_t base_revision, 225 apr_pool_t *pool, 226 void **child_baton) 227{ 228 struct node_baton *pb = parent_baton; 229 struct edit_baton *eb = pb->edit_baton; 230 struct node_baton *b; 231 232 /* Check for sufficient depth. */ 233 if (okay_to_edit(eb, pb, svn_node_dir)) 234 { 235 b = make_node_baton(eb, FALSE, pb->dir_depth + 1, pool); 236 SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton, 237 base_revision, pool, 238 &b->wrapped_baton)); 239 } 240 else 241 { 242 b = make_node_baton(eb, TRUE, pb->dir_depth + 1, pool); 243 } 244 245 *child_baton = b; 246 return SVN_NO_ERROR; 247} 248 249static svn_error_t * 250add_file(const char *path, 251 void *parent_baton, 252 const char *copyfrom_path, 253 svn_revnum_t copyfrom_revision, 254 apr_pool_t *pool, 255 void **child_baton) 256{ 257 struct node_baton *pb = parent_baton; 258 struct edit_baton *eb = pb->edit_baton; 259 struct node_baton *b = NULL; 260 261 /* Check for sufficient depth. */ 262 if (okay_to_edit(eb, pb, svn_node_file)) 263 { 264 b = make_node_baton(eb, FALSE, pb->dir_depth, pool); 265 SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_baton, 266 copyfrom_path, copyfrom_revision, 267 pool, &b->wrapped_baton)); 268 } 269 else 270 { 271 b = make_node_baton(eb, TRUE, pb->dir_depth, pool); 272 } 273 274 *child_baton = b; 275 return SVN_NO_ERROR; 276} 277 278static svn_error_t * 279open_file(const char *path, 280 void *parent_baton, 281 svn_revnum_t base_revision, 282 apr_pool_t *pool, 283 void **child_baton) 284{ 285 struct node_baton *pb = parent_baton; 286 struct edit_baton *eb = pb->edit_baton; 287 struct node_baton *b; 288 289 /* Check for sufficient depth. */ 290 if (okay_to_edit(eb, pb, svn_node_file)) 291 { 292 b = make_node_baton(eb, FALSE, pb->dir_depth, pool); 293 SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_baton, 294 base_revision, pool, 295 &b->wrapped_baton)); 296 } 297 else 298 { 299 b = make_node_baton(eb, TRUE, pb->dir_depth, pool); 300 } 301 302 *child_baton = b; 303 return SVN_NO_ERROR; 304} 305 306static svn_error_t * 307apply_textdelta(void *file_baton, 308 const char *base_checksum, 309 apr_pool_t *pool, 310 svn_txdelta_window_handler_t *handler, 311 void **handler_baton) 312{ 313 struct node_baton *fb = file_baton; 314 struct edit_baton *eb = fb->edit_baton; 315 316 /* For filtered files, we just consume the textdelta. */ 317 if (fb->filtered) 318 { 319 *handler = svn_delta_noop_window_handler; 320 *handler_baton = NULL; 321 } 322 else 323 { 324 SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_baton, 325 base_checksum, pool, 326 handler, handler_baton)); 327 } 328 return SVN_NO_ERROR; 329} 330 331static svn_error_t * 332close_file(void *file_baton, 333 const char *text_checksum, 334 apr_pool_t *pool) 335{ 336 struct node_baton *fb = file_baton; 337 struct edit_baton *eb = fb->edit_baton; 338 339 /* Don't close filtered files. */ 340 if (! fb->filtered) 341 SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_baton, 342 text_checksum, pool)); 343 344 return SVN_NO_ERROR; 345} 346 347static svn_error_t * 348absent_file(const char *path, 349 void *parent_baton, 350 apr_pool_t *pool) 351{ 352 struct node_baton *pb = parent_baton; 353 struct edit_baton *eb = pb->edit_baton; 354 355 /* Don't report absent items in filtered directories. */ 356 if (! pb->filtered) 357 SVN_ERR(eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool)); 358 359 return SVN_NO_ERROR; 360} 361 362static svn_error_t * 363close_directory(void *dir_baton, 364 apr_pool_t *pool) 365{ 366 struct node_baton *db = dir_baton; 367 struct edit_baton *eb = db->edit_baton; 368 369 /* Don't close filtered directories. */ 370 if (! db->filtered) 371 SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_baton, pool)); 372 373 return SVN_NO_ERROR; 374} 375 376static svn_error_t * 377absent_directory(const char *path, 378 void *parent_baton, 379 apr_pool_t *pool) 380{ 381 struct node_baton *pb = parent_baton; 382 struct edit_baton *eb = pb->edit_baton; 383 384 /* Don't report absent items in filtered directories. */ 385 if (! pb->filtered) 386 SVN_ERR(eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, 387 pool)); 388 389 return SVN_NO_ERROR; 390} 391 392static svn_error_t * 393change_file_prop(void *file_baton, 394 const char *name, 395 const svn_string_t *value, 396 apr_pool_t *pool) 397{ 398 struct node_baton *fb = file_baton; 399 struct edit_baton *eb = fb->edit_baton; 400 401 /* No propchanges on filtered files. */ 402 if (! fb->filtered) 403 SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_baton, 404 name, value, pool)); 405 406 return SVN_NO_ERROR; 407} 408 409static svn_error_t * 410change_dir_prop(void *dir_baton, 411 const char *name, 412 const svn_string_t *value, 413 apr_pool_t *pool) 414{ 415 struct node_baton *db = dir_baton; 416 struct edit_baton *eb = db->edit_baton; 417 418 /* No propchanges on filtered nodes. */ 419 if (! db->filtered) 420 SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_baton, 421 name, value, pool)); 422 423 return SVN_NO_ERROR; 424} 425 426static svn_error_t * 427close_edit(void *edit_baton, 428 apr_pool_t *pool) 429{ 430 struct edit_baton *eb = edit_baton; 431 return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); 432} 433 434svn_error_t * 435svn_delta_depth_filter_editor(const svn_delta_editor_t **editor, 436 void **edit_baton, 437 const svn_delta_editor_t *wrapped_editor, 438 void *wrapped_edit_baton, 439 svn_depth_t requested_depth, 440 svn_boolean_t has_target, 441 apr_pool_t *pool) 442{ 443 svn_delta_editor_t *depth_filter_editor; 444 struct edit_baton *eb; 445 446 /* Easy out: if the caller wants infinite depth, there's nothing to 447 filter, so just return the editor we were supposed to wrap. And 448 if they've asked for an unknown depth, we can't possibly know 449 what that means, so why bother? */ 450 if ((requested_depth == svn_depth_unknown) 451 || (requested_depth == svn_depth_infinity)) 452 { 453 *editor = wrapped_editor; 454 *edit_baton = wrapped_edit_baton; 455 return SVN_NO_ERROR; 456 } 457 458 depth_filter_editor = svn_delta_default_editor(pool); 459 depth_filter_editor->set_target_revision = set_target_revision; 460 depth_filter_editor->open_root = open_root; 461 depth_filter_editor->delete_entry = delete_entry; 462 depth_filter_editor->add_directory = add_directory; 463 depth_filter_editor->open_directory = open_directory; 464 depth_filter_editor->change_dir_prop = change_dir_prop; 465 depth_filter_editor->close_directory = close_directory; 466 depth_filter_editor->absent_directory = absent_directory; 467 depth_filter_editor->add_file = add_file; 468 depth_filter_editor->open_file = open_file; 469 depth_filter_editor->apply_textdelta = apply_textdelta; 470 depth_filter_editor->change_file_prop = change_file_prop; 471 depth_filter_editor->close_file = close_file; 472 depth_filter_editor->absent_file = absent_file; 473 depth_filter_editor->close_edit = close_edit; 474 475 eb = apr_palloc(pool, sizeof(*eb)); 476 eb->wrapped_editor = wrapped_editor; 477 eb->wrapped_edit_baton = wrapped_edit_baton; 478 eb->has_target = has_target; 479 eb->requested_depth = requested_depth; 480 481 *editor = depth_filter_editor; 482 *edit_baton = eb; 483 484 return SVN_NO_ERROR; 485} 486