/* * ambient_depth_filter_editor.c -- provide a svn_delta_editor_t which wraps * another editor and provides * *ambient* depth-based filtering * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include "svn_delta.h" #include "svn_wc.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "wc.h" /* Notes on the general depth-filtering strategy. ============================================== When a depth-aware (>= 1.5) client pulls an update from a non-depth-aware server, the server may send back too much data, because it doesn't hear what the client tells it about the "requested depth" of the update (the "foo" in "--depth=foo"), nor about the "ambient depth" of each working copy directory. For example, suppose a 1.5 client does this against a 1.4 server: $ svn co --depth=empty -rSOME_OLD_REV http://url/repos/blah/ wc $ cd wc $ svn up In the initial checkout, the requested depth is 'empty', so the depth-filtering editor (see libsvn_delta/depth_filter_editor.c) that wraps the main update editor transparently filters out all the unwanted calls. In the 'svn up', the requested depth is unspecified, meaning that the ambient depth(s) of the working copy should be preserved. Since there's only one directory, and its depth is 'empty', clearly we should filter out or render no-ops all editor calls after open_root(), except maybe for change_dir_prop() on the top-level directory. (Note that the server will have stuff to send down, because we checked out at an old revision in the first place, to set up this scenario.) The depth-filtering editor won't help us here. It only filters based on the requested depth, it never looks in the working copy to get ambient depths. So the update editor itself will have to filter out the unwanted calls -- or better yet, it will have to be wrapped in a filtering editor that does the job. This is that filtering editor. Most of the work is done at the moment of baton construction. When a file or dir is opened, we create its baton with the appropriate ambient depth, either taking the depth directly from the corresponding working copy object (if available), or from its parent baton. In the latter case, we don't just copy the parent baton's depth, but rather use it to choose the correct depth for this child. The usual depth demotion rules apply, with the additional stipulation that as soon as we find a subtree is not present at all, due to being omitted for depth reasons, we set the ambiently_excluded flag in its baton, which signals that all descendant batons should be ignored. (In fact, we may just re-use the parent baton, since none of the other fields will be used anyway.) See issues #2842 and #2897 for more. */ /*** Batons, and the Toys That Create Them ***/ struct edit_baton { const svn_delta_editor_t *wrapped_editor; void *wrapped_edit_baton; svn_wc__db_t *db; const char *anchor_abspath; const char *target; }; struct file_baton { svn_boolean_t ambiently_excluded; struct edit_baton *edit_baton; void *wrapped_baton; }; struct dir_baton { svn_boolean_t ambiently_excluded; svn_depth_t ambient_depth; struct edit_baton *edit_baton; const char *abspath; void *wrapped_baton; }; /* Fetch the STATUS, KIND and DEPTH of the base node at LOCAL_ABSPATH. * If there is no such base node, report 'normal', 'unknown' and 'unknown' * respectively. * * STATUS and/or DEPTH may be NULL if not wanted; KIND must not be NULL. */ static svn_error_t * ambient_read_info(svn_wc__db_status_t *status, svn_node_kind_t *kind, svn_depth_t *depth, svn_wc__db_t *db, const char *local_abspath, apr_pool_t *scratch_pool) { svn_error_t *err; SVN_ERR_ASSERT(kind != NULL); err = svn_wc__db_base_get_info(status, kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, depth, NULL, NULL, NULL, NULL, NULL, NULL, db, local_abspath, scratch_pool, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); *kind = svn_node_unknown; if (status) *status = svn_wc__db_status_normal; if (depth) *depth = svn_depth_unknown; return SVN_NO_ERROR; } else SVN_ERR(err); return SVN_NO_ERROR; } /* */ static svn_error_t * make_dir_baton(struct dir_baton **d_p, const char *path, struct edit_baton *eb, struct dir_baton *pb, svn_boolean_t added, apr_pool_t *pool) { struct dir_baton *d; SVN_ERR_ASSERT(path || (! pb)); if (pb && pb->ambiently_excluded) { /* Just re-use the parent baton, since the only field that matters is ambiently_excluded. */ *d_p = pb; return SVN_NO_ERROR; } /* Okay, no easy out, so allocate and initialize a dir baton. */ d = apr_pcalloc(pool, sizeof(*d)); if (path) d->abspath = svn_dirent_join(eb->anchor_abspath, path, pool); else d->abspath = apr_pstrdup(pool, eb->anchor_abspath); /* The svn_depth_unknown means that: 1) pb is the anchor; 2) there is an non-null target, for which we are preparing the baton. This enables explicitly pull in the target. */ if (pb && pb->ambient_depth != svn_depth_unknown) { svn_boolean_t exclude; svn_wc__db_status_t status; svn_node_kind_t kind; svn_boolean_t exists = TRUE; if (!added) { SVN_ERR(ambient_read_info(&status, &kind, NULL, eb->db, d->abspath, pool)); } else { status = svn_wc__db_status_not_present; kind = svn_node_unknown; } exists = (kind != svn_node_unknown); if (pb->ambient_depth == svn_depth_empty || pb->ambient_depth == svn_depth_files) { /* This is not a depth upgrade, and the parent directory is depth==empty or depth==files. So if the parent doesn't already have an entry for the new dir, then the parent doesn't want the new dir at all, thus we should initialize it with ambiently_excluded=TRUE. */ exclude = !exists; } else { /* If the parent expect all children by default, only exclude it whenever it is explicitly marked as exclude. */ exclude = exists && (status == svn_wc__db_status_excluded); } if (exclude) { d->ambiently_excluded = TRUE; *d_p = d; return SVN_NO_ERROR; } } d->edit_baton = eb; /* We'll initialize this differently in add_directory and open_directory. */ d->ambient_depth = svn_depth_unknown; *d_p = d; return SVN_NO_ERROR; } /* */ static svn_error_t * make_file_baton(struct file_baton **f_p, struct dir_baton *pb, const char *path, svn_boolean_t added, apr_pool_t *pool) { struct file_baton *f = apr_pcalloc(pool, sizeof(*f)); struct edit_baton *eb = pb->edit_baton; svn_wc__db_status_t status; svn_node_kind_t kind; const char *abspath; SVN_ERR_ASSERT(path); if (pb->ambiently_excluded) { f->ambiently_excluded = TRUE; *f_p = f; return SVN_NO_ERROR; } abspath = svn_dirent_join(eb->anchor_abspath, path, pool); if (!added) { SVN_ERR(ambient_read_info(&status, &kind, NULL, eb->db, abspath, pool)); } else { status = svn_wc__db_status_not_present; kind = svn_node_unknown; } if (pb->ambient_depth == svn_depth_empty) { /* This is not a depth upgrade, and the parent directory is depth==empty. So if the parent doesn't already have an entry for the file, then the parent doesn't want to hear about the file at all. */ if (status == svn_wc__db_status_not_present || status == svn_wc__db_status_server_excluded || status == svn_wc__db_status_excluded || kind == svn_node_unknown) { f->ambiently_excluded = TRUE; *f_p = f; return SVN_NO_ERROR; } } /* If pb->ambient_depth == svn_depth_unknown we are pulling in new nodes */ if (pb->ambient_depth != svn_depth_unknown && status == svn_wc__db_status_excluded) { f->ambiently_excluded = TRUE; *f_p = f; return SVN_NO_ERROR; } f->edit_baton = pb->edit_baton; *f_p = f; return SVN_NO_ERROR; } /*** Editor Functions ***/ /* An svn_delta_editor_t function. */ static svn_error_t * set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool) { struct edit_baton *eb = edit_baton; /* Nothing depth-y to filter here. */ return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, target_revision, pool); } /* An svn_delta_editor_t function. */ static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton) { struct edit_baton *eb = edit_baton; struct dir_baton *b; SVN_ERR(make_dir_baton(&b, NULL, eb, NULL, FALSE, pool)); *root_baton = b; if (b->ambiently_excluded) return SVN_NO_ERROR; if (! *eb->target) { /* For an update with a NULL target, this is equivalent to open_dir(): */ svn_node_kind_t kind; svn_wc__db_status_t status; svn_depth_t depth; /* Read the depth from the entry. */ SVN_ERR(ambient_read_info(&status, &kind, &depth, eb->db, eb->anchor_abspath, pool)); if (kind != svn_node_unknown && status != svn_wc__db_status_not_present && status != svn_wc__db_status_excluded && status != svn_wc__db_status_server_excluded) { b->ambient_depth = depth; } } return eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision, pool, &b->wrapped_baton); } /* An svn_delta_editor_t function. */ static svn_error_t * delete_entry(const char *path, svn_revnum_t base_revision, void *parent_baton, apr_pool_t *pool) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; if (pb->ambiently_excluded) return SVN_NO_ERROR; if (pb->ambient_depth < svn_depth_immediates) { /* If the entry we want to delete doesn't exist, that's OK. It's probably an old server that doesn't understand depths. */ svn_node_kind_t kind; svn_wc__db_status_t status; const char *abspath; abspath = svn_dirent_join(eb->anchor_abspath, path, pool); SVN_ERR(ambient_read_info(&status, &kind, NULL, eb->db, abspath, pool)); if (kind == svn_node_unknown || status == svn_wc__db_status_not_present || status == svn_wc__db_status_excluded || status == svn_wc__db_status_server_excluded) return SVN_NO_ERROR; } return eb->wrapped_editor->delete_entry(path, base_revision, pb->wrapped_baton, pool); } /* An svn_delta_editor_t function. */ 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) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; struct dir_baton *b = NULL; SVN_ERR(make_dir_baton(&b, path, eb, pb, TRUE, pool)); *child_baton = b; if (b->ambiently_excluded) return SVN_NO_ERROR; /* It's not excluded, so what should we treat the ambient depth as being? */ if (strcmp(eb->target, path) == 0) { /* The target of the edit is being added, so make it infinity. */ b->ambient_depth = svn_depth_infinity; } else if (pb->ambient_depth == svn_depth_immediates) { b->ambient_depth = svn_depth_empty; } else { /* There may be a requested depth < svn_depth_infinity, but that's okay, libsvn_delta/depth_filter_editor.c will filter further calls out for us anyway, and the update_editor will do the right thing when it creates the directory. */ b->ambient_depth = svn_depth_infinity; } return eb->wrapped_editor->add_directory(path, pb->wrapped_baton, copyfrom_path, copyfrom_revision, pool, &b->wrapped_baton); } /* An svn_delta_editor_t function. */ static svn_error_t * open_directory(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **child_baton) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; struct dir_baton *b; const char *local_abspath; svn_node_kind_t kind; svn_wc__db_status_t status; svn_depth_t depth; SVN_ERR(make_dir_baton(&b, path, eb, pb, FALSE, pool)); *child_baton = b; if (b->ambiently_excluded) return SVN_NO_ERROR; SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton, base_revision, pool, &b->wrapped_baton)); /* Note that for the update editor, the open_directory above will flush the logs of pb's directory, which might be important for this svn_wc_entry call. */ local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); SVN_ERR(ambient_read_info(&status, &kind, &depth, eb->db, local_abspath, pool)); if (kind != svn_node_unknown && status != svn_wc__db_status_not_present && status != svn_wc__db_status_excluded && status != svn_wc__db_status_server_excluded) { b->ambient_depth = depth; } return SVN_NO_ERROR; } /* An svn_delta_editor_t function. */ 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) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; struct file_baton *b = NULL; SVN_ERR(make_file_baton(&b, pb, path, TRUE, pool)); *child_baton = b; if (b->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->add_file(path, pb->wrapped_baton, copyfrom_path, copyfrom_revision, pool, &b->wrapped_baton); } /* An svn_delta_editor_t function. */ static svn_error_t * open_file(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **child_baton) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; struct file_baton *b; SVN_ERR(make_file_baton(&b, pb, path, FALSE, pool)); *child_baton = b; if (b->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->open_file(path, pb->wrapped_baton, base_revision, pool, &b->wrapped_baton); } /* An svn_delta_editor_t function. */ 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) { struct file_baton *fb = file_baton; struct edit_baton *eb = fb->edit_baton; /* For filtered files, we just consume the textdelta. */ if (fb->ambiently_excluded) { *handler = svn_delta_noop_window_handler; *handler_baton = NULL; return SVN_NO_ERROR; } return eb->wrapped_editor->apply_textdelta(fb->wrapped_baton, base_checksum, pool, handler, handler_baton); } /* An svn_delta_editor_t function. */ static svn_error_t * close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool) { struct file_baton *fb = file_baton; struct edit_baton *eb = fb->edit_baton; if (fb->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->close_file(fb->wrapped_baton, text_checksum, pool); } /* An svn_delta_editor_t function. */ static svn_error_t * absent_file(const char *path, void *parent_baton, apr_pool_t *pool) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; if (pb->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool); } /* An svn_delta_editor_t function. */ static svn_error_t * close_directory(void *dir_baton, apr_pool_t *pool) { struct dir_baton *db = dir_baton; struct edit_baton *eb = db->edit_baton; if (db->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->close_directory(db->wrapped_baton, pool); } /* An svn_delta_editor_t function. */ static svn_error_t * absent_directory(const char *path, void *parent_baton, apr_pool_t *pool) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; /* Don't report absent items in filtered directories. */ if (pb->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, pool); } /* An svn_delta_editor_t function. */ static svn_error_t * change_file_prop(void *file_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { struct file_baton *fb = file_baton; struct edit_baton *eb = fb->edit_baton; if (fb->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->change_file_prop(fb->wrapped_baton, name, value, pool); } /* An svn_delta_editor_t function. */ static svn_error_t * change_dir_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { struct dir_baton *db = dir_baton; struct edit_baton *eb = db->edit_baton; if (db->ambiently_excluded) return SVN_NO_ERROR; return eb->wrapped_editor->change_dir_prop(db->wrapped_baton, name, value, pool); } /* An svn_delta_editor_t function. */ static svn_error_t * close_edit(void *edit_baton, apr_pool_t *pool) { struct edit_baton *eb = edit_baton; return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); } svn_error_t * svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor, void **edit_baton, svn_wc__db_t *db, const char *anchor_abspath, const char *target, const svn_delta_editor_t *wrapped_editor, void *wrapped_edit_baton, apr_pool_t *result_pool) { svn_delta_editor_t *depth_filter_editor; struct edit_baton *eb; SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); depth_filter_editor = svn_delta_default_editor(result_pool); depth_filter_editor->set_target_revision = set_target_revision; depth_filter_editor->open_root = open_root; depth_filter_editor->delete_entry = delete_entry; depth_filter_editor->add_directory = add_directory; depth_filter_editor->open_directory = open_directory; depth_filter_editor->change_dir_prop = change_dir_prop; depth_filter_editor->close_directory = close_directory; depth_filter_editor->absent_directory = absent_directory; depth_filter_editor->add_file = add_file; depth_filter_editor->open_file = open_file; depth_filter_editor->apply_textdelta = apply_textdelta; depth_filter_editor->change_file_prop = change_file_prop; depth_filter_editor->close_file = close_file; depth_filter_editor->absent_file = absent_file; depth_filter_editor->close_edit = close_edit; eb = apr_pcalloc(result_pool, sizeof(*eb)); eb->wrapped_editor = wrapped_editor; eb->wrapped_edit_baton = wrapped_edit_baton; eb->db = db; eb->anchor_abspath = anchor_abspath; eb->target = target; *editor = depth_filter_editor; *edit_baton = eb; return SVN_NO_ERROR; }