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));
236 if (opt_state->diff.summarize)
238 if (opt_state->diff.use_git_diff_format)
239 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
240 _("'%s' not valid with '--summarize' option"),
242 if (opt_state->diff.patch_compatible)
243 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
244 _("'%s' not valid with '--summarize' option"),
245 "--patch-compatible");
246 if (opt_state->diff.show_copies_as_adds)
247 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
248 _("'%s' not valid with '--summarize' option"),
249 "--show-copies-as-adds");
250 if (opt_state->diff.internal_diff)
251 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
252 _("'%s' not valid with '--summarize' option"),
254 if (opt_state->diff.diff_cmd)
255 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
256 _("'%s' not valid with '--summarize' option"),
258 if (opt_state->diff.no_diff_added)
259 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
260 _("'%s' not valid with '--summarize' option"),
262 if (opt_state->diff.no_diff_deleted)
263 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
264 _("'%s' not valid with '--summarize' option"),
265 "--no-diff-deleted");
266 if (opt_state->force)
267 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
268 _("'%s' not valid with '--summarize' option"),
270 /* Not handling ignore-properties, and properties-only as there should
271 be a patch adding support for these being applied soon */
274 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
278 if (! opt_state->old_target && ! opt_state->new_target
279 && (targets->nelts == 2)
280 && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
281 || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
282 && opt_state->start_revision.kind == svn_opt_revision_unspecified
283 && opt_state->end_revision.kind == svn_opt_revision_unspecified)
285 /* A 2-target diff where one or both targets are URLs. These are
286 * shorthands for some 'svn diff --old X --new Y' invocations. */
288 SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
289 APR_ARRAY_IDX(targets, 0, const char *),
291 SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
292 APR_ARRAY_IDX(targets, 1, const char *),
296 /* Set default start/end revisions based on target types, in the same
297 * manner as done for the corresponding '--old X --new Y' cases,
298 * (note that we have an explicit --new target) */
299 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
300 opt_state->start_revision.kind = svn_path_is_url(old_target)
301 ? svn_opt_revision_head : svn_opt_revision_working;
303 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
304 opt_state->end_revision.kind = svn_path_is_url(new_target)
305 ? svn_opt_revision_head : svn_opt_revision_working;
307 else if (opt_state->old_target)
309 apr_array_header_t *tmp, *tmp2;
310 svn_opt_revision_t old_rev, new_rev;
312 /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
313 [PATH...]' case matches. */
315 tmp = apr_array_make(pool, 2, sizeof(const char *));
316 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
317 APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
318 ? opt_state->new_target
319 : opt_state->old_target);
321 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
324 /* Check if either or both targets were skipped (e.g. because they
325 * were .svn directories). */
327 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
329 SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
330 APR_ARRAY_IDX(tmp2, 0, const char *),
332 if (old_rev.kind != svn_opt_revision_unspecified)
333 opt_state->start_revision = old_rev;
334 SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
335 APR_ARRAY_IDX(tmp2, 1, const char *),
337 if (new_rev.kind != svn_opt_revision_unspecified)
338 opt_state->end_revision = new_rev;
340 /* For URLs, default to HEAD. For WC paths, default to WORKING if
341 * new target is explicit; if new target is implicitly the same as
342 * old target, then default the old to BASE and new to WORKING. */
343 if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
344 opt_state->start_revision.kind = svn_path_is_url(old_target)
345 ? svn_opt_revision_head
346 : (opt_state->new_target
347 ? svn_opt_revision_working : svn_opt_revision_base);
348 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
349 opt_state->end_revision.kind = svn_path_is_url(new_target)
350 ? svn_opt_revision_head : svn_opt_revision_working;
352 else if (opt_state->new_target)
354 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
355 _("'--new' option only valid with "
360 svn_boolean_t working_copy_present;
362 /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
364 /* Here each target is a pegged object. Find out the starting
365 and ending paths for each target. */
367 svn_opt_push_implicit_dot_target(targets, pool);
372 SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
373 _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
374 "target types. Try using the --old and --new options or one of "
375 "the shorthand invocations listed in 'svn help diff'."));
377 working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
380 if (opt_state->start_revision.kind == svn_opt_revision_unspecified
381 && working_copy_present)
382 opt_state->start_revision.kind = svn_opt_revision_base;
383 if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
384 opt_state->end_revision.kind = working_copy_present
385 ? svn_opt_revision_working : svn_opt_revision_head;
387 /* Determine if we need to do pegged diffs. */
388 if ((opt_state->start_revision.kind != svn_opt_revision_base
389 && opt_state->start_revision.kind != svn_opt_revision_working)
390 || (opt_state->end_revision.kind != svn_opt_revision_base
391 && opt_state->end_revision.kind != svn_opt_revision_working))
396 /* Should we ignore the content-type when deciding what to diff? */
397 if (opt_state->force)
399 ignore_content_type = TRUE;
401 else if (ctx->config)
403 SVN_ERR(svn_config_get_bool(svn_hash_gets(ctx->config,
404 SVN_CONFIG_CATEGORY_CONFIG),
405 &ignore_content_type,
406 SVN_CONFIG_SECTION_MISCELLANY,
407 SVN_CONFIG_OPTION_DIFF_IGNORE_CONTENT_TYPE,
412 ignore_content_type = FALSE;
415 svn_opt_push_implicit_dot_target(targets, pool);
417 iterpool = svn_pool_create(pool);
419 for (i = 0; i < targets->nelts; ++i)
421 const char *path = APR_ARRAY_IDX(targets, i, const char *);
422 const char *target1, *target2;
424 svn_pool_clear(iterpool);
427 /* We can't be tacking URLs onto base paths! */
428 if (svn_path_is_url(path))
429 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
430 _("Path '%s' not relative to base URLs"),
433 if (svn_path_is_url(old_target))
434 target1 = svn_path_url_add_component2(
436 svn_relpath_canonicalize(path, iterpool),
439 target1 = svn_dirent_join(old_target, path, iterpool);
441 if (svn_path_is_url(new_target))
442 target2 = svn_path_url_add_component2(
444 svn_relpath_canonicalize(path, iterpool),
447 target2 = svn_dirent_join(new_target, path, iterpool);
449 if (opt_state->diff.summarize)
451 summarize_baton.anchor = target1;
452 summarize_baton.ignore_properties = ignore_properties;
454 SVN_ERR(svn_client_diff_summarize2(
456 &opt_state->start_revision,
458 &opt_state->end_revision,
460 ! opt_state->diff.notice_ancestry,
461 opt_state->changelists,
462 summarize_func, &summarize_baton,
466 SVN_ERR(svn_client_diff6(
469 &(opt_state->start_revision),
471 &(opt_state->end_revision),
474 ! opt_state->diff.notice_ancestry,
475 opt_state->diff.no_diff_added,
476 opt_state->diff.no_diff_deleted,
480 opt_state->diff.properties_only,
481 opt_state->diff.use_git_diff_format,
482 svn_cmdline_output_encoding(pool),
485 opt_state->changelists,
490 const char *truepath;
491 svn_opt_revision_t peg_revision;
493 /* First check for a peg revision. */
494 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
497 /* Set the default peg revision if one was not specified. */
498 if (peg_revision.kind == svn_opt_revision_unspecified)
499 peg_revision.kind = svn_path_is_url(path)
500 ? svn_opt_revision_head : svn_opt_revision_working;
502 if (opt_state->diff.summarize)
504 summarize_baton.anchor = truepath;
505 summarize_baton.ignore_properties = ignore_properties;
506 SVN_ERR(svn_client_diff_summarize_peg2(
509 &opt_state->start_revision,
510 &opt_state->end_revision,
512 ! opt_state->diff.notice_ancestry,
513 opt_state->changelists,
514 summarize_func, &summarize_baton,
518 SVN_ERR(svn_client_diff_peg6(
522 &opt_state->start_revision,
523 &opt_state->end_revision,
526 ! opt_state->diff.notice_ancestry,
527 opt_state->diff.no_diff_added,
528 opt_state->diff.no_diff_deleted,
532 opt_state->diff.properties_only,
533 opt_state->diff.use_git_diff_format,
534 svn_cmdline_output_encoding(pool),
537 opt_state->changelists,
544 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
545 svn_xml_make_close_tag(&sb, pool, "paths");
546 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
547 SVN_ERR(svn_cl__xml_print_footer("diff", pool));
550 svn_pool_destroy(iterpool);