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