/*
* diff_local.c: comparing local trees with each other
*
* ====================================================================
* 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.
* ====================================================================
*/
/* ==================================================================== */
/*** Includes. ***/
#include <apr_strings.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include "svn_hash.h"
#include "svn_types.h"
#include "svn_wc.h"
#include "svn_diff.h"
#include "svn_client.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_dirent_uri.h"
#include "svn_io.h"
#include "svn_pools.h"
#include "svn_props.h"
#include "svn_sorts.h"
#include "svn_subst.h"
#include "client.h"
#include "private/svn_sorts_private.h"
#include "private/svn_wc_private.h"
#include "private/svn_diff_tree.h"
#include "svn_private_config.h"
/* Try to get properties for LOCAL_ABSPATH and return them in the property
* hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
* versioned, return an empty property hash. */
static svn_error_t *
get_props(apr_hash_t **props,
const char *local_abspath,
svn_wc_context_t *wc_ctx,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_error_t *err;
err = svn_wc_prop_list2(props, wc_ctx, local_abspath, result_pool,
scratch_pool);
if (err)
{
if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
{
svn_error_clear(err);
*props = apr_hash_make(result_pool);
/* ### Apply autoprops, like 'svn add' would? */
}
else
return svn_error_trace(err);
}
return SVN_NO_ERROR;
}
/* Forward declaration */
static svn_error_t *
do_file_diff(const char *left_abspath,
const char *right_abspath,
const char *left_root_abspath,
const char *right_root_abspath,
svn_boolean_t left_only,
svn_boolean_t right_only,
void *parent_baton,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool);
/* Forward declaration */
static svn_error_t *
do_dir_diff(const char *left_abspath,
const char *right_abspath,
const char *left_root_abspath,
const char *right_root_abspath,
svn_boolean_t left_only,
svn_boolean_t right_only,
svn_boolean_t left_before_right,
svn_depth_t depth,
void *parent_baton,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool);
/* Produce a diff of depth DEPTH between two arbitrary directories at
* LEFT_ABSPATH1 and RIGHT_ABSPATH2, using the provided diff callbacks
* to show file changes and, for versioned nodes, property changes.
*
* Report paths as relative from LEFT_ROOT_ABSPATH/RIGHT_ROOT_ABSPATH.
*
* If LEFT_ONLY is TRUE, only the left source exists (= everything will
* be reported as deleted). If RIGHT_ONLY is TRUE, only the right source
* exists (= everything will be reported as added).
*
* If LEFT_BEFORE_RIGHT is TRUE and left and right are unrelated, left is
* reported first. If false, right is reported first. (This is to allow
* producing a proper inverse diff).
*
* Walk the sources according to depth, and report with parent baton
* PARENT_BATON. */
static svn_error_t *
inner_dir_diff(const char *left_abspath,
const char *right_abspath,
const char *left_root_abspath,
const char *right_root_abspath,
svn_boolean_t left_only,
svn_boolean_t right_only,
svn_boolean_t left_before_right,
svn_depth_t depth,
void *parent_baton,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_hash_t *left_dirents;
apr_hash_t *right_dirents;
apr_array_header_t *sorted_dirents;
svn_error_t *err;
svn_depth_t depth_below_here;
int i;
SVN_ERR_ASSERT(depth >= svn_depth_files && depth <= svn_depth_infinity);
if (!right_only)
{
err = svn_io_get_dirents3(&left_dirents, left_abspath, FALSE,
scratch_pool, iterpool);
if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
|| SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
{
svn_error_clear(err);
left_dirents = apr_hash_make(scratch_pool);
right_only = TRUE;
}
else
SVN_ERR(err);
}
else
left_dirents = apr_hash_make(scratch_pool);
if (!left_only)
{
err = svn_io_get_dirents3(&right_dirents, right_abspath, FALSE,
scratch_pool, iterpool);
if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
|| SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
{
svn_error_clear(err);
right_dirents = apr_hash_make(scratch_pool);
left_only = TRUE;
}
else
SVN_ERR(err);
}
else
right_dirents = apr_hash_make(scratch_pool);
if (left_only && right_only)
return SVN_NO_ERROR; /* Somebody deleted the directory?? */
if (depth != svn_depth_infinity)
depth_below_here = svn_depth_empty;
else
depth_below_here = svn_depth_infinity;
sorted_dirents = svn_sort__hash(apr_hash_merge(iterpool, left_dirents,
right_dirents, NULL, NULL),
svn_sort_compare_items_as_paths,
scratch_pool);
for (i = 0; i < sorted_dirents->nelts; i++)
{
svn_sort__item_t* elt = &APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t);
svn_io_dirent2_t *left_dirent;
svn_io_dirent2_t *right_dirent;
const char *child_left_abspath;
const char *child_right_abspath;
svn_pool_clear(iterpool);
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
if (svn_wc_is_adm_dir(elt->key, iterpool))
continue;
left_dirent = right_only ? NULL : svn_hash_gets(left_dirents, elt->key);
right_dirent = left_only ? NULL : svn_hash_gets(right_dirents, elt->key);
child_left_abspath = svn_dirent_join(left_abspath, elt->key, iterpool);
child_right_abspath = svn_dirent_join(right_abspath, elt->key, iterpool);
if (((left_dirent == NULL) != (right_dirent == NULL))
|| (left_dirent->kind != right_dirent->kind))
{
/* Report delete and/or add */
if (left_dirent && left_before_right)
{
if (left_dirent->kind == svn_node_file)
SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, parent_baton,
diff_processor, ctx, iterpool));
else if (depth >= svn_depth_immediates)
SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, left_before_right,
depth_below_here, parent_baton,
diff_processor, ctx, iterpool));
}
if (right_dirent)
{
if (right_dirent->kind == svn_node_file)
SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
FALSE, TRUE, parent_baton,
diff_processor, ctx, iterpool));
else if (depth >= svn_depth_immediates)
SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
FALSE, TRUE, left_before_right,
depth_below_here, parent_baton,
diff_processor, ctx, iterpool));
}
if (left_dirent && !left_before_right)
{
if (left_dirent->kind == svn_node_file)
SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, parent_baton,
diff_processor, ctx, iterpool));
else if (depth >= svn_depth_immediates)
SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, left_before_right,
depth_below_here, parent_baton,
diff_processor, ctx, iterpool));
}
}
else if (left_dirent->kind == svn_node_file)
{
/* Perform file-file diff */
SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
FALSE, FALSE, parent_baton,
diff_processor, ctx, iterpool));
}
else if (depth >= svn_depth_immediates)
{
/* Perform dir-dir diff */
SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath,
left_root_abspath, right_root_abspath,
FALSE, FALSE, left_before_right,
depth_below_here, parent_baton,
diff_processor, ctx, iterpool));
}
}
return SVN_NO_ERROR;
}
/* Translates *LEFT_ABSPATH to a temporary file if PROPS specify that the
file needs translation. *LEFT_ABSPATH is updated to point to a file that
lives at least as long as RESULT_POOL when translation is necessary.
Otherwise the value is not updated */
static svn_error_t *
translate_if_necessary(const char **local_abspath,
apr_hash_t *props,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
const svn_string_t *eol_style_val;
const svn_string_t *keywords_val;
svn_subst_eol_style_t eol_style;
const char *eol;
apr_hash_t *keywords;
svn_stream_t *contents;
svn_stream_t *dst;
/* if (svn_hash_gets(props, SVN_PROP_SPECIAL))
### TODO: Implement */
eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
if (eol_style_val)
svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
else
{
eol = NULL;
eol_style = svn_subst_eol_style_none;
}
if (keywords_val)
SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data,
APR_STRINGIFY(SVN_INVALID_REVNUM),
"", "", 0, "", scratch_pool));
else
keywords = NULL;
if (!svn_subst_translation_required(eol_style, eol, keywords, FALSE, FALSE))
return SVN_NO_ERROR;
SVN_ERR(svn_stream_open_readonly(&contents, *local_abspath,
scratch_pool, scratch_pool));
SVN_ERR(svn_stream_open_unique(&dst, local_abspath, NULL,
svn_io_file_del_on_pool_cleanup,
result_pool, scratch_pool));
dst = svn_subst_stream_translated(dst, eol, TRUE /* repair */,
keywords, FALSE /* expand */,
scratch_pool);
SVN_ERR(svn_stream_copy3(contents, dst, cancel_func, cancel_baton,
scratch_pool));
return SVN_NO_ERROR;
}
/* Handles reporting of a file for inner_dir_diff */
static svn_error_t *
do_file_diff(const char *left_abspath,
const char *right_abspath,
const char *left_root_abspath,
const char *right_root_abspath,
svn_boolean_t left_only,
svn_boolean_t right_only,
void *parent_baton,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *relpath;
svn_diff_source_t *left_source;
svn_diff_source_t *right_source;
svn_boolean_t skip = FALSE;
apr_hash_t *left_props;
apr_hash_t *right_props;
void *file_baton;
relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath);
if (! right_only)
left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
else
left_source = NULL;
if (! left_only)
right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
else
right_source = NULL;
SVN_ERR(diff_processor->file_opened(&file_baton, &skip,
relpath,
left_source,
right_source,
NULL /* copyfrom_source */,
parent_baton,
diff_processor,
scratch_pool,
scratch_pool));
if (skip)
return SVN_NO_ERROR;
if (! right_only)
{
SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx,
scratch_pool, scratch_pool));
/* We perform a mimetype detection to avoid diffing binary files
for textual changes.*/
if (! svn_hash_gets(left_props, SVN_PROP_MIME_TYPE))
{
const char *mime_type;
/* ### Use libmagic magic? */
SVN_ERR(svn_io_detect_mimetype2(&mime_type, left_abspath,
ctx->mimetypes_map, scratch_pool));
if (mime_type)
svn_hash_sets(left_props, SVN_PROP_MIME_TYPE,
svn_string_create(mime_type, scratch_pool));
}
SVN_ERR(translate_if_necessary(&left_abspath, left_props,
ctx->cancel_func, ctx->cancel_baton,
scratch_pool, scratch_pool));
}
else
left_props = NULL;
if (! left_only)
{
SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx,
scratch_pool, scratch_pool));
/* We perform a mimetype detection to avoid diffing binary files
for textual changes.*/
if (! svn_hash_gets(right_props, SVN_PROP_MIME_TYPE))
{
const char *mime_type;
/* ### Use libmagic magic? */
SVN_ERR(svn_io_detect_mimetype2(&mime_type, right_abspath,
ctx->mimetypes_map, scratch_pool));
if (mime_type)
svn_hash_sets(right_props, SVN_PROP_MIME_TYPE,
svn_string_create(mime_type, scratch_pool));
}
SVN_ERR(translate_if_necessary(&right_abspath, right_props,
ctx->cancel_func, ctx->cancel_baton,
scratch_pool, scratch_pool));
}
else
right_props = NULL;
if (left_only)
{
SVN_ERR(diff_processor->file_deleted(relpath,
left_source,
left_abspath,
left_props,
file_baton,
diff_processor,
scratch_pool));
}
else if (right_only)
{
SVN_ERR(diff_processor->file_added(relpath,
NULL /* copyfrom_source */,
right_source,
NULL /* copyfrom_file */,
right_abspath,
NULL /* copyfrom_props */,
right_props,
file_baton,
diff_processor,
scratch_pool));
}
else
{
/* ### Perform diff -> close/changed */
svn_boolean_t same;
apr_array_header_t *prop_changes;
SVN_ERR(svn_io_files_contents_same_p(&same, left_abspath, right_abspath,
scratch_pool));
SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
scratch_pool));
if (!same || prop_changes->nelts > 0)
{
SVN_ERR(diff_processor->file_changed(relpath,
left_source,
right_source,
same ? NULL : left_abspath,
same ? NULL : right_abspath,
left_props,
right_props,
!same,
prop_changes,
file_baton,
diff_processor,
scratch_pool));
}
else
{
SVN_ERR(diff_processor->file_closed(relpath,
left_source,
right_source,
file_baton,
diff_processor,
scratch_pool));
}
}
return SVN_NO_ERROR;
}
/* Handles reporting of a directory and its children for inner_dir_diff */
static svn_error_t *
do_dir_diff(const char *left_abspath,
const char *right_abspath,
const char *left_root_abspath,
const char *right_root_abspath,
svn_boolean_t left_only,
svn_boolean_t right_only,
svn_boolean_t left_before_right,
svn_depth_t depth,
void *parent_baton,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *relpath;
svn_diff_source_t *left_source;
svn_diff_source_t *right_source;
svn_boolean_t skip = FALSE;
svn_boolean_t skip_children = FALSE;
void *dir_baton;
apr_hash_t *left_props;
apr_hash_t *right_props;
relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath);
if (! right_only)
{
left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx,
scratch_pool, scratch_pool));
}
else
{
left_source = NULL;
left_props = NULL;
}
if (! left_only)
{
right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx,
scratch_pool, scratch_pool));
}
else
{
right_source = NULL;
right_props = NULL;
}
SVN_ERR(diff_processor->dir_opened(&dir_baton, &skip, &skip_children,
relpath,
left_source,
right_source,
NULL /* copyfrom_source */,
parent_baton,
diff_processor,
scratch_pool, scratch_pool));
if (!skip_children)
{
if (depth >= svn_depth_files)
SVN_ERR(inner_dir_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
left_only, right_only,
left_before_right, depth,
dir_baton,
diff_processor, ctx, scratch_pool));
}
else if (skip)
return SVN_NO_ERROR;
if (left_props && right_props)
{
apr_array_header_t *prop_diffs;
SVN_ERR(svn_prop_diffs(&prop_diffs, right_props, left_props,
scratch_pool));
if (prop_diffs->nelts)
{
SVN_ERR(diff_processor->dir_changed(relpath,
left_source,
right_source,
left_props,
right_props,
prop_diffs,
dir_baton,
diff_processor,
scratch_pool));
return SVN_NO_ERROR;
}
}
if (left_source && right_source)
{
SVN_ERR(diff_processor->dir_closed(relpath,
left_source,
right_source,
dir_baton,
diff_processor,
scratch_pool));
}
else if (left_source)
{
SVN_ERR(diff_processor->dir_deleted(relpath,
left_source,
left_props,
dir_baton,
diff_processor,
scratch_pool));
}
else
{
SVN_ERR(diff_processor->dir_added(relpath,
NULL /* copyfrom_source */,
right_source,
NULL /* copyfrom_props */,
right_props,
dir_baton,
diff_processor,
scratch_pool));
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_client__arbitrary_nodes_diff(const char *left_abspath,
const char *right_abspath,
svn_depth_t depth,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
svn_node_kind_t left_kind;
svn_node_kind_t right_kind;
const char *left_root_abspath = left_abspath;
const char *right_root_abspath = right_abspath;
svn_boolean_t left_before_right = TRUE; /* Future argument? */
if (depth == svn_depth_unknown)
depth = svn_depth_infinity;
SVN_ERR(svn_io_check_resolved_path(left_abspath, &left_kind, scratch_pool));
SVN_ERR(svn_io_check_resolved_path(right_abspath, &right_kind, scratch_pool));
if (left_kind == svn_node_dir && right_kind == svn_node_dir)
{
SVN_ERR(do_dir_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
FALSE, FALSE, left_before_right,
depth, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
}
else if (left_kind == svn_node_file && right_kind == svn_node_file)
{
SVN_ERR(do_file_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
FALSE, FALSE,
NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
}
else if (left_kind == svn_node_file || left_kind == svn_node_dir
|| right_kind == svn_node_file || right_kind == svn_node_dir)
{
/* The root is added/deleted/replaced. Report delete and/or add. */
if (left_before_right)
{
if (left_kind == svn_node_file)
SVN_ERR(do_file_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
else if (left_kind == svn_node_dir)
SVN_ERR(do_dir_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, left_before_right,
depth, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
}
if (right_kind == svn_node_file)
SVN_ERR(do_file_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
FALSE, TRUE, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
else if (right_kind == svn_node_dir)
SVN_ERR(do_dir_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
FALSE, TRUE, left_before_right,
depth, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
if (! left_before_right)
{
if (left_kind == svn_node_file)
SVN_ERR(do_file_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
else if (left_kind == svn_node_dir)
SVN_ERR(do_dir_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
TRUE, FALSE, left_before_right,
depth, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
}
}
else
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
_("'%s' is not a file or directory"),
svn_dirent_local_style(
(left_kind == svn_node_none)
? left_abspath
: right_abspath,
scratch_pool));
return SVN_NO_ERROR;
}