/* * diff_local.c -- A simple diff walker which compares local files against * their pristine versions. * * ==================================================================== * 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. * ==================================================================== * * This is the simple working copy diff algorithm which is used when you * just use 'svn diff PATH'. It shows what is modified in your working copy * since a node was checked out or copied but doesn't show most kinds of * restructuring operations. * * You can look at this as another form of the status walker. */ #include #include "svn_error.h" #include "svn_pools.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_hash.h" #include "private/svn_wc_private.h" #include "private/svn_diff_tree.h" #include "wc.h" #include "wc_db.h" #include "props.h" #include "diff.h" #include "svn_private_config.h" /*-------------------------------------------------------------------------*/ /* Baton containing the state of a directory reported open via a diff processor */ struct node_state_t { struct node_state_t *parent; apr_pool_t *pool; const char *local_abspath; const char *relpath; void *baton; svn_diff_source_t *left_src; svn_diff_source_t *right_src; svn_diff_source_t *copy_src; svn_boolean_t skip; svn_boolean_t skip_children; apr_hash_t *left_props; apr_hash_t *right_props; const apr_array_header_t *propchanges; }; /* The diff baton */ struct diff_baton { /* A wc db. */ svn_wc__db_t *db; /* Report editor paths relative from this directory */ const char *anchor_abspath; struct node_state_t *cur; const svn_diff_tree_processor_t *processor; /* Should this diff ignore node ancestry? */ svn_boolean_t ignore_ancestry; /* Cancel function/baton */ svn_cancel_func_t cancel_func; void *cancel_baton; apr_pool_t *pool; }; /* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself, but create it marked with skip+skip_children. */ static svn_error_t * ensure_state(struct diff_baton *eb, const char *local_abspath, svn_boolean_t recursive_skip, apr_pool_t *scratch_pool) { struct node_state_t *ns; apr_pool_t *ns_pool; if (!eb->cur) { const char *relpath; relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); if (! relpath) return SVN_NO_ERROR; /* Don't recurse on the anchor, as that might loop infinitely because svn_dirent_dirname("/",...) -> "/" svn_dirent_dirname("C:/",...) -> "C:/" (Windows) */ if (*relpath) SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), FALSE, scratch_pool)); } else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL)) SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), FALSE, scratch_pool)); else return SVN_NO_ERROR; if (eb->cur && eb->cur->skip_children) return SVN_NO_ERROR; ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); ns = apr_pcalloc(ns_pool, sizeof(*ns)); ns->pool = ns_pool; ns->local_abspath = apr_pstrdup(ns_pool, local_abspath); ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath); ns->parent = eb->cur; eb->cur = ns; if (recursive_skip) { ns->skip = TRUE; ns->skip_children = TRUE; return SVN_NO_ERROR; } { svn_revnum_t revision; svn_error_t *err; err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool); if (err) { if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) return svn_error_trace(err); svn_error_clear(err); revision = 0; /* Use original revision? */ } ns->left_src = svn_diff__source_create(revision, ns->pool); ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, &ns->skip_children, ns->relpath, ns->left_src, ns->right_src, NULL /* copyfrom_source */, ns->parent ? ns->parent->baton : NULL, eb->processor, ns->pool, scratch_pool)); } return SVN_NO_ERROR; } /* Implements svn_wc_status_func3_t */ static svn_error_t * diff_status_callback(void *baton, const char *local_abspath, const svn_wc_status3_t *status, apr_pool_t *scratch_pool) { struct diff_baton *eb = baton; svn_wc__db_t *db = eb->db; if (! status->versioned) return SVN_NO_ERROR; /* unversioned (includes dir externals) */ if (status->node_status == svn_wc_status_conflicted && status->text_status == svn_wc_status_none && status->prop_status == svn_wc_status_none) { /* Node is an actual only node describing a tree conflict */ return SVN_NO_ERROR; } /* Not text/prop modified, not copied. Easy out */ if (status->node_status == svn_wc_status_normal && !status->copied) return SVN_NO_ERROR; /* Mark all directories where we are no longer inside as closed */ while (eb->cur && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) { struct node_state_t *ns = eb->cur; if (!ns->skip) { if (ns->propchanges) SVN_ERR(eb->processor->dir_changed(ns->relpath, ns->left_src, ns->right_src, ns->left_props, ns->right_props, ns->propchanges, ns->baton, eb->processor, ns->pool)); else SVN_ERR(eb->processor->dir_closed(ns->relpath, ns->left_src, ns->right_src, ns->baton, eb->processor, ns->pool)); } eb->cur = ns->parent; svn_pool_clear(ns->pool); } SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), FALSE, scratch_pool)); if (eb->cur && eb->cur->skip_children) return SVN_NO_ERROR; /* This code does about the same thing as the inner body of walk_local_nodes_diff() in diff_editor.c, except that it is already filtered by the status walker, doesn't have to account for remote changes (and many tiny other details) */ { svn_boolean_t repos_only; svn_boolean_t local_only; svn_wc__db_status_t db_status; svn_boolean_t have_base; svn_node_kind_t base_kind; svn_node_kind_t db_kind = status->kind; svn_depth_t depth_below_here = svn_depth_unknown; const char *child_abspath = local_abspath; const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, local_abspath); repos_only = FALSE; local_only = FALSE; /* ### optimize away this call using status info. Should be possible in almost every case (except conflict, missing, obst.)*/ SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &have_base, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (!have_base) { local_only = TRUE; /* Only report additions */ } else if (db_status == svn_wc__db_status_normal || db_status == svn_wc__db_status_incomplete) { /* Simple diff */ base_kind = db_kind; } else if (db_status == svn_wc__db_status_deleted) { svn_wc__db_status_t base_status; repos_only = TRUE; SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (base_status != svn_wc__db_status_normal && base_status != svn_wc__db_status_incomplete) return SVN_NO_ERROR; } else { /* working status is either added or deleted */ svn_wc__db_status_t base_status; SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, eb->db, local_abspath, scratch_pool, scratch_pool)); if (base_status != svn_wc__db_status_normal && base_status != svn_wc__db_status_incomplete) local_only = TRUE; else if (base_kind != db_kind || !eb->ignore_ancestry) { repos_only = TRUE; local_only = TRUE; } } if (repos_only) { /* Report repository form deleted */ if (base_kind == svn_node_file) SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, eb->processor, eb->cur ? eb->cur->baton : NULL, scratch_pool)); else if (base_kind == svn_node_dir) SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, depth_below_here, eb->processor, eb->cur ? eb->cur->baton : NULL, eb->cancel_func, eb->cancel_baton, scratch_pool)); } else if (!local_only) { /* Diff base against actual */ if (db_kind == svn_node_file) { SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, child_relpath, SVN_INVALID_REVNUM, eb->processor, eb->cur ? eb->cur->baton : NULL, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); } else if (db_kind == svn_node_dir) { SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); if (status->prop_status != svn_wc_status_none && status->prop_status != svn_wc_status_normal) { apr_array_header_t *propchanges; SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, eb->db, local_abspath, eb->cur->pool, scratch_pool)); SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, eb->db, local_abspath, eb->cur->pool, scratch_pool)); SVN_ERR(svn_prop_diffs(&propchanges, eb->cur->right_props, eb->cur->left_props, eb->cur->pool)); eb->cur->propchanges = propchanges; } } } if (local_only && (db_status != svn_wc__db_status_deleted)) { /* Moved from. Relative from diff anchor*/ const char *moved_from_relpath = NULL; if (status->moved_from_abspath) { moved_from_relpath = svn_dirent_skip_ancestor( eb->anchor_abspath, status->moved_from_abspath); } if (db_kind == svn_node_file) SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, child_relpath, moved_from_relpath, eb->processor, eb->cur ? eb->cur->baton : NULL, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); else if (db_kind == svn_node_dir) SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, child_relpath, depth_below_here, moved_from_relpath, eb->processor, eb->cur ? eb->cur->baton : NULL, FALSE, eb->cancel_func, eb->cancel_baton, scratch_pool)); } if (db_kind == svn_node_dir && (local_only || repos_only)) SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); } return SVN_NO_ERROR; } /* Public Interface */ svn_error_t * svn_wc__diff7(const char **root_relpath, svn_boolean_t *root_is_dir, svn_wc_context_t *wc_ctx, const char *local_abspath, svn_depth_t depth, svn_boolean_t ignore_ancestry, const apr_array_header_t *changelist_filter, const svn_diff_tree_processor_t *diff_processor, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct diff_baton eb = { 0 }; svn_node_kind_t kind; svn_boolean_t get_all; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, FALSE /* allow_missing */, TRUE /* show_deleted */, FALSE /* show_hidden */, scratch_pool)); eb.anchor_abspath = local_abspath; if (root_relpath) { svn_boolean_t is_wcroot; SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, wc_ctx->db, local_abspath, scratch_pool)); if (!is_wcroot) eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); } else if (kind != svn_node_dir) eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); if (root_relpath) *root_relpath = apr_pstrdup(result_pool, svn_dirent_skip_ancestor(eb.anchor_abspath, local_abspath)); if (root_is_dir) *root_is_dir = (kind == svn_node_dir); /* Apply changelist filtering to the output */ if (changelist_filter && changelist_filter->nelts) { apr_hash_t *changelist_hash; SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, result_pool)); diff_processor = svn_wc__changelist_filter_tree_processor_create( diff_processor, wc_ctx, local_abspath, changelist_hash, result_pool); } eb.db = wc_ctx->db; eb.processor = diff_processor; eb.ignore_ancestry = ignore_ancestry; eb.pool = scratch_pool; if (ignore_ancestry) get_all = TRUE; /* We need unmodified descendants of copies */ else get_all = FALSE; /* Walk status handles files and directories */ SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth, get_all, TRUE /* no_ignore */, FALSE /* ignore_text_mods */, NULL /* ignore_patterns */, diff_status_callback, &eb, cancel_func, cancel_baton, scratch_pool)); /* Close the remaining open directories */ while (eb.cur) { struct node_state_t *ns = eb.cur; if (!ns->skip) { if (ns->propchanges) SVN_ERR(diff_processor->dir_changed(ns->relpath, ns->left_src, ns->right_src, ns->left_props, ns->right_props, ns->propchanges, ns->baton, diff_processor, ns->pool)); else SVN_ERR(diff_processor->dir_closed(ns->relpath, ns->left_src, ns->right_src, ns->baton, diff_processor, ns->pool)); } eb.cur = ns->parent; svn_pool_clear(ns->pool); } return SVN_NO_ERROR; } svn_error_t * svn_wc_diff6(svn_wc_context_t *wc_ctx, const char *local_abspath, const svn_wc_diff_callbacks4_t *callbacks, void *callback_baton, svn_depth_t depth, svn_boolean_t ignore_ancestry, svn_boolean_t show_copies_as_adds, svn_boolean_t use_git_diff_format, const apr_array_header_t *changelist_filter, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { const svn_diff_tree_processor_t *processor; SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, callbacks, callback_baton, TRUE, scratch_pool, scratch_pool)); if (use_git_diff_format) show_copies_as_adds = TRUE; if (show_copies_as_adds) ignore_ancestry = FALSE; if (! show_copies_as_adds && !use_git_diff_format) processor = svn_diff__tree_processor_copy_as_changed_create(processor, scratch_pool); return svn_error_trace(svn_wc__diff7(NULL, NULL, wc_ctx, local_abspath, depth, ignore_ancestry, changelist_filter, processor, cancel_func, cancel_baton, scratch_pool, scratch_pool)); }