1/* 2 * log-cmd.c -- Display log messages 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#include <apr_fnmatch.h> 25 26#include "svn_client.h" 27#include "svn_compat.h" 28#include "svn_dirent_uri.h" 29#include "svn_string.h" 30#include "svn_path.h" 31#include "svn_error.h" 32#include "svn_sorts.h" 33#include "svn_xml.h" 34#include "svn_time.h" 35#include "svn_cmdline.h" 36#include "svn_props.h" 37#include "svn_pools.h" 38 39#include "private/svn_cmdline_private.h" 40 41#include "cl.h" 42 43#include "svn_private_config.h" 44 45 46/*** Code. ***/ 47 48/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */ 49struct log_receiver_baton 50{ 51 /* Client context. */ 52 svn_client_ctx_t *ctx; 53 54 /* The target of the log operation. */ 55 const char *target_path_or_url; 56 svn_opt_revision_t target_peg_revision; 57 58 /* Don't print log message body nor its line count. */ 59 svn_boolean_t omit_log_message; 60 61 /* Whether to show diffs in the log. (maps to --diff) */ 62 svn_boolean_t show_diff; 63 64 /* Depth applied to diff output. */ 65 svn_depth_t depth; 66 67 /* Diff arguments received from command line. */ 68 const char *diff_extensions; 69 70 /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */ 71 apr_array_header_t *merge_stack; 72 73 /* Log message search patterns. Log entries will only be shown if the author, 74 * the log message, or a changed path matches one of these patterns. */ 75 apr_array_header_t *search_patterns; 76 77 /* Pool for persistent allocations. */ 78 apr_pool_t *pool; 79}; 80 81 82/* The separator between log messages. */ 83#define SEP_STRING \ 84 "------------------------------------------------------------------------\n" 85 86 87/* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as 88 * it changed in the revision that LOG_ENTRY describes. 89 * 90 * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff 91 * subroutine. 92 * 93 * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM. 94 * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error? 95 * ### Should we get rid of ERRSTREAM and use svn_error_t instead? 96 */ 97static svn_error_t * 98display_diff(const svn_log_entry_t *log_entry, 99 const char *target_path_or_url, 100 const svn_opt_revision_t *target_peg_revision, 101 svn_depth_t depth, 102 const char *diff_extensions, 103 svn_stream_t *outstream, 104 svn_stream_t *errstream, 105 svn_client_ctx_t *ctx, 106 apr_pool_t *pool) 107{ 108 apr_array_header_t *diff_options; 109 svn_opt_revision_t start_revision; 110 svn_opt_revision_t end_revision; 111 112 /* Fall back to "" to get options initialized either way. */ 113 if (diff_extensions) 114 diff_options = svn_cstring_split(diff_extensions, " \t\n\r", 115 TRUE, pool); 116 else 117 diff_options = NULL; 118 119 start_revision.kind = svn_opt_revision_number; 120 start_revision.value.number = log_entry->revision - 1; 121 end_revision.kind = svn_opt_revision_number; 122 end_revision.value.number = log_entry->revision; 123 124 SVN_ERR(svn_stream_puts(outstream, "\n")); 125 SVN_ERR(svn_client_diff_peg6(diff_options, 126 target_path_or_url, 127 target_peg_revision, 128 &start_revision, &end_revision, 129 NULL, 130 depth, 131 FALSE /* ignore ancestry */, 132 FALSE /* no diff added */, 133 TRUE /* no diff deleted */, 134 FALSE /* show copies as adds */, 135 FALSE /* ignore content type */, 136 FALSE /* ignore prop diff */, 137 FALSE /* properties only */, 138 FALSE /* use git diff format */, 139 svn_cmdline_output_encoding(pool), 140 outstream, 141 errstream, 142 NULL, 143 ctx, pool)); 144 SVN_ERR(svn_stream_puts(outstream, _("\n"))); 145 return SVN_NO_ERROR; 146} 147 148 149/* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE, 150 * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE. 151 * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */ 152static svn_boolean_t 153match_search_pattern(const char *search_pattern, 154 const char *author, 155 const char *date, 156 const char *log_message, 157 apr_hash_t *changed_paths, 158 apr_pool_t *pool) 159{ 160 /* Match any substring containing the pattern, like UNIX 'grep' does. */ 161 const char *pattern = apr_psprintf(pool, "*%s*", search_pattern); 162 int flags = 0; 163 164 /* Does the author match the search pattern? */ 165 if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS) 166 return TRUE; 167 168 /* Does the date the search pattern? */ 169 if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS) 170 return TRUE; 171 172 /* Does the log message the search pattern? */ 173 if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS) 174 return TRUE; 175 176 if (changed_paths) 177 { 178 apr_hash_index_t *hi; 179 180 /* Does a changed path match the search pattern? */ 181 for (hi = apr_hash_first(pool, changed_paths); 182 hi; 183 hi = apr_hash_next(hi)) 184 { 185 const char *path = svn__apr_hash_index_key(hi); 186 svn_log_changed_path2_t *log_item; 187 188 if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS) 189 return TRUE; 190 191 /* Match copy-from paths, too. */ 192 log_item = svn__apr_hash_index_val(hi); 193 if (log_item->copyfrom_path 194 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev) 195 && apr_fnmatch(pattern, 196 log_item->copyfrom_path, flags) == APR_SUCCESS) 197 return TRUE; 198 } 199 } 200 201 return FALSE; 202} 203 204/* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE, 205 * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE. 206 * SCRACH_POOL is used for temporary allocations. */ 207static svn_boolean_t 208match_search_patterns(apr_array_header_t *search_patterns, 209 const char *author, 210 const char *date, 211 const char *message, 212 apr_hash_t *changed_paths, 213 apr_pool_t *scratch_pool) 214{ 215 int i; 216 svn_boolean_t match = FALSE; 217 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 218 219 for (i = 0; i < search_patterns->nelts; i++) 220 { 221 apr_array_header_t *pattern_group; 222 int j; 223 224 pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *); 225 226 /* All patterns within the group must match. */ 227 for (j = 0; j < pattern_group->nelts; j++) 228 { 229 const char *pattern; 230 231 svn_pool_clear(iterpool); 232 233 pattern = APR_ARRAY_IDX(pattern_group, j, const char *); 234 match = match_search_pattern(pattern, author, date, message, 235 changed_paths, iterpool); 236 if (!match) 237 break; 238 } 239 240 match = (match && j == pattern_group->nelts); 241 if (match) 242 break; 243 } 244 svn_pool_destroy(iterpool); 245 246 return match; 247} 248 249/* Implement `svn_log_entry_receiver_t', printing the logs in 250 * a human-readable and machine-parseable format. 251 * 252 * BATON is of type `struct log_receiver_baton'. 253 * 254 * First, print a header line. Then if CHANGED_PATHS is non-null, 255 * print all affected paths in a list headed "Changed paths:\n", 256 * immediately following the header line. Then print a newline 257 * followed by the message body, unless BATON->omit_log_message is true. 258 * 259 * Here are some examples of the output: 260 * 261 * $ svn log -r1847:1846 262 * ------------------------------------------------------------------------ 263 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines 264 * 265 * Fix for Issue #694. 266 * 267 * * subversion/libsvn_repos/delta.c 268 * (delta_files): Rework the logic in this function to only call 269 * send_text_deltas if there are deltas to send, and within that case, 270 * only use a real delta stream if the caller wants real text deltas. 271 * 272 * ------------------------------------------------------------------------ 273 * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line 274 * 275 * imagine an example log message here 276 * ------------------------------------------------------------------------ 277 * 278 * Or: 279 * 280 * $ svn log -r1847:1846 -v 281 * ------------------------------------------------------------------------ 282 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines 283 * Changed paths: 284 * M /trunk/subversion/libsvn_repos/delta.c 285 * 286 * Fix for Issue #694. 287 * 288 * * subversion/libsvn_repos/delta.c 289 * (delta_files): Rework the logic in this function to only call 290 * send_text_deltas if there are deltas to send, and within that case, 291 * only use a real delta stream if the caller wants real text deltas. 292 * 293 * ------------------------------------------------------------------------ 294 * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line 295 * Changed paths: 296 * M /trunk/notes/fs_dumprestore.txt 297 * M /trunk/subversion/libsvn_repos/dump.c 298 * 299 * imagine an example log message here 300 * ------------------------------------------------------------------------ 301 * 302 * Or: 303 * 304 * $ svn log -r1847:1846 -q 305 * ------------------------------------------------------------------------ 306 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 307 * ------------------------------------------------------------------------ 308 * rev 1846: whoever | Wed 1 May 2002 15:23:41 309 * ------------------------------------------------------------------------ 310 * 311 * Or: 312 * 313 * $ svn log -r1847:1846 -qv 314 * ------------------------------------------------------------------------ 315 * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 316 * Changed paths: 317 * M /trunk/subversion/libsvn_repos/delta.c 318 * ------------------------------------------------------------------------ 319 * rev 1846: whoever | Wed 1 May 2002 15:23:41 320 * Changed paths: 321 * M /trunk/notes/fs_dumprestore.txt 322 * M /trunk/subversion/libsvn_repos/dump.c 323 * ------------------------------------------------------------------------ 324 * 325 */ 326static svn_error_t * 327log_entry_receiver(void *baton, 328 svn_log_entry_t *log_entry, 329 apr_pool_t *pool) 330{ 331 struct log_receiver_baton *lb = baton; 332 const char *author; 333 const char *date; 334 const char *message; 335 336 if (lb->ctx->cancel_func) 337 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); 338 339 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); 340 341 if (log_entry->revision == 0 && message == NULL) 342 return SVN_NO_ERROR; 343 344 if (! SVN_IS_VALID_REVNUM(log_entry->revision)) 345 { 346 apr_array_pop(lb->merge_stack); 347 return SVN_NO_ERROR; 348 } 349 350 /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807 351 for more on the fallback fuzzy conversions below. */ 352 353 if (author == NULL) 354 author = _("(no author)"); 355 356 if (date && date[0]) 357 /* Convert date to a format for humans. */ 358 SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool)); 359 else 360 date = _("(no date)"); 361 362 if (! lb->omit_log_message && message == NULL) 363 message = ""; 364 365 if (lb->search_patterns && 366 ! match_search_patterns(lb->search_patterns, author, date, message, 367 log_entry->changed_paths2, pool)) 368 { 369 if (log_entry->has_children) 370 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 371 372 return SVN_NO_ERROR; 373 } 374 375 SVN_ERR(svn_cmdline_printf(pool, 376 SEP_STRING "r%ld | %s | %s", 377 log_entry->revision, author, date)); 378 379 if (message != NULL) 380 { 381 /* Number of lines in the msg. */ 382 int lines = svn_cstring_count_newlines(message) + 1; 383 384 SVN_ERR(svn_cmdline_printf(pool, 385 Q_(" | %d line", " | %d lines", lines), 386 lines)); 387 } 388 389 SVN_ERR(svn_cmdline_printf(pool, "\n")); 390 391 if (log_entry->changed_paths2) 392 { 393 apr_array_header_t *sorted_paths; 394 int i; 395 396 /* Get an array of sorted hash keys. */ 397 sorted_paths = svn_sort__hash(log_entry->changed_paths2, 398 svn_sort_compare_items_as_paths, pool); 399 400 SVN_ERR(svn_cmdline_printf(pool, 401 _("Changed paths:\n"))); 402 for (i = 0; i < sorted_paths->nelts; i++) 403 { 404 svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i, 405 svn_sort__item_t)); 406 const char *path = item->key; 407 svn_log_changed_path2_t *log_item = item->value; 408 const char *copy_data = ""; 409 410 if (lb->ctx->cancel_func) 411 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); 412 413 if (log_item->copyfrom_path 414 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) 415 { 416 copy_data 417 = apr_psprintf(pool, 418 _(" (from %s:%ld)"), 419 log_item->copyfrom_path, 420 log_item->copyfrom_rev); 421 } 422 SVN_ERR(svn_cmdline_printf(pool, " %c %s%s\n", 423 log_item->action, path, 424 copy_data)); 425 } 426 } 427 428 if (lb->merge_stack->nelts > 0) 429 { 430 int i; 431 432 /* Print the result of merge line */ 433 if (log_entry->subtractive_merge) 434 SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:"))); 435 else 436 SVN_ERR(svn_cmdline_printf(pool, _("Merged via:"))); 437 for (i = 0; i < lb->merge_stack->nelts; i++) 438 { 439 svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t); 440 441 SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev, 442 i == lb->merge_stack->nelts - 1 ? 443 '\n' : ',')); 444 } 445 } 446 447 if (message != NULL) 448 { 449 /* A blank line always precedes the log message. */ 450 SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message)); 451 } 452 453 SVN_ERR(svn_cmdline_fflush(stdout)); 454 SVN_ERR(svn_cmdline_fflush(stderr)); 455 456 /* Print a diff if requested. */ 457 if (lb->show_diff) 458 { 459 svn_stream_t *outstream; 460 svn_stream_t *errstream; 461 462 SVN_ERR(svn_stream_for_stdout(&outstream, pool)); 463 SVN_ERR(svn_stream_for_stderr(&errstream, pool)); 464 465 SVN_ERR(display_diff(log_entry, 466 lb->target_path_or_url, &lb->target_peg_revision, 467 lb->depth, lb->diff_extensions, 468 outstream, errstream, 469 lb->ctx, pool)); 470 471 SVN_ERR(svn_stream_close(outstream)); 472 SVN_ERR(svn_stream_close(errstream)); 473 } 474 475 if (log_entry->has_children) 476 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 477 478 return SVN_NO_ERROR; 479} 480 481 482/* This implements `svn_log_entry_receiver_t', printing the logs in XML. 483 * 484 * BATON is of type `struct log_receiver_baton'. 485 * 486 * Here is an example of the output; note that the "<log>" and 487 * "</log>" tags are not emitted by this function: 488 * 489 * $ svn log --xml -r 1648:1649 490 * <log> 491 * <logentry 492 * revision="1648"> 493 * <author>david</author> 494 * <date>2002-04-06T16:34:51.428043Z</date> 495 * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36. 496 * </msg> 497 * </logentry> 498 * <logentry 499 * revision="1649"> 500 * <author>cmpilato</author> 501 * <date>2002-04-06T17:01:28.185136Z</date> 502 * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah 503 * ... now that's *much* nicer. 504 * 505 * * subversion/clients/cmdline/util.c 506 * (svn_cl__edit_externally): Clean up the "no external editor" 507 * error message. 508 * (svn_cl__get_log_message): Wrap "no external editor" 509 * errors with helpful hints about the -m and -F options. 510 * 511 * * subversion/libsvn_client/commit.c 512 * (svn_client_commit): Actually capture and propagate "no external 513 * editor" errors.</msg> 514 * </logentry> 515 * </log> 516 * 517 */ 518static svn_error_t * 519log_entry_receiver_xml(void *baton, 520 svn_log_entry_t *log_entry, 521 apr_pool_t *pool) 522{ 523 struct log_receiver_baton *lb = baton; 524 /* Collate whole log message into sb before printing. */ 525 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 526 char *revstr; 527 const char *author; 528 const char *date; 529 const char *message; 530 531 if (lb->ctx->cancel_func) 532 SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); 533 534 svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); 535 536 if (log_entry->revision == 0 && message == NULL) 537 return SVN_NO_ERROR; 538 539 if (! SVN_IS_VALID_REVNUM(log_entry->revision)) 540 { 541 svn_xml_make_close_tag(&sb, pool, "logentry"); 542 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 543 apr_array_pop(lb->merge_stack); 544 545 return SVN_NO_ERROR; 546 } 547 548 /* Match search pattern before XML-escaping. */ 549 if (lb->search_patterns && 550 ! match_search_patterns(lb->search_patterns, author, date, message, 551 log_entry->changed_paths2, pool)) 552 { 553 if (log_entry->has_children) 554 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 555 556 return SVN_NO_ERROR; 557 } 558 559 if (author) 560 author = svn_xml_fuzzy_escape(author, pool); 561 if (date) 562 date = svn_xml_fuzzy_escape(date, pool); 563 if (message) 564 message = svn_xml_fuzzy_escape(message, pool); 565 566 revstr = apr_psprintf(pool, "%ld", log_entry->revision); 567 /* <logentry revision="xxx"> */ 568 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry", 569 "revision", revstr, NULL); 570 571 /* <author>xxx</author> */ 572 svn_cl__xml_tagged_cdata(&sb, pool, "author", author); 573 574 /* Print the full, uncut, date. This is machine output. */ 575 /* According to the docs for svn_log_entry_receiver_t, either 576 NULL or the empty string represents no date. Avoid outputting an 577 empty date element. */ 578 if (date && date[0] == '\0') 579 date = NULL; 580 /* <date>xxx</date> */ 581 svn_cl__xml_tagged_cdata(&sb, pool, "date", date); 582 583 if (log_entry->changed_paths2) 584 { 585 apr_array_header_t *sorted_paths; 586 int i; 587 588 /* <paths> */ 589 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", 590 NULL); 591 592 /* Get an array of sorted hash keys. */ 593 sorted_paths = svn_sort__hash(log_entry->changed_paths2, 594 svn_sort_compare_items_as_paths, pool); 595 596 for (i = 0; i < sorted_paths->nelts; i++) 597 { 598 svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i, 599 svn_sort__item_t)); 600 const char *path = item->key; 601 svn_log_changed_path2_t *log_item = item->value; 602 char action[2]; 603 604 action[0] = log_item->action; 605 action[1] = '\0'; 606 if (log_item->copyfrom_path 607 && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) 608 { 609 /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */ 610 revstr = apr_psprintf(pool, "%ld", 611 log_item->copyfrom_rev); 612 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", 613 "action", action, 614 "copyfrom-path", log_item->copyfrom_path, 615 "copyfrom-rev", revstr, 616 "kind", svn_cl__node_kind_str_xml( 617 log_item->node_kind), 618 "text-mods", svn_tristate__to_word( 619 log_item->text_modified), 620 "prop-mods", svn_tristate__to_word( 621 log_item->props_modified), 622 NULL); 623 } 624 else 625 { 626 /* <path action="X"> */ 627 svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", 628 "action", action, 629 "kind", svn_cl__node_kind_str_xml( 630 log_item->node_kind), 631 "text-mods", svn_tristate__to_word( 632 log_item->text_modified), 633 "prop-mods", svn_tristate__to_word( 634 log_item->props_modified), 635 NULL); 636 } 637 /* xxx</path> */ 638 svn_xml_escape_cdata_cstring(&sb, path, pool); 639 svn_xml_make_close_tag(&sb, pool, "path"); 640 } 641 642 /* </paths> */ 643 svn_xml_make_close_tag(&sb, pool, "paths"); 644 } 645 646 if (message != NULL) 647 { 648 /* <msg>xxx</msg> */ 649 svn_cl__xml_tagged_cdata(&sb, pool, "msg", message); 650 } 651 652 svn_compat_log_revprops_clear(log_entry->revprops); 653 if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0) 654 { 655 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL); 656 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops, 657 FALSE, /* name_only */ 658 FALSE, pool)); 659 svn_xml_make_close_tag(&sb, pool, "revprops"); 660 } 661 662 if (log_entry->has_children) 663 APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 664 else 665 svn_xml_make_close_tag(&sb, pool, "logentry"); 666 667 return svn_cl__error_checked_fputs(sb->data, stdout); 668} 669 670 671/* This implements the `svn_opt_subcommand_t' interface. */ 672svn_error_t * 673svn_cl__log(apr_getopt_t *os, 674 void *baton, 675 apr_pool_t *pool) 676{ 677 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 678 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 679 apr_array_header_t *targets; 680 struct log_receiver_baton lb; 681 const char *target; 682 int i; 683 apr_array_header_t *revprops; 684 685 if (!opt_state->xml) 686 { 687 if (opt_state->all_revprops) 688 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 689 _("'with-all-revprops' option only valid in" 690 " XML mode")); 691 if (opt_state->no_revprops) 692 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 693 _("'with-no-revprops' option only valid in" 694 " XML mode")); 695 if (opt_state->revprop_table != NULL) 696 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 697 _("'with-revprop' option only valid in" 698 " XML mode")); 699 } 700 else 701 { 702 if (opt_state->show_diff) 703 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 704 _("'diff' option is not supported in " 705 "XML mode")); 706 } 707 708 if (opt_state->quiet && opt_state->show_diff) 709 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 710 _("'quiet' and 'diff' options are " 711 "mutually exclusive")); 712 if (opt_state->diff.diff_cmd && (! opt_state->show_diff)) 713 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 714 _("'diff-cmd' option requires 'diff' " 715 "option")); 716 if (opt_state->diff.internal_diff && (! opt_state->show_diff)) 717 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 718 _("'internal-diff' option requires " 719 "'diff' option")); 720 if (opt_state->extensions && (! opt_state->show_diff)) 721 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 722 _("'extensions' option requires 'diff' " 723 "option")); 724 725 if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff)) 726 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 727 _("'depth' option requires 'diff' option")); 728 729 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 730 opt_state->targets, 731 ctx, FALSE, pool)); 732 733 /* Add "." if user passed 0 arguments */ 734 svn_opt_push_implicit_dot_target(targets, pool); 735 736 /* Determine if they really want a two-revision range. */ 737 if (opt_state->used_change_arg) 738 { 739 if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1) 740 { 741 return svn_error_create 742 (SVN_ERR_CLIENT_BAD_REVISION, NULL, 743 _("-c and -r are mutually exclusive")); 744 } 745 for (i = 0; i < opt_state->revision_ranges->nelts; i++) 746 { 747 svn_opt_revision_range_t *range; 748 range = APR_ARRAY_IDX(opt_state->revision_ranges, i, 749 svn_opt_revision_range_t *); 750 if (range->start.value.number < range->end.value.number) 751 range->start.value.number++; 752 else 753 range->end.value.number++; 754 } 755 } 756 757 /* Parse the first target into path-or-url and peg revision. */ 758 target = APR_ARRAY_IDX(targets, 0, const char *); 759 SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url, 760 target, pool)); 761 if (lb.target_peg_revision.kind == svn_opt_revision_unspecified) 762 lb.target_peg_revision.kind = (svn_path_is_url(target) 763 ? svn_opt_revision_head 764 : svn_opt_revision_working); 765 APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url; 766 767 if (svn_path_is_url(target)) 768 { 769 for (i = 1; i < targets->nelts; i++) 770 { 771 target = APR_ARRAY_IDX(targets, i, const char *); 772 773 if (svn_path_is_url(target) || target[0] == '/') 774 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 775 _("Only relative paths can be specified" 776 " after a URL for 'svn log', " 777 "but '%s' is not a relative path"), 778 target); 779 } 780 } 781 782 lb.ctx = ctx; 783 lb.omit_log_message = opt_state->quiet; 784 lb.show_diff = opt_state->show_diff; 785 lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity 786 : opt_state->depth; 787 lb.diff_extensions = opt_state->extensions; 788 lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t)); 789 lb.search_patterns = opt_state->search_patterns; 790 lb.pool = pool; 791 792 if (opt_state->xml) 793 { 794 /* If output is not incremental, output the XML header and wrap 795 everything in a top-level element. This makes the output in 796 its entirety a well-formed XML document. */ 797 if (! opt_state->incremental) 798 SVN_ERR(svn_cl__xml_print_header("log", pool)); 799 800 if (opt_state->all_revprops) 801 revprops = NULL; 802 else if(opt_state->no_revprops) 803 { 804 revprops = apr_array_make(pool, 0, sizeof(char *)); 805 } 806 else if (opt_state->revprop_table != NULL) 807 { 808 apr_hash_index_t *hi; 809 revprops = apr_array_make(pool, 810 apr_hash_count(opt_state->revprop_table), 811 sizeof(char *)); 812 for (hi = apr_hash_first(pool, opt_state->revprop_table); 813 hi != NULL; 814 hi = apr_hash_next(hi)) 815 { 816 const char *property = svn__apr_hash_index_key(hi); 817 svn_string_t *value = svn__apr_hash_index_val(hi); 818 819 if (value && value->data[0] != '\0') 820 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 821 _("cannot assign with 'with-revprop'" 822 " option (drop the '=')")); 823 APR_ARRAY_PUSH(revprops, const char *) = property; 824 } 825 } 826 else 827 { 828 revprops = apr_array_make(pool, 3, sizeof(char *)); 829 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; 830 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; 831 if (!opt_state->quiet) 832 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; 833 } 834 SVN_ERR(svn_client_log5(targets, 835 &lb.target_peg_revision, 836 opt_state->revision_ranges, 837 opt_state->limit, 838 opt_state->verbose, 839 opt_state->stop_on_copy, 840 opt_state->use_merge_history, 841 revprops, 842 log_entry_receiver_xml, 843 &lb, 844 ctx, 845 pool)); 846 847 if (! opt_state->incremental) 848 SVN_ERR(svn_cl__xml_print_footer("log", pool)); 849 } 850 else /* default output format */ 851 { 852 revprops = apr_array_make(pool, 3, sizeof(char *)); 853 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; 854 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; 855 if (!opt_state->quiet) 856 APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; 857 SVN_ERR(svn_client_log5(targets, 858 &lb.target_peg_revision, 859 opt_state->revision_ranges, 860 opt_state->limit, 861 opt_state->verbose, 862 opt_state->stop_on_copy, 863 opt_state->use_merge_history, 864 revprops, 865 log_entry_receiver, 866 &lb, 867 ctx, 868 pool)); 869 870 if (! opt_state->incremental) 871 SVN_ERR(svn_cmdline_printf(pool, SEP_STRING)); 872 } 873 874 return SVN_NO_ERROR; 875} 876