diff-cmd.c revision 299742
1/* 2 * diff-cmd.c -- Display context diff of a file 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include "svn_pools.h" 31#include "svn_client.h" 32#include "svn_string.h" 33#include "svn_dirent_uri.h" 34#include "svn_path.h" 35#include "svn_error_codes.h" 36#include "svn_error.h" 37#include "svn_types.h" 38#include "svn_cmdline.h" 39#include "svn_xml.h" 40#include "svn_hash.h" 41#include "cl.h" 42 43#include "svn_private_config.h" 44 45 46/*** Code. ***/ 47 48/* Convert KIND into a single character for display to the user. */ 49static char 50kind_to_char(svn_client_diff_summarize_kind_t kind) 51{ 52 switch (kind) 53 { 54 case svn_client_diff_summarize_kind_modified: 55 return 'M'; 56 57 case svn_client_diff_summarize_kind_added: 58 return 'A'; 59 60 case svn_client_diff_summarize_kind_deleted: 61 return 'D'; 62 63 default: 64 return ' '; 65 } 66} 67 68/* Convert KIND into a word describing the kind to the user. */ 69static const char * 70kind_to_word(svn_client_diff_summarize_kind_t kind) 71{ 72 switch (kind) 73 { 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"; 78 } 79} 80 81/* Baton for summarize_xml and summarize_regular */ 82struct summarize_baton_t 83{ 84 const char *anchor; 85 svn_boolean_t ignore_properties; 86}; 87 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. */ 92static svn_error_t * 93summarize_xml(const svn_client_diff_summarize_t *summary, 94 void *baton, 95 apr_pool_t *pool) 96{ 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; 103 104 if (b->ignore_properties && 105 summary->summarize_kind == svn_client_diff_summarize_kind_normal) 106 return SVN_NO_ERROR; 107 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)) 111 { 112 path = svn_path_url_add_component2(path, summary->path, pool); 113 } 114 else 115 { 116 path = svn_dirent_join(path, summary->path, pool); 117 118 /* Convert non-urls to local style, so that things like "" 119 show up as "." */ 120 path = svn_dirent_local_style(path, pool); 121 } 122 123 prop_change = summary->prop_changed ? "modified" : "none"; 124 if (b->ignore_properties) 125 prop_change = "none"; 126 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, 131 SVN_VA_NULL); 132 133 svn_xml_escape_cdata_cstring(&sb, path, pool); 134 svn_xml_make_close_tag(&sb, pool, "path"); 135 136 return svn_cl__error_checked_fputs(sb->data, stdout); 137} 138 139/* Print summary information about a given change, implements the 140 * svn_client_diff_summarize_func_t interface. */ 141static svn_error_t * 142summarize_regular(const svn_client_diff_summarize_t *summary, 143 void *baton, 144 apr_pool_t *pool) 145{ 146 struct summarize_baton_t *b = baton; 147 const char *path = b->anchor; 148 char prop_change; 149 150 if (b->ignore_properties && 151 summary->summarize_kind == svn_client_diff_summarize_kind_normal) 152 return SVN_NO_ERROR; 153 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)) 157 { 158 path = svn_path_url_add_component2(path, summary->path, pool); 159 } 160 else 161 { 162 path = svn_dirent_join(path, summary->path, pool); 163 164 /* Convert non-urls to local style, so that things like "" 165 show up as "." */ 166 path = svn_dirent_local_style(path, pool); 167 } 168 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. */ 172 173 prop_change = summary->prop_changed ? 'M' : ' '; 174 if (b->ignore_properties) 175 prop_change = ' '; 176 177 SVN_ERR(svn_cmdline_printf(pool, "%c%c %s\n", 178 kind_to_char(summary->summarize_kind), 179 prop_change, path)); 180 181 return svn_cmdline_fflush(stdout); 182} 183 184/* An svn_opt_subcommand_t to handle the 'diff' command. 185 This implements the `svn_opt_subcommand_t' interface. */ 186svn_error_t * 187svn_cl__diff(apr_getopt_t *os, 188 void *baton, 189 apr_pool_t *pool) 190{ 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; 205 int i; 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); 209 210 if (opt_state->extensions) 211 options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); 212 else 213 options = NULL; 214 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)); 219 220 if (opt_state->xml) 221 { 222 svn_stringbuf_t *sb; 223 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")); 229 230 SVN_ERR(svn_cl__xml_print_header("diff", pool)); 231 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)); 235 } 236 237 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 238 opt_state->targets, 239 ctx, FALSE, pool)); 240 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) 247 { 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. */ 250 251 SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target, 252 APR_ARRAY_IDX(targets, 0, const char *), 253 pool)); 254 SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target, 255 APR_ARRAY_IDX(targets, 1, const char *), 256 pool)); 257 targets->nelts = 0; 258 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; 265 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; 269 } 270 else if (opt_state->old_target) 271 { 272 apr_array_header_t *tmp, *tmp2; 273 svn_opt_revision_t old_rev, new_rev; 274 275 /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]] 276 [PATH...]' case matches. */ 277 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); 283 284 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp, 285 ctx, FALSE, pool)); 286 287 /* Check if either or both targets were skipped (e.g. because they 288 * were .svn directories). */ 289 if (tmp2->nelts < 2) 290 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); 291 292 SVN_ERR(svn_opt_parse_path(&old_rev, &old_target, 293 APR_ARRAY_IDX(tmp2, 0, const char *), 294 pool)); 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 *), 299 pool)); 300 if (new_rev.kind != svn_opt_revision_unspecified) 301 opt_state->end_revision = new_rev; 302 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; 314 } 315 else if (opt_state->new_target) 316 { 317 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 318 _("'--new' option only valid with " 319 "'--old' option")); 320 } 321 else 322 { 323 svn_boolean_t working_copy_present; 324 325 /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */ 326 327 /* Here each target is a pegged object. Find out the starting 328 and ending paths for each target. */ 329 330 svn_opt_push_implicit_dot_target(targets, pool); 331 332 old_target = ""; 333 new_target = ""; 334 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'.")); 339 340 working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0, 341 const char *)); 342 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; 349 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)) 355 pegged_diff = TRUE; 356 357 } 358 359 /* Should we ignore the content-type when deciding what to diff? */ 360 if (opt_state->force) 361 { 362 ignore_content_type = TRUE; 363 } 364 else if (ctx->config) 365 { 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, 371 FALSE)); 372 } 373 else 374 { 375 ignore_content_type = FALSE; 376 } 377 378 svn_opt_push_implicit_dot_target(targets, pool); 379 380 iterpool = svn_pool_create(pool); 381 382 for (i = 0; i < targets->nelts; ++i) 383 { 384 const char *path = APR_ARRAY_IDX(targets, i, const char *); 385 const char *target1, *target2; 386 387 svn_pool_clear(iterpool); 388 if (! pegged_diff) 389 { 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"), 394 path); 395 396 if (svn_path_is_url(old_target)) 397 target1 = svn_path_url_add_component2( 398 old_target, 399 svn_relpath_canonicalize(path, iterpool), 400 iterpool); 401 else 402 target1 = svn_dirent_join(old_target, path, iterpool); 403 404 if (svn_path_is_url(new_target)) 405 target2 = svn_path_url_add_component2( 406 new_target, 407 svn_relpath_canonicalize(path, iterpool), 408 iterpool); 409 else 410 target2 = svn_dirent_join(new_target, path, iterpool); 411 412 if (opt_state->diff.summarize) 413 { 414 summarize_baton.anchor = target1; 415 summarize_baton.ignore_properties = ignore_properties; 416 417 SVN_ERR(svn_client_diff_summarize2( 418 target1, 419 &opt_state->start_revision, 420 target2, 421 &opt_state->end_revision, 422 opt_state->depth, 423 ! opt_state->diff.notice_ancestry, 424 opt_state->changelists, 425 summarize_func, &summarize_baton, 426 ctx, iterpool)); 427 } 428 else 429 SVN_ERR(svn_client_diff6( 430 options, 431 target1, 432 &(opt_state->start_revision), 433 target2, 434 &(opt_state->end_revision), 435 NULL, 436 opt_state->depth, 437 ! opt_state->diff.notice_ancestry, 438 opt_state->diff.no_diff_added, 439 opt_state->diff.no_diff_deleted, 440 show_copies_as_adds, 441 ignore_content_type, 442 ignore_properties, 443 opt_state->diff.properties_only, 444 opt_state->diff.use_git_diff_format, 445 svn_cmdline_output_encoding(pool), 446 outstream, 447 errstream, 448 opt_state->changelists, 449 ctx, iterpool)); 450 } 451 else 452 { 453 const char *truepath; 454 svn_opt_revision_t peg_revision; 455 456 /* First check for a peg revision. */ 457 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path, 458 iterpool)); 459 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; 464 465 if (opt_state->diff.summarize) 466 { 467 summarize_baton.anchor = truepath; 468 summarize_baton.ignore_properties = ignore_properties; 469 SVN_ERR(svn_client_diff_summarize_peg2( 470 truepath, 471 &peg_revision, 472 &opt_state->start_revision, 473 &opt_state->end_revision, 474 opt_state->depth, 475 ! opt_state->diff.notice_ancestry, 476 opt_state->changelists, 477 summarize_func, &summarize_baton, 478 ctx, iterpool)); 479 } 480 else 481 SVN_ERR(svn_client_diff_peg6( 482 options, 483 truepath, 484 &peg_revision, 485 &opt_state->start_revision, 486 &opt_state->end_revision, 487 NULL, 488 opt_state->depth, 489 ! opt_state->diff.notice_ancestry, 490 opt_state->diff.no_diff_added, 491 opt_state->diff.no_diff_deleted, 492 show_copies_as_adds, 493 ignore_content_type, 494 ignore_properties, 495 opt_state->diff.properties_only, 496 opt_state->diff.use_git_diff_format, 497 svn_cmdline_output_encoding(pool), 498 outstream, 499 errstream, 500 opt_state->changelists, 501 ctx, iterpool)); 502 } 503 } 504 505 if (opt_state->xml) 506 { 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)); 511 } 512 513 svn_pool_destroy(iterpool); 514 515 return SVN_NO_ERROR; 516} 517