2 * ambient_depth_filter_editor.c -- provide a svn_delta_editor_t which wraps
3 * another editor and provides
4 * *ambient* depth-based filtering
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements. See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership. The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License. You may obtain a copy of the License at
15 * http://www.apache.org/licenses/LICENSE-2.0
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied. See the License for the
21 * specific language governing permissions and limitations
23 * ====================================================================
26 #include "svn_delta.h"
28 #include "svn_dirent_uri.h"
34 Notes on the general depth-filtering strategy.
35 ==============================================
37 When a depth-aware (>= 1.5) client pulls an update from a
38 non-depth-aware server, the server may send back too much data,
39 because it doesn't hear what the client tells it about the
40 "requested depth" of the update (the "foo" in "--depth=foo"), nor
41 about the "ambient depth" of each working copy directory.
43 For example, suppose a 1.5 client does this against a 1.4 server:
45 $ svn co --depth=empty -rSOME_OLD_REV http://url/repos/blah/ wc
49 In the initial checkout, the requested depth is 'empty', so the
50 depth-filtering editor (see libsvn_delta/depth_filter_editor.c)
51 that wraps the main update editor transparently filters out all
54 In the 'svn up', the requested depth is unspecified, meaning that
55 the ambient depth(s) of the working copy should be preserved.
56 Since there's only one directory, and its depth is 'empty',
57 clearly we should filter out or render no-ops all editor calls
58 after open_root(), except maybe for change_dir_prop() on the
59 top-level directory. (Note that the server will have stuff to
60 send down, because we checked out at an old revision in the first
61 place, to set up this scenario.)
63 The depth-filtering editor won't help us here. It only filters
64 based on the requested depth, it never looks in the working copy
65 to get ambient depths. So the update editor itself will have to
66 filter out the unwanted calls -- or better yet, it will have to
67 be wrapped in a filtering editor that does the job.
69 This is that filtering editor.
71 Most of the work is done at the moment of baton construction.
72 When a file or dir is opened, we create its baton with the
73 appropriate ambient depth, either taking the depth directly from
74 the corresponding working copy object (if available), or from its
75 parent baton. In the latter case, we don't just copy the parent
76 baton's depth, but rather use it to choose the correct depth for
77 this child. The usual depth demotion rules apply, with the
78 additional stipulation that as soon as we find a subtree is not
79 present at all, due to being omitted for depth reasons, we set the
80 ambiently_excluded flag in its baton, which signals that
81 all descendant batons should be ignored.
82 (In fact, we may just re-use the parent baton, since none of the
83 other fields will be used anyway.)
85 See issues #2842 and #2897 for more.
89 /*** Batons, and the Toys That Create Them ***/
93 const svn_delta_editor_t *wrapped_editor;
94 void *wrapped_edit_baton;
96 const char *anchor_abspath;
102 svn_boolean_t ambiently_excluded;
103 struct edit_baton *edit_baton;
109 svn_boolean_t ambiently_excluded;
110 svn_depth_t ambient_depth;
111 struct edit_baton *edit_baton;
116 /* Fetch the STATUS, KIND and DEPTH of the base node at LOCAL_ABSPATH.
117 * If there is no such base node, report 'normal', 'unknown' and 'unknown'
120 * STATUS and/or DEPTH may be NULL if not wanted; KIND must not be NULL.
123 ambient_read_info(svn_wc__db_status_t *status,
124 svn_node_kind_t *kind,
127 const char *local_abspath,
128 apr_pool_t *scratch_pool)
131 SVN_ERR_ASSERT(kind != NULL);
133 err = svn_wc__db_base_get_info(status, kind, NULL, NULL, NULL, NULL,
134 NULL, NULL, NULL, depth, NULL, NULL,
135 NULL, NULL, NULL, NULL,
137 scratch_pool, scratch_pool);
139 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
141 svn_error_clear(err);
143 *kind = svn_node_unknown;
145 *status = svn_wc__db_status_normal;
147 *depth = svn_depth_unknown;
159 make_dir_baton(struct dir_baton **d_p,
161 struct edit_baton *eb,
162 struct dir_baton *pb,
168 SVN_ERR_ASSERT(path || (! pb));
170 if (pb && pb->ambiently_excluded)
172 /* Just re-use the parent baton, since the only field that
173 matters is ambiently_excluded. */
178 /* Okay, no easy out, so allocate and initialize a dir baton. */
179 d = apr_pcalloc(pool, sizeof(*d));
182 d->abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
184 d->abspath = apr_pstrdup(pool, eb->anchor_abspath);
186 /* The svn_depth_unknown means that: 1) pb is the anchor; 2) there
187 is an non-null target, for which we are preparing the baton.
188 This enables explicitly pull in the target. */
189 if (pb && pb->ambient_depth != svn_depth_unknown)
191 svn_boolean_t exclude;
192 svn_wc__db_status_t status;
193 svn_node_kind_t kind;
194 svn_boolean_t exists = TRUE;
198 SVN_ERR(ambient_read_info(&status, &kind, NULL,
199 eb->db, d->abspath, pool));
203 status = svn_wc__db_status_not_present;
204 kind = svn_node_unknown;
207 exists = (kind != svn_node_unknown);
209 if (pb->ambient_depth == svn_depth_empty
210 || pb->ambient_depth == svn_depth_files)
212 /* This is not a depth upgrade, and the parent directory is
213 depth==empty or depth==files. So if the parent doesn't
214 already have an entry for the new dir, then the parent
215 doesn't want the new dir at all, thus we should initialize
216 it with ambiently_excluded=TRUE. */
221 /* If the parent expect all children by default, only exclude
222 it whenever it is explicitly marked as exclude. */
223 exclude = exists && (status == svn_wc__db_status_excluded);
227 d->ambiently_excluded = TRUE;
234 /* We'll initialize this differently in add_directory and
236 d->ambient_depth = svn_depth_unknown;
244 make_file_baton(struct file_baton **f_p,
245 struct dir_baton *pb,
250 struct file_baton *f = apr_pcalloc(pool, sizeof(*f));
251 struct edit_baton *eb = pb->edit_baton;
252 svn_wc__db_status_t status;
253 svn_node_kind_t kind;
256 SVN_ERR_ASSERT(path);
258 if (pb->ambiently_excluded)
260 f->ambiently_excluded = TRUE;
265 abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
269 SVN_ERR(ambient_read_info(&status, &kind, NULL,
270 eb->db, abspath, pool));
274 status = svn_wc__db_status_not_present;
275 kind = svn_node_unknown;
278 if (pb->ambient_depth == svn_depth_empty)
280 /* This is not a depth upgrade, and the parent directory is
281 depth==empty. So if the parent doesn't
282 already have an entry for the file, then the parent
283 doesn't want to hear about the file at all. */
285 if (status == svn_wc__db_status_not_present
286 || status == svn_wc__db_status_server_excluded
287 || status == svn_wc__db_status_excluded
288 || kind == svn_node_unknown)
290 f->ambiently_excluded = TRUE;
296 /* If pb->ambient_depth == svn_depth_unknown we are pulling
298 if (pb->ambient_depth != svn_depth_unknown
299 && status == svn_wc__db_status_excluded)
301 f->ambiently_excluded = TRUE;
306 f->edit_baton = pb->edit_baton;
313 /*** Editor Functions ***/
315 /* An svn_delta_editor_t function. */
317 set_target_revision(void *edit_baton,
318 svn_revnum_t target_revision,
321 struct edit_baton *eb = edit_baton;
323 /* Nothing depth-y to filter here. */
324 return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
325 target_revision, pool);
328 /* An svn_delta_editor_t function. */
330 open_root(void *edit_baton,
331 svn_revnum_t base_revision,
335 struct edit_baton *eb = edit_baton;
338 SVN_ERR(make_dir_baton(&b, NULL, eb, NULL, FALSE, pool));
341 if (b->ambiently_excluded)
346 /* For an update with a NULL target, this is equivalent to open_dir(): */
347 svn_node_kind_t kind;
348 svn_wc__db_status_t status;
351 /* Read the depth from the entry. */
352 SVN_ERR(ambient_read_info(&status, &kind, &depth,
353 eb->db, eb->anchor_abspath,
356 if (kind != svn_node_unknown
357 && status != svn_wc__db_status_not_present
358 && status != svn_wc__db_status_excluded
359 && status != svn_wc__db_status_server_excluded)
361 b->ambient_depth = depth;
365 return eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision,
366 pool, &b->wrapped_baton);
369 /* An svn_delta_editor_t function. */
371 delete_entry(const char *path,
372 svn_revnum_t base_revision,
376 struct dir_baton *pb = parent_baton;
377 struct edit_baton *eb = pb->edit_baton;
379 if (pb->ambiently_excluded)
382 if (pb->ambient_depth < svn_depth_immediates)
384 /* If the entry we want to delete doesn't exist, that's OK.
385 It's probably an old server that doesn't understand
387 svn_node_kind_t kind;
388 svn_wc__db_status_t status;
391 abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
393 SVN_ERR(ambient_read_info(&status, &kind, NULL,
394 eb->db, abspath, pool));
396 if (kind == svn_node_unknown
397 || status == svn_wc__db_status_not_present
398 || status == svn_wc__db_status_excluded
399 || status == svn_wc__db_status_server_excluded)
403 return eb->wrapped_editor->delete_entry(path, base_revision,
404 pb->wrapped_baton, pool);
407 /* An svn_delta_editor_t function. */
409 add_directory(const char *path,
411 const char *copyfrom_path,
412 svn_revnum_t copyfrom_revision,
416 struct dir_baton *pb = parent_baton;
417 struct edit_baton *eb = pb->edit_baton;
418 struct dir_baton *b = NULL;
420 SVN_ERR(make_dir_baton(&b, path, eb, pb, TRUE, pool));
423 if (b->ambiently_excluded)
426 /* It's not excluded, so what should we treat the ambient depth as
428 if (strcmp(eb->target, path) == 0)
430 /* The target of the edit is being added, so make it
432 b->ambient_depth = svn_depth_infinity;
434 else if (pb->ambient_depth == svn_depth_immediates)
436 b->ambient_depth = svn_depth_empty;
440 /* There may be a requested depth < svn_depth_infinity, but
441 that's okay, libsvn_delta/depth_filter_editor.c will filter
442 further calls out for us anyway, and the update_editor will
443 do the right thing when it creates the directory. */
444 b->ambient_depth = svn_depth_infinity;
447 return eb->wrapped_editor->add_directory(path, pb->wrapped_baton,
450 pool, &b->wrapped_baton);
453 /* An svn_delta_editor_t function. */
455 open_directory(const char *path,
457 svn_revnum_t base_revision,
461 struct dir_baton *pb = parent_baton;
462 struct edit_baton *eb = pb->edit_baton;
464 const char *local_abspath;
465 svn_node_kind_t kind;
466 svn_wc__db_status_t status;
469 SVN_ERR(make_dir_baton(&b, path, eb, pb, FALSE, pool));
472 if (b->ambiently_excluded)
475 SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton,
478 /* Note that for the update editor, the open_directory above will
479 flush the logs of pb's directory, which might be important for
480 this svn_wc_entry call. */
482 local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool);
484 SVN_ERR(ambient_read_info(&status, &kind, &depth,
485 eb->db, local_abspath, pool));
487 if (kind != svn_node_unknown
488 && status != svn_wc__db_status_not_present
489 && status != svn_wc__db_status_excluded
490 && status != svn_wc__db_status_server_excluded)
492 b->ambient_depth = depth;
498 /* An svn_delta_editor_t function. */
500 add_file(const char *path,
502 const char *copyfrom_path,
503 svn_revnum_t copyfrom_revision,
507 struct dir_baton *pb = parent_baton;
508 struct edit_baton *eb = pb->edit_baton;
509 struct file_baton *b = NULL;
511 SVN_ERR(make_file_baton(&b, pb, path, TRUE, pool));
514 if (b->ambiently_excluded)
517 return eb->wrapped_editor->add_file(path, pb->wrapped_baton,
518 copyfrom_path, copyfrom_revision,
519 pool, &b->wrapped_baton);
522 /* An svn_delta_editor_t function. */
524 open_file(const char *path,
526 svn_revnum_t base_revision,
530 struct dir_baton *pb = parent_baton;
531 struct edit_baton *eb = pb->edit_baton;
532 struct file_baton *b;
534 SVN_ERR(make_file_baton(&b, pb, path, FALSE, pool));
536 if (b->ambiently_excluded)
539 return eb->wrapped_editor->open_file(path, pb->wrapped_baton,
544 /* An svn_delta_editor_t function. */
546 apply_textdelta(void *file_baton,
547 const char *base_checksum,
549 svn_txdelta_window_handler_t *handler,
550 void **handler_baton)
552 struct file_baton *fb = file_baton;
553 struct edit_baton *eb = fb->edit_baton;
555 /* For filtered files, we just consume the textdelta. */
556 if (fb->ambiently_excluded)
558 *handler = svn_delta_noop_window_handler;
559 *handler_baton = NULL;
563 return eb->wrapped_editor->apply_textdelta(fb->wrapped_baton,
565 handler, handler_baton);
568 /* An svn_delta_editor_t function. */
570 close_file(void *file_baton,
571 const char *text_checksum,
574 struct file_baton *fb = file_baton;
575 struct edit_baton *eb = fb->edit_baton;
577 if (fb->ambiently_excluded)
580 return eb->wrapped_editor->close_file(fb->wrapped_baton,
581 text_checksum, pool);
584 /* An svn_delta_editor_t function. */
586 absent_file(const char *path,
590 struct dir_baton *pb = parent_baton;
591 struct edit_baton *eb = pb->edit_baton;
593 if (pb->ambiently_excluded)
596 return eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool);
599 /* An svn_delta_editor_t function. */
601 close_directory(void *dir_baton,
604 struct dir_baton *db = dir_baton;
605 struct edit_baton *eb = db->edit_baton;
607 if (db->ambiently_excluded)
610 return eb->wrapped_editor->close_directory(db->wrapped_baton, pool);
613 /* An svn_delta_editor_t function. */
615 absent_directory(const char *path,
619 struct dir_baton *pb = parent_baton;
620 struct edit_baton *eb = pb->edit_baton;
622 /* Don't report absent items in filtered directories. */
623 if (pb->ambiently_excluded)
626 return eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, pool);
629 /* An svn_delta_editor_t function. */
631 change_file_prop(void *file_baton,
633 const svn_string_t *value,
636 struct file_baton *fb = file_baton;
637 struct edit_baton *eb = fb->edit_baton;
639 if (fb->ambiently_excluded)
642 return eb->wrapped_editor->change_file_prop(fb->wrapped_baton,
646 /* An svn_delta_editor_t function. */
648 change_dir_prop(void *dir_baton,
650 const svn_string_t *value,
653 struct dir_baton *db = dir_baton;
654 struct edit_baton *eb = db->edit_baton;
656 if (db->ambiently_excluded)
659 return eb->wrapped_editor->change_dir_prop(db->wrapped_baton,
663 /* An svn_delta_editor_t function. */
665 close_edit(void *edit_baton,
668 struct edit_baton *eb = edit_baton;
669 return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
673 svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor,
676 const char *anchor_abspath,
678 const svn_delta_editor_t *wrapped_editor,
679 void *wrapped_edit_baton,
680 apr_pool_t *result_pool)
682 svn_delta_editor_t *depth_filter_editor;
683 struct edit_baton *eb;
685 SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath));
687 depth_filter_editor = svn_delta_default_editor(result_pool);
688 depth_filter_editor->set_target_revision = set_target_revision;
689 depth_filter_editor->open_root = open_root;
690 depth_filter_editor->delete_entry = delete_entry;
691 depth_filter_editor->add_directory = add_directory;
692 depth_filter_editor->open_directory = open_directory;
693 depth_filter_editor->change_dir_prop = change_dir_prop;
694 depth_filter_editor->close_directory = close_directory;
695 depth_filter_editor->absent_directory = absent_directory;
696 depth_filter_editor->add_file = add_file;
697 depth_filter_editor->open_file = open_file;
698 depth_filter_editor->apply_textdelta = apply_textdelta;
699 depth_filter_editor->change_file_prop = change_file_prop;
700 depth_filter_editor->close_file = close_file;
701 depth_filter_editor->absent_file = absent_file;
702 depth_filter_editor->close_edit = close_edit;
704 eb = apr_pcalloc(result_pool, sizeof(*eb));
705 eb->wrapped_editor = wrapped_editor;
706 eb->wrapped_edit_baton = wrapped_edit_baton;
708 eb->anchor_abspath = anchor_abspath;
711 *editor = depth_filter_editor;