diff options
Diffstat (limited to 'subversion/svn/diff-cmd.c')
-rw-r--r-- | subversion/svn/diff-cmd.c | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/subversion/svn/diff-cmd.c b/subversion/svn/diff-cmd.c new file mode 100644 index 000000000000..2cbd202e3e25 --- /dev/null +++ b/subversion/svn/diff-cmd.c @@ -0,0 +1,476 @@ +/* + * diff-cmd.c -- Display context diff of a file + * + * ==================================================================== + * 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 "svn_pools.h" +#include "svn_client.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_types.h" +#include "svn_cmdline.h" +#include "svn_xml.h" +#include "cl.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +/* Convert KIND into a single character for display to the user. */ +static char +kind_to_char(svn_client_diff_summarize_kind_t kind) +{ + switch (kind) + { + case svn_client_diff_summarize_kind_modified: + return 'M'; + + case svn_client_diff_summarize_kind_added: + return 'A'; + + case svn_client_diff_summarize_kind_deleted: + return 'D'; + + default: + return ' '; + } +} + +/* Convert KIND into a word describing the kind to the user. */ +static const char * +kind_to_word(svn_client_diff_summarize_kind_t kind) +{ + switch (kind) + { + case svn_client_diff_summarize_kind_modified: return "modified"; + case svn_client_diff_summarize_kind_added: return "added"; + case svn_client_diff_summarize_kind_deleted: return "deleted"; + default: return "none"; + } +} + +/* Baton for summarize_xml and summarize_regular */ +struct summarize_baton_t +{ + const char *anchor; +}; + +/* Print summary information about a given change as XML, implements the + * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *' + * representing the either the path to the working copy root or the url + * the path the working copy root corresponds to. */ +static svn_error_t * +summarize_xml(const svn_client_diff_summarize_t *summary, + void *baton, + apr_pool_t *pool) +{ + struct summarize_baton_t *b = baton; + /* Full path to the object being diffed. This is created by taking the + * baton, and appending the target's relative path. */ + const char *path = b->anchor; + svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); + + /* Tack on the target path, so we can differentiate between different parts + * of the output when we're given multiple targets. */ + if (svn_path_is_url(path)) + { + path = svn_path_url_add_component2(path, summary->path, pool); + } + else + { + path = svn_dirent_join(path, summary->path, pool); + + /* Convert non-urls to local style, so that things like "" + show up as "." */ + path = svn_dirent_local_style(path, pool); + } + + svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", + "kind", svn_cl__node_kind_str_xml(summary->node_kind), + "item", kind_to_word(summary->summarize_kind), + "props", summary->prop_changed ? "modified" : "none", + NULL); + + svn_xml_escape_cdata_cstring(&sb, path, pool); + svn_xml_make_close_tag(&sb, pool, "path"); + + return svn_cl__error_checked_fputs(sb->data, stdout); +} + +/* Print summary information about a given change, implements the + * svn_client_diff_summarize_func_t interface. */ +static svn_error_t * +summarize_regular(const svn_client_diff_summarize_t *summary, + void *baton, + apr_pool_t *pool) +{ + struct summarize_baton_t *b = baton; + const char *path = b->anchor; + + /* Tack on the target path, so we can differentiate between different parts + * of the output when we're given multiple targets. */ + if (svn_path_is_url(path)) + { + path = svn_path_url_add_component2(path, summary->path, pool); + } + else + { + path = svn_dirent_join(path, summary->path, pool); + + /* Convert non-urls to local style, so that things like "" + show up as "." */ + path = svn_dirent_local_style(path, pool); + } + + /* Note: This output format tries to look like the output of 'svn status', + * thus the blank spaces where information that is not relevant to + * a diff summary would go. */ + + SVN_ERR(svn_cmdline_printf(pool, + "%c%c %s\n", + kind_to_char(summary->summarize_kind), + summary->prop_changed ? 'M' : ' ', + path)); + + return svn_cmdline_fflush(stdout); +} + +/* An svn_opt_subcommand_t to handle the 'diff' command. + This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__diff(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + apr_array_header_t *options; + apr_array_header_t *targets; + svn_stream_t *outstream; + svn_stream_t *errstream; + const char *old_target, *new_target; + apr_pool_t *iterpool; + svn_boolean_t pegged_diff = FALSE; + svn_boolean_t show_copies_as_adds = + opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds; + svn_boolean_t ignore_properties = + opt_state->diff.patch_compatible || opt_state->diff.ignore_properties; + int i; + struct summarize_baton_t summarize_baton; + const svn_client_diff_summarize_func_t summarize_func = + (opt_state->xml ? summarize_xml : summarize_regular); + + if (opt_state->extensions) + options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); + else + options = NULL; + + /* Get streams representing stdout and stderr, which is where + we'll have the external 'diff' program print to. */ + SVN_ERR(svn_stream_for_stdout(&outstream, pool)); + SVN_ERR(svn_stream_for_stderr(&errstream, pool)); + + if (opt_state->xml) + { + svn_stringbuf_t *sb; + + /* Check that the --summarize is passed as well. */ + if (!opt_state->diff.summarize) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'--xml' option only valid with " + "'--summarize' option")); + + SVN_ERR(svn_cl__xml_print_header("diff", pool)); + + sb = svn_stringbuf_create_empty(pool); + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + } + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + + if (! opt_state->old_target && ! opt_state->new_target + && (targets->nelts == 2) + && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)) + || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *))) + && opt_state->start_revision.kind == svn_opt_revision_unspecified + && opt_state->end_revision.kind == svn_opt_revision_unspecified) + { + /* A 2-target diff where one or both targets are URLs. These are + * shorthands for some 'svn diff --old X --new Y' invocations. */ + + SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target, + APR_ARRAY_IDX(targets, 0, const char *), + pool)); + SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target, + APR_ARRAY_IDX(targets, 1, const char *), + pool)); + targets->nelts = 0; + + /* Set default start/end revisions based on target types, in the same + * manner as done for the corresponding '--old X --new Y' cases, + * (note that we have an explicit --new target) */ + if (opt_state->start_revision.kind == svn_opt_revision_unspecified) + opt_state->start_revision.kind = svn_path_is_url(old_target) + ? svn_opt_revision_head : svn_opt_revision_working; + + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + opt_state->end_revision.kind = svn_path_is_url(new_target) + ? svn_opt_revision_head : svn_opt_revision_working; + } + else if (opt_state->old_target) + { + apr_array_header_t *tmp, *tmp2; + svn_opt_revision_t old_rev, new_rev; + + /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]] + [PATH...]' case matches. */ + + tmp = apr_array_make(pool, 2, sizeof(const char *)); + APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target); + APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target + ? opt_state->new_target + : opt_state->old_target); + + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp, + ctx, FALSE, pool)); + + /* Check if either or both targets were skipped (e.g. because they + * were .svn directories). */ + if (tmp2->nelts < 2) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); + + SVN_ERR(svn_opt_parse_path(&old_rev, &old_target, + APR_ARRAY_IDX(tmp2, 0, const char *), + pool)); + if (old_rev.kind != svn_opt_revision_unspecified) + opt_state->start_revision = old_rev; + SVN_ERR(svn_opt_parse_path(&new_rev, &new_target, + APR_ARRAY_IDX(tmp2, 1, const char *), + pool)); + if (new_rev.kind != svn_opt_revision_unspecified) + opt_state->end_revision = new_rev; + + /* For URLs, default to HEAD. For WC paths, default to WORKING if + * new target is explicit; if new target is implicitly the same as + * old target, then default the old to BASE and new to WORKING. */ + if (opt_state->start_revision.kind == svn_opt_revision_unspecified) + opt_state->start_revision.kind = svn_path_is_url(old_target) + ? svn_opt_revision_head + : (opt_state->new_target + ? svn_opt_revision_working : svn_opt_revision_base); + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + opt_state->end_revision.kind = svn_path_is_url(new_target) + ? svn_opt_revision_head : svn_opt_revision_working; + } + else if (opt_state->new_target) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'--new' option only valid with " + "'--old' option")); + } + else + { + svn_boolean_t working_copy_present; + + /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */ + + /* Here each target is a pegged object. Find out the starting + and ending paths for each target. */ + + svn_opt_push_implicit_dot_target(targets, pool); + + old_target = ""; + new_target = ""; + + SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets), + _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed " + "target types. Try using the --old and --new options or one of " + "the shorthand invocations listed in 'svn help diff'.")); + + working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0, + const char *)); + + if (opt_state->start_revision.kind == svn_opt_revision_unspecified + && working_copy_present) + opt_state->start_revision.kind = svn_opt_revision_base; + if (opt_state->end_revision.kind == svn_opt_revision_unspecified) + opt_state->end_revision.kind = working_copy_present + ? svn_opt_revision_working : svn_opt_revision_head; + + /* Determine if we need to do pegged diffs. */ + if ((opt_state->start_revision.kind != svn_opt_revision_base + && opt_state->start_revision.kind != svn_opt_revision_working) + || (opt_state->end_revision.kind != svn_opt_revision_base + && opt_state->end_revision.kind != svn_opt_revision_working)) + pegged_diff = TRUE; + + } + + svn_opt_push_implicit_dot_target(targets, pool); + + iterpool = svn_pool_create(pool); + + for (i = 0; i < targets->nelts; ++i) + { + const char *path = APR_ARRAY_IDX(targets, i, const char *); + const char *target1, *target2; + + svn_pool_clear(iterpool); + if (! pegged_diff) + { + /* We can't be tacking URLs onto base paths! */ + if (svn_path_is_url(path)) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Path '%s' not relative to base URLs"), + path); + + if (svn_path_is_url(old_target)) + target1 = svn_path_url_add_component2( + old_target, + svn_relpath_canonicalize(path, iterpool), + iterpool); + else + target1 = svn_dirent_join(old_target, path, iterpool); + + if (svn_path_is_url(new_target)) + target2 = svn_path_url_add_component2( + new_target, + svn_relpath_canonicalize(path, iterpool), + iterpool); + else + target2 = svn_dirent_join(new_target, path, iterpool); + + if (opt_state->diff.summarize) + { + summarize_baton.anchor = target1; + + SVN_ERR(svn_client_diff_summarize2( + target1, + &opt_state->start_revision, + target2, + &opt_state->end_revision, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->changelists, + summarize_func, &summarize_baton, + ctx, iterpool)); + } + else + SVN_ERR(svn_client_diff6( + options, + target1, + &(opt_state->start_revision), + target2, + &(opt_state->end_revision), + NULL, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->diff.no_diff_added, + opt_state->diff.no_diff_deleted, + show_copies_as_adds, + opt_state->force, + ignore_properties, + opt_state->diff.properties_only, + opt_state->diff.use_git_diff_format, + svn_cmdline_output_encoding(pool), + outstream, + errstream, + opt_state->changelists, + ctx, iterpool)); + } + else + { + const char *truepath; + svn_opt_revision_t peg_revision; + + /* First check for a peg revision. */ + SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path, + iterpool)); + + /* Set the default peg revision if one was not specified. */ + if (peg_revision.kind == svn_opt_revision_unspecified) + peg_revision.kind = svn_path_is_url(path) + ? svn_opt_revision_head : svn_opt_revision_working; + + if (opt_state->diff.summarize) + { + summarize_baton.anchor = truepath; + SVN_ERR(svn_client_diff_summarize_peg2( + truepath, + &peg_revision, + &opt_state->start_revision, + &opt_state->end_revision, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->changelists, + summarize_func, &summarize_baton, + ctx, iterpool)); + } + else + SVN_ERR(svn_client_diff_peg6( + options, + truepath, + &peg_revision, + &opt_state->start_revision, + &opt_state->end_revision, + NULL, + opt_state->depth, + ! opt_state->diff.notice_ancestry, + opt_state->diff.no_diff_added, + opt_state->diff.no_diff_deleted, + show_copies_as_adds, + opt_state->force, + ignore_properties, + opt_state->diff.properties_only, + opt_state->diff.use_git_diff_format, + svn_cmdline_output_encoding(pool), + outstream, + errstream, + opt_state->changelists, + ctx, iterpool)); + } + } + + if (opt_state->xml) + { + svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); + svn_xml_make_close_tag(&sb, pool, "paths"); + SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); + SVN_ERR(svn_cl__xml_print_footer("diff", pool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} |