1251881Speter/* 2251881Speter * diff-cmd.c -- Display context diff of a file 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include "svn_pools.h" 31251881Speter#include "svn_client.h" 32251881Speter#include "svn_string.h" 33251881Speter#include "svn_dirent_uri.h" 34251881Speter#include "svn_path.h" 35251881Speter#include "svn_error_codes.h" 36251881Speter#include "svn_error.h" 37251881Speter#include "svn_types.h" 38251881Speter#include "svn_cmdline.h" 39251881Speter#include "svn_xml.h" 40251881Speter#include "cl.h" 41251881Speter 42251881Speter#include "svn_private_config.h" 43251881Speter 44251881Speter 45251881Speter/*** Code. ***/ 46251881Speter 47251881Speter/* Convert KIND into a single character for display to the user. */ 48251881Speterstatic char 49251881Speterkind_to_char(svn_client_diff_summarize_kind_t kind) 50251881Speter{ 51251881Speter switch (kind) 52251881Speter { 53251881Speter case svn_client_diff_summarize_kind_modified: 54251881Speter return 'M'; 55251881Speter 56251881Speter case svn_client_diff_summarize_kind_added: 57251881Speter return 'A'; 58251881Speter 59251881Speter case svn_client_diff_summarize_kind_deleted: 60251881Speter return 'D'; 61251881Speter 62251881Speter default: 63251881Speter return ' '; 64251881Speter } 65251881Speter} 66251881Speter 67251881Speter/* Convert KIND into a word describing the kind to the user. */ 68251881Speterstatic const char * 69251881Speterkind_to_word(svn_client_diff_summarize_kind_t kind) 70251881Speter{ 71251881Speter switch (kind) 72251881Speter { 73251881Speter case svn_client_diff_summarize_kind_modified: return "modified"; 74251881Speter case svn_client_diff_summarize_kind_added: return "added"; 75251881Speter case svn_client_diff_summarize_kind_deleted: return "deleted"; 76251881Speter default: return "none"; 77251881Speter } 78251881Speter} 79251881Speter 80251881Speter/* Baton for summarize_xml and summarize_regular */ 81251881Speterstruct summarize_baton_t 82251881Speter{ 83251881Speter const char *anchor; 84251881Speter}; 85251881Speter 86251881Speter/* Print summary information about a given change as XML, implements the 87251881Speter * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *' 88251881Speter * representing the either the path to the working copy root or the url 89251881Speter * the path the working copy root corresponds to. */ 90251881Speterstatic svn_error_t * 91251881Spetersummarize_xml(const svn_client_diff_summarize_t *summary, 92251881Speter void *baton, 93251881Speter apr_pool_t *pool) 94251881Speter{ 95251881Speter struct summarize_baton_t *b = baton; 96251881Speter /* Full path to the object being diffed. This is created by taking the 97251881Speter * baton, and appending the target's relative path. */ 98251881Speter const char *path = b->anchor; 99251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 100251881Speter 101251881Speter /* Tack on the target path, so we can differentiate between different parts 102251881Speter * of the output when we're given multiple targets. */ 103251881Speter if (svn_path_is_url(path)) 104251881Speter { 105251881Speter path = svn_path_url_add_component2(path, summary->path, pool); 106251881Speter } 107251881Speter else 108251881Speter { 109251881Speter path = svn_dirent_join(path, summary->path, pool); 110251881Speter 111251881Speter /* Convert non-urls to local style, so that things like "" 112251881Speter show up as "." */ 113251881Speter path = svn_dirent_local_style(path, pool); 114251881Speter } 115251881Speter 116251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", 117251881Speter "kind", svn_cl__node_kind_str_xml(summary->node_kind), 118251881Speter "item", kind_to_word(summary->summarize_kind), 119251881Speter "props", summary->prop_changed ? "modified" : "none", 120251881Speter NULL); 121251881Speter 122251881Speter svn_xml_escape_cdata_cstring(&sb, path, pool); 123251881Speter svn_xml_make_close_tag(&sb, pool, "path"); 124251881Speter 125251881Speter return svn_cl__error_checked_fputs(sb->data, stdout); 126251881Speter} 127251881Speter 128251881Speter/* Print summary information about a given change, implements the 129251881Speter * svn_client_diff_summarize_func_t interface. */ 130251881Speterstatic svn_error_t * 131251881Spetersummarize_regular(const svn_client_diff_summarize_t *summary, 132251881Speter void *baton, 133251881Speter apr_pool_t *pool) 134251881Speter{ 135251881Speter struct summarize_baton_t *b = baton; 136251881Speter const char *path = b->anchor; 137251881Speter 138251881Speter /* Tack on the target path, so we can differentiate between different parts 139251881Speter * of the output when we're given multiple targets. */ 140251881Speter if (svn_path_is_url(path)) 141251881Speter { 142251881Speter path = svn_path_url_add_component2(path, summary->path, pool); 143251881Speter } 144251881Speter else 145251881Speter { 146251881Speter path = svn_dirent_join(path, summary->path, pool); 147251881Speter 148251881Speter /* Convert non-urls to local style, so that things like "" 149251881Speter show up as "." */ 150251881Speter path = svn_dirent_local_style(path, pool); 151251881Speter } 152251881Speter 153251881Speter /* Note: This output format tries to look like the output of 'svn status', 154251881Speter * thus the blank spaces where information that is not relevant to 155251881Speter * a diff summary would go. */ 156251881Speter 157251881Speter SVN_ERR(svn_cmdline_printf(pool, 158251881Speter "%c%c %s\n", 159251881Speter kind_to_char(summary->summarize_kind), 160251881Speter summary->prop_changed ? 'M' : ' ', 161251881Speter path)); 162251881Speter 163251881Speter return svn_cmdline_fflush(stdout); 164251881Speter} 165251881Speter 166251881Speter/* An svn_opt_subcommand_t to handle the 'diff' command. 167251881Speter This implements the `svn_opt_subcommand_t' interface. */ 168251881Spetersvn_error_t * 169251881Spetersvn_cl__diff(apr_getopt_t *os, 170251881Speter void *baton, 171251881Speter apr_pool_t *pool) 172251881Speter{ 173251881Speter svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 174251881Speter svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 175251881Speter apr_array_header_t *options; 176251881Speter apr_array_header_t *targets; 177251881Speter svn_stream_t *outstream; 178251881Speter svn_stream_t *errstream; 179251881Speter const char *old_target, *new_target; 180251881Speter apr_pool_t *iterpool; 181251881Speter svn_boolean_t pegged_diff = FALSE; 182251881Speter svn_boolean_t show_copies_as_adds = 183251881Speter opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds; 184251881Speter svn_boolean_t ignore_properties = 185251881Speter opt_state->diff.patch_compatible || opt_state->diff.ignore_properties; 186251881Speter int i; 187251881Speter struct summarize_baton_t summarize_baton; 188251881Speter const svn_client_diff_summarize_func_t summarize_func = 189251881Speter (opt_state->xml ? summarize_xml : summarize_regular); 190251881Speter 191251881Speter if (opt_state->extensions) 192251881Speter options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); 193251881Speter else 194251881Speter options = NULL; 195251881Speter 196251881Speter /* Get streams representing stdout and stderr, which is where 197251881Speter we'll have the external 'diff' program print to. */ 198251881Speter SVN_ERR(svn_stream_for_stdout(&outstream, pool)); 199251881Speter SVN_ERR(svn_stream_for_stderr(&errstream, pool)); 200251881Speter 201251881Speter if (opt_state->xml) 202251881Speter { 203251881Speter svn_stringbuf_t *sb; 204251881Speter 205251881Speter /* Check that the --summarize is passed as well. */ 206251881Speter if (!opt_state->diff.summarize) 207251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 208251881Speter _("'--xml' option only valid with " 209251881Speter "'--summarize' option")); 210251881Speter 211251881Speter SVN_ERR(svn_cl__xml_print_header("diff", pool)); 212251881Speter 213251881Speter sb = svn_stringbuf_create_empty(pool); 214251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL); 215251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 216251881Speter } 217251881Speter 218251881Speter SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 219251881Speter opt_state->targets, 220251881Speter ctx, FALSE, pool)); 221251881Speter 222251881Speter if (! opt_state->old_target && ! opt_state->new_target 223251881Speter && (targets->nelts == 2) 224251881Speter && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)) 225251881Speter || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *))) 226251881Speter && opt_state->start_revision.kind == svn_opt_revision_unspecified 227251881Speter && opt_state->end_revision.kind == svn_opt_revision_unspecified) 228251881Speter { 229251881Speter /* A 2-target diff where one or both targets are URLs. These are 230251881Speter * shorthands for some 'svn diff --old X --new Y' invocations. */ 231251881Speter 232251881Speter SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target, 233251881Speter APR_ARRAY_IDX(targets, 0, const char *), 234251881Speter pool)); 235251881Speter SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target, 236251881Speter APR_ARRAY_IDX(targets, 1, const char *), 237251881Speter pool)); 238251881Speter targets->nelts = 0; 239251881Speter 240251881Speter /* Set default start/end revisions based on target types, in the same 241251881Speter * manner as done for the corresponding '--old X --new Y' cases, 242251881Speter * (note that we have an explicit --new target) */ 243251881Speter if (opt_state->start_revision.kind == svn_opt_revision_unspecified) 244251881Speter opt_state->start_revision.kind = svn_path_is_url(old_target) 245251881Speter ? svn_opt_revision_head : svn_opt_revision_working; 246251881Speter 247251881Speter if (opt_state->end_revision.kind == svn_opt_revision_unspecified) 248251881Speter opt_state->end_revision.kind = svn_path_is_url(new_target) 249251881Speter ? svn_opt_revision_head : svn_opt_revision_working; 250251881Speter } 251251881Speter else if (opt_state->old_target) 252251881Speter { 253251881Speter apr_array_header_t *tmp, *tmp2; 254251881Speter svn_opt_revision_t old_rev, new_rev; 255251881Speter 256251881Speter /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]] 257251881Speter [PATH...]' case matches. */ 258251881Speter 259251881Speter tmp = apr_array_make(pool, 2, sizeof(const char *)); 260251881Speter APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target); 261251881Speter APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target 262251881Speter ? opt_state->new_target 263251881Speter : opt_state->old_target); 264251881Speter 265251881Speter SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp, 266251881Speter ctx, FALSE, pool)); 267251881Speter 268251881Speter /* Check if either or both targets were skipped (e.g. because they 269251881Speter * were .svn directories). */ 270251881Speter if (tmp2->nelts < 2) 271251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL); 272251881Speter 273251881Speter SVN_ERR(svn_opt_parse_path(&old_rev, &old_target, 274251881Speter APR_ARRAY_IDX(tmp2, 0, const char *), 275251881Speter pool)); 276251881Speter if (old_rev.kind != svn_opt_revision_unspecified) 277251881Speter opt_state->start_revision = old_rev; 278251881Speter SVN_ERR(svn_opt_parse_path(&new_rev, &new_target, 279251881Speter APR_ARRAY_IDX(tmp2, 1, const char *), 280251881Speter pool)); 281251881Speter if (new_rev.kind != svn_opt_revision_unspecified) 282251881Speter opt_state->end_revision = new_rev; 283251881Speter 284251881Speter /* For URLs, default to HEAD. For WC paths, default to WORKING if 285251881Speter * new target is explicit; if new target is implicitly the same as 286251881Speter * old target, then default the old to BASE and new to WORKING. */ 287251881Speter if (opt_state->start_revision.kind == svn_opt_revision_unspecified) 288251881Speter opt_state->start_revision.kind = svn_path_is_url(old_target) 289251881Speter ? svn_opt_revision_head 290251881Speter : (opt_state->new_target 291251881Speter ? svn_opt_revision_working : svn_opt_revision_base); 292251881Speter if (opt_state->end_revision.kind == svn_opt_revision_unspecified) 293251881Speter opt_state->end_revision.kind = svn_path_is_url(new_target) 294251881Speter ? svn_opt_revision_head : svn_opt_revision_working; 295251881Speter } 296251881Speter else if (opt_state->new_target) 297251881Speter { 298251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 299251881Speter _("'--new' option only valid with " 300251881Speter "'--old' option")); 301251881Speter } 302251881Speter else 303251881Speter { 304251881Speter svn_boolean_t working_copy_present; 305251881Speter 306251881Speter /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */ 307251881Speter 308251881Speter /* Here each target is a pegged object. Find out the starting 309251881Speter and ending paths for each target. */ 310251881Speter 311251881Speter svn_opt_push_implicit_dot_target(targets, pool); 312251881Speter 313251881Speter old_target = ""; 314251881Speter new_target = ""; 315251881Speter 316251881Speter SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets), 317251881Speter _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed " 318251881Speter "target types. Try using the --old and --new options or one of " 319251881Speter "the shorthand invocations listed in 'svn help diff'.")); 320251881Speter 321251881Speter working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0, 322251881Speter const char *)); 323251881Speter 324251881Speter if (opt_state->start_revision.kind == svn_opt_revision_unspecified 325251881Speter && working_copy_present) 326251881Speter opt_state->start_revision.kind = svn_opt_revision_base; 327251881Speter if (opt_state->end_revision.kind == svn_opt_revision_unspecified) 328251881Speter opt_state->end_revision.kind = working_copy_present 329251881Speter ? svn_opt_revision_working : svn_opt_revision_head; 330251881Speter 331251881Speter /* Determine if we need to do pegged diffs. */ 332251881Speter if ((opt_state->start_revision.kind != svn_opt_revision_base 333251881Speter && opt_state->start_revision.kind != svn_opt_revision_working) 334251881Speter || (opt_state->end_revision.kind != svn_opt_revision_base 335251881Speter && opt_state->end_revision.kind != svn_opt_revision_working)) 336251881Speter pegged_diff = TRUE; 337251881Speter 338251881Speter } 339251881Speter 340251881Speter svn_opt_push_implicit_dot_target(targets, pool); 341251881Speter 342251881Speter iterpool = svn_pool_create(pool); 343251881Speter 344251881Speter for (i = 0; i < targets->nelts; ++i) 345251881Speter { 346251881Speter const char *path = APR_ARRAY_IDX(targets, i, const char *); 347251881Speter const char *target1, *target2; 348251881Speter 349251881Speter svn_pool_clear(iterpool); 350251881Speter if (! pegged_diff) 351251881Speter { 352251881Speter /* We can't be tacking URLs onto base paths! */ 353251881Speter if (svn_path_is_url(path)) 354251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 355251881Speter _("Path '%s' not relative to base URLs"), 356251881Speter path); 357251881Speter 358251881Speter if (svn_path_is_url(old_target)) 359251881Speter target1 = svn_path_url_add_component2( 360251881Speter old_target, 361251881Speter svn_relpath_canonicalize(path, iterpool), 362251881Speter iterpool); 363251881Speter else 364251881Speter target1 = svn_dirent_join(old_target, path, iterpool); 365251881Speter 366251881Speter if (svn_path_is_url(new_target)) 367251881Speter target2 = svn_path_url_add_component2( 368251881Speter new_target, 369251881Speter svn_relpath_canonicalize(path, iterpool), 370251881Speter iterpool); 371251881Speter else 372251881Speter target2 = svn_dirent_join(new_target, path, iterpool); 373251881Speter 374251881Speter if (opt_state->diff.summarize) 375251881Speter { 376251881Speter summarize_baton.anchor = target1; 377251881Speter 378251881Speter SVN_ERR(svn_client_diff_summarize2( 379251881Speter target1, 380251881Speter &opt_state->start_revision, 381251881Speter target2, 382251881Speter &opt_state->end_revision, 383251881Speter opt_state->depth, 384251881Speter ! opt_state->diff.notice_ancestry, 385251881Speter opt_state->changelists, 386251881Speter summarize_func, &summarize_baton, 387251881Speter ctx, iterpool)); 388251881Speter } 389251881Speter else 390251881Speter SVN_ERR(svn_client_diff6( 391251881Speter options, 392251881Speter target1, 393251881Speter &(opt_state->start_revision), 394251881Speter target2, 395251881Speter &(opt_state->end_revision), 396251881Speter NULL, 397251881Speter opt_state->depth, 398251881Speter ! opt_state->diff.notice_ancestry, 399251881Speter opt_state->diff.no_diff_added, 400251881Speter opt_state->diff.no_diff_deleted, 401251881Speter show_copies_as_adds, 402251881Speter opt_state->force, 403251881Speter ignore_properties, 404251881Speter opt_state->diff.properties_only, 405251881Speter opt_state->diff.use_git_diff_format, 406251881Speter svn_cmdline_output_encoding(pool), 407251881Speter outstream, 408251881Speter errstream, 409251881Speter opt_state->changelists, 410251881Speter ctx, iterpool)); 411251881Speter } 412251881Speter else 413251881Speter { 414251881Speter const char *truepath; 415251881Speter svn_opt_revision_t peg_revision; 416251881Speter 417251881Speter /* First check for a peg revision. */ 418251881Speter SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path, 419251881Speter iterpool)); 420251881Speter 421251881Speter /* Set the default peg revision if one was not specified. */ 422251881Speter if (peg_revision.kind == svn_opt_revision_unspecified) 423251881Speter peg_revision.kind = svn_path_is_url(path) 424251881Speter ? svn_opt_revision_head : svn_opt_revision_working; 425251881Speter 426251881Speter if (opt_state->diff.summarize) 427251881Speter { 428251881Speter summarize_baton.anchor = truepath; 429251881Speter SVN_ERR(svn_client_diff_summarize_peg2( 430251881Speter truepath, 431251881Speter &peg_revision, 432251881Speter &opt_state->start_revision, 433251881Speter &opt_state->end_revision, 434251881Speter opt_state->depth, 435251881Speter ! opt_state->diff.notice_ancestry, 436251881Speter opt_state->changelists, 437251881Speter summarize_func, &summarize_baton, 438251881Speter ctx, iterpool)); 439251881Speter } 440251881Speter else 441251881Speter SVN_ERR(svn_client_diff_peg6( 442251881Speter options, 443251881Speter truepath, 444251881Speter &peg_revision, 445251881Speter &opt_state->start_revision, 446251881Speter &opt_state->end_revision, 447251881Speter NULL, 448251881Speter opt_state->depth, 449251881Speter ! opt_state->diff.notice_ancestry, 450251881Speter opt_state->diff.no_diff_added, 451251881Speter opt_state->diff.no_diff_deleted, 452251881Speter show_copies_as_adds, 453251881Speter opt_state->force, 454251881Speter ignore_properties, 455251881Speter opt_state->diff.properties_only, 456251881Speter opt_state->diff.use_git_diff_format, 457251881Speter svn_cmdline_output_encoding(pool), 458251881Speter outstream, 459251881Speter errstream, 460251881Speter opt_state->changelists, 461251881Speter ctx, iterpool)); 462251881Speter } 463251881Speter } 464251881Speter 465251881Speter if (opt_state->xml) 466251881Speter { 467251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 468251881Speter svn_xml_make_close_tag(&sb, pool, "paths"); 469251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 470251881Speter SVN_ERR(svn_cl__xml_print_footer("diff", pool)); 471251881Speter } 472251881Speter 473251881Speter svn_pool_destroy(iterpool); 474251881Speter 475251881Speter return SVN_NO_ERROR; 476251881Speter} 477