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