2 * diff-cmd.c -- Display context diff of a file
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
30 #include "svn_pools.h"
31 #include "svn_client.h"
32 #include "svn_string.h"
33 #include "svn_dirent_uri.h"
35 #include "svn_error_codes.h"
36 #include "svn_error.h"
37 #include "svn_types.h"
38 #include "svn_cmdline.h"
43 #include "svn_private_config.h"
48 /* Convert KIND into a single character for display to the user. */
50 kind_to_char(svn_client_diff_summarize_kind_t kind)
54 case svn_client_diff_summarize_kind_modified:
57 case svn_client_diff_summarize_kind_added:
60 case svn_client_diff_summarize_kind_deleted:
68 /* Convert KIND into a word describing the kind to the user. */
70 kind_to_word(svn_client_diff_summarize_kind_t kind)
74 case svn_client_diff_summarize_kind_modified: return "modified";
75 case svn_client_diff_summarize_kind_added: return "added";
76 case svn_client_diff_summarize_kind_deleted: return "deleted";
77 default: return "none";
81 /* Baton for summarize_xml and summarize_regular */
82 struct summarize_baton_t
85 svn_boolean_t ignore_properties;
88 /* Print summary information about a given change as XML, implements the
89 * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *'
90 * representing the either the path to the working copy root or the url
91 * the path the working copy root corresponds to. */
93 summarize_xml(const svn_client_diff_summarize_t *summary,
97 struct summarize_baton_t *b = baton;
98 /* Full path to the object being diffed. This is created by taking the
99 * baton, and appending the target's relative path. */
100 const char *path = b->anchor;
101 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
102 const char *prop_change;
104 if (b->ignore_properties &&
105 summary->summarize_kind == svn_client_diff_summarize_kind_normal)
108 /* Tack on the target path, so we can differentiate between different parts
109 * of the output when we're given multiple targets. */
110 if (svn_path_is_url(path))
112 path = svn_path_url_add_component2(path, summary->path, pool);
116 path = svn_dirent_join(path, summary->path, pool);
118 /* Convert non-urls to local style, so that things like ""
120 path = svn_dirent_local_style(path, pool);
123 prop_change = summary->prop_changed ? "modified" : "none";
124 if (b->ignore_properties)
125 prop_change = "none";
127 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
128 "kind", svn_cl__node_kind_str_xml(summary->node_kind),
129 "item", kind_to_word(summary->summarize_kind),
130 "props", prop_change,
133 svn_xml_escape_cdata_cstring(&sb, path, pool);
134 svn_xml_make_close_tag(&sb, pool, "path");
136 return svn_cl__error_checked_fputs(sb->data, stdout);
139 /* Print summary information about a given change, implements the
140 * svn_client_diff_summarize_func_t interface. */
142 summarize_regular(const svn_client_diff_summarize_t *summary,
146 struct summarize_baton_t *b = baton;
147 const char *path = b->anchor;
150 if (b->ignore_properties &&
151 summary->summarize_kind == svn_client_diff_summarize_kind_normal)
154 /* Tack on the target path, so we can differentiate between different parts
155 * of the output when we're given multiple targets. */
156 if (svn_path_is_url(path))
158 path = svn_path_url_add_component2(path, summary->path, pool);
162 path = svn_dirent_join(path, summary->path, pool);
164 /* Convert non-urls to local style, so that things like ""
166 path = svn_dirent_local_style(path, pool);
169 /* Note: This output format tries to look like the output of 'svn status',
170 * thus the blank spaces where information that is not relevant to
171 * a diff summary would go. */
173 prop_change = summary->prop_changed ? 'M' : ' ';
174 if (b->ignore_properties)
177 SVN_ERR(svn_cmdline_printf(pool, "%c%c %s\n",
178 kind_to_char(summary->summarize_kind),
181 return svn_cmdline_fflush(stdout);
184 /* An svn_opt_subcommand_t to handle the 'diff' command.
185 This implements the `svn_opt_subcommand_t' interface. */
187 svn_cl__diff(apr_getopt_t *os,
191 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
192 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
193 apr_array_header_t *options;
194 apr_array_header_t *targets;
195 svn_stream_t *outstream;
196 svn_stream_t *errstream;
197 const char *old_target, *new_target;
198 apr_pool_t *iterpool;
199 svn_boolean_t pegged_diff = FALSE;
200 svn_boolean_t ignore_content_type;
201 svn_boolean_t show_copies_as_adds =
202 opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds;
203 svn_boolean_t ignore_properties =
204 opt_state->diff.patch_compatible || opt_state->diff.ignore_properties;
206 struct summarize_baton_t summarize_baton;
207 const svn_client_diff_summarize_func_t summarize_func =
208 (opt_state->xml ? summarize_xml : summarize_regular);
210 if (opt_state->extensions)
211 options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
215 /* Get streams representing stdout and stderr, which is where
216 we'll have the external 'diff' program print to. */
217 SVN_ERR(svn_stream_for_stdout(&outstream, pool));
218 SVN_ERR(svn_stream_for_stderr(&errstream, pool));
224 /* Check that the --summarize is passed as well. */
225 if (!opt_state->diff.summarize)
226 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
227 _("'--xml' option only valid with "
228 "'--summarize' option"));
230 SVN_ERR(svn_cl__xml_print_header("diff", pool));
232 sb = svn_stringbuf_create_empty(pool);
233 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", SVN_VA_NULL);
234 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
237 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
241 if (! opt_state->old_target && ! opt_state->new_target
242 && (targets->nelts == 2)
243 && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
244 || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
245 && opt_state->start_revision.kind == svn_opt_revision_unspecified
246 && opt_state->end_revision.kind == svn_opt_revision_unspecified)
248 /* A 2-target diff where one or both targets are URLs. These are
249 * shorthands for some 'svn diff --old X --new Y' invocations. */
251 SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
252 APR_ARRAY_IDX(targets, 0, const char *),
254 SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
255 APR_ARRAY_IDX(targets, 1, const char *),
259 /* Set default start/end revisions based on target types, in the same
260 * manner as done for the corresponding '--old X --new Y' cases,
261 * (note that we have an explicit --new target) */
262 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
263 opt_state->start_revision.kind = svn_path_is_url(old_target)
264 ? svn_opt_revision_head : svn_opt_revision_working;
266 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
267 opt_state->end_revision.kind = svn_path_is_url(new_target)
268 ? svn_opt_revision_head : svn_opt_revision_working;
270 else if (opt_state->old_target)
272 apr_array_header_t *tmp, *tmp2;
273 svn_opt_revision_t old_rev, new_rev;
275 /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
276 [PATH...]' case matches. */
278 tmp = apr_array_make(pool, 2, sizeof(const char *));
279 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
280 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
281 ? opt_state->new_target
282 : opt_state->old_target);
284 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
287 /* Check if either or both targets were skipped (e.g. because they
288 * were .svn directories). */
290 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
292 SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
293 APR_ARRAY_IDX(tmp2, 0, const char *),
295 if (old_rev.kind != svn_opt_revision_unspecified)
296 opt_state->start_revision = old_rev;
297 SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
298 APR_ARRAY_IDX(tmp2, 1, const char *),
300 if (new_rev.kind != svn_opt_revision_unspecified)
301 opt_state->end_revision = new_rev;
303 /* For URLs, default to HEAD. For WC paths, default to WORKING if
304 * new target is explicit; if new target is implicitly the same as
305 * old target, then default the old to BASE and new to WORKING. */
306 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
307 opt_state->start_revision.kind = svn_path_is_url(old_target)
308 ? svn_opt_revision_head
309 : (opt_state->new_target
310 ? svn_opt_revision_working : svn_opt_revision_base);
311 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
312 opt_state->end_revision.kind = svn_path_is_url(new_target)
313 ? svn_opt_revision_head : svn_opt_revision_working;
315 else if (opt_state->new_target)
317 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
318 _("'--new' option only valid with "
323 svn_boolean_t working_copy_present;
325 /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
327 /* Here each target is a pegged object. Find out the starting
328 and ending paths for each target. */
330 svn_opt_push_implicit_dot_target(targets, pool);
335 SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
336 _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
337 "target types. Try using the --old and --new options or one of "
338 "the shorthand invocations listed in 'svn help diff'."));
340 working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
343 if (opt_state->start_revision.kind == svn_opt_revision_unspecified
344 && working_copy_present)
345 opt_state->start_revision.kind = svn_opt_revision_base;
346 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
347 opt_state->end_revision.kind = working_copy_present
348 ? svn_opt_revision_working : svn_opt_revision_head;
350 /* Determine if we need to do pegged diffs. */
351 if ((opt_state->start_revision.kind != svn_opt_revision_base
352 && opt_state->start_revision.kind != svn_opt_revision_working)
353 || (opt_state->end_revision.kind != svn_opt_revision_base
354 && opt_state->end_revision.kind != svn_opt_revision_working))
359 /* Should we ignore the content-type when deciding what to diff? */
360 if (opt_state->force)
362 ignore_content_type = TRUE;
364 else if (ctx->config)
366 SVN_ERR(svn_config_get_bool(svn_hash_gets(ctx->config,
367 SVN_CONFIG_CATEGORY_CONFIG),
368 &ignore_content_type,
369 SVN_CONFIG_SECTION_MISCELLANY,
370 SVN_CONFIG_OPTION_DIFF_IGNORE_CONTENT_TYPE,
375 ignore_content_type = FALSE;
378 svn_opt_push_implicit_dot_target(targets, pool);
380 iterpool = svn_pool_create(pool);
382 for (i = 0; i < targets->nelts; ++i)
384 const char *path = APR_ARRAY_IDX(targets, i, const char *);
385 const char *target1, *target2;
387 svn_pool_clear(iterpool);
390 /* We can't be tacking URLs onto base paths! */
391 if (svn_path_is_url(path))
392 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
393 _("Path '%s' not relative to base URLs"),
396 if (svn_path_is_url(old_target))
397 target1 = svn_path_url_add_component2(
399 svn_relpath_canonicalize(path, iterpool),
402 target1 = svn_dirent_join(old_target, path, iterpool);
404 if (svn_path_is_url(new_target))
405 target2 = svn_path_url_add_component2(
407 svn_relpath_canonicalize(path, iterpool),
410 target2 = svn_dirent_join(new_target, path, iterpool);
412 if (opt_state->diff.summarize)
414 summarize_baton.anchor = target1;
415 summarize_baton.ignore_properties = ignore_properties;
417 SVN_ERR(svn_client_diff_summarize2(
419 &opt_state->start_revision,
421 &opt_state->end_revision,
423 ! opt_state->diff.notice_ancestry,
424 opt_state->changelists,
425 summarize_func, &summarize_baton,
429 SVN_ERR(svn_client_diff6(
432 &(opt_state->start_revision),
434 &(opt_state->end_revision),
437 ! opt_state->diff.notice_ancestry,
438 opt_state->diff.no_diff_added,
439 opt_state->diff.no_diff_deleted,
443 opt_state->diff.properties_only,
444 opt_state->diff.use_git_diff_format,
445 svn_cmdline_output_encoding(pool),
448 opt_state->changelists,
453 const char *truepath;
454 svn_opt_revision_t peg_revision;
456 /* First check for a peg revision. */
457 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
460 /* Set the default peg revision if one was not specified. */
461 if (peg_revision.kind == svn_opt_revision_unspecified)
462 peg_revision.kind = svn_path_is_url(path)
463 ? svn_opt_revision_head : svn_opt_revision_working;
465 if (opt_state->diff.summarize)
467 summarize_baton.anchor = truepath;
468 summarize_baton.ignore_properties = ignore_properties;
469 SVN_ERR(svn_client_diff_summarize_peg2(
472 &opt_state->start_revision,
473 &opt_state->end_revision,
475 ! opt_state->diff.notice_ancestry,
476 opt_state->changelists,
477 summarize_func, &summarize_baton,
481 SVN_ERR(svn_client_diff_peg6(
485 &opt_state->start_revision,
486 &opt_state->end_revision,
489 ! opt_state->diff.notice_ancestry,
490 opt_state->diff.no_diff_added,
491 opt_state->diff.no_diff_deleted,
495 opt_state->diff.properties_only,
496 opt_state->diff.use_git_diff_format,
497 svn_cmdline_output_encoding(pool),
500 opt_state->changelists,
507 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
508 svn_xml_make_close_tag(&sb, pool, "paths");
509 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
510 SVN_ERR(svn_cl__xml_print_footer("diff", pool));
513 svn_pool_destroy(iterpool);