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
30 struct 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
45 struct 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. */
70 static struct node_baton *
make_node_baton(void * edit_baton,svn_boolean_t filtered,int dir_depth,apr_pool_t * pool)71 make_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. */
88 static svn_boolean_t
okay_to_edit(struct edit_baton * eb,struct node_baton * pb,svn_node_kind_t kind)89 okay_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
137 static svn_error_t *
set_target_revision(void * edit_baton,svn_revnum_t target_revision,apr_pool_t * pool)138 set_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
149 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** root_baton)150 open_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
167 static svn_error_t *
delete_entry(const char * path,svn_revnum_t base_revision,void * parent_baton,apr_pool_t * pool)168 delete_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
191 static svn_error_t *
add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * pool,void ** child_baton)192 add_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
221 static svn_error_t *
open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** child_baton)222 open_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
249 static svn_error_t *
add_file(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * pool,void ** child_baton)250 add_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
278 static svn_error_t *
open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** child_baton)279 open_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
306 static svn_error_t *
apply_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)307 apply_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
331 static svn_error_t *
close_file(void * file_baton,const char * text_checksum,apr_pool_t * pool)332 close_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
347 static svn_error_t *
absent_file(const char * path,void * parent_baton,apr_pool_t * pool)348 absent_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
362 static svn_error_t *
close_directory(void * dir_baton,apr_pool_t * pool)363 close_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
376 static svn_error_t *
absent_directory(const char * path,void * parent_baton,apr_pool_t * pool)377 absent_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
392 static svn_error_t *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)393 change_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
409 static svn_error_t *
change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)410 change_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
426 static svn_error_t *
close_edit(void * edit_baton,apr_pool_t * pool)427 close_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
434 svn_error_t *
svn_delta_depth_filter_editor(const svn_delta_editor_t ** editor,void ** edit_baton,const svn_delta_editor_t * wrapped_editor,void * wrapped_edit_baton,svn_depth_t requested_depth,svn_boolean_t has_target,apr_pool_t * pool)435 svn_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