blob: 61ec06767e3a0dfef25409687e88f9ce4b1ca054 [file] [log] [blame]
/*
* 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 <apr_hash.h>
#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));
}