aboutsummaryrefslogtreecommitdiff
path: root/subversion/svn/diff-cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/diff-cmd.c')
-rw-r--r--subversion/svn/diff-cmd.c476
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;
+}