1251881Speter/* 2251881Speter * log-cmd.c -- Display log messages 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#include <apr_fnmatch.h> 25251881Speter 26251881Speter#include "svn_client.h" 27251881Speter#include "svn_compat.h" 28251881Speter#include "svn_dirent_uri.h" 29251881Speter#include "svn_string.h" 30251881Speter#include "svn_path.h" 31251881Speter#include "svn_error.h" 32251881Speter#include "svn_sorts.h" 33251881Speter#include "svn_xml.h" 34251881Speter#include "svn_time.h" 35251881Speter#include "svn_cmdline.h" 36251881Speter#include "svn_props.h" 37251881Speter#include "svn_pools.h" 38251881Speter 39251881Speter#include "private/svn_cmdline_private.h" 40251881Speter 41251881Speter#include "cl.h" 42251881Speter 43251881Speter#include "svn_private_config.h" 44251881Speter 45251881Speter 46251881Speter/*** Code. ***/ 47251881Speter 48251881Speter/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */ 49251881Speterstruct log_receiver_baton 50251881Speter{ 51251881Speter /* Client context. */ 52251881Speter svn_client_ctx_t *ctx; 53251881Speter 54251881Speter /* The target of the log operation. */ 55251881Speter const char *target_path_or_url; 56251881Speter svn_opt_revision_t target_peg_revision; 57251881Speter 58251881Speter /* Don't print log message body nor its line count. */ 59251881Speter svn_boolean_t omit_log_message; 60251881Speter 61251881Speter /* Whether to show diffs in the log. (maps to --diff) */ 62251881Speter svn_boolean_t show_diff; 63251881Speter 64251881Speter /* Depth applied to diff output. */ 65251881Speter svn_depth_t depth; 66251881Speter 67251881Speter /* Diff arguments received from command line. */ 68251881Speter const char *diff_extensions; 69251881Speter 70251881Speter /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */ 71251881Speter apr_array_header_t *merge_stack; 72251881Speter 73251881Speter /* Log message search patterns. Log entries will only be shown if the author, 74251881Speter * the log message, or a changed path matches one of these patterns. */ 75251881Speter apr_array_header_t *search_patterns; 76251881Speter 77251881Speter /* Pool for persistent allocations. */ 78251881Speter apr_pool_t *pool; 79251881Speter}; 80251881Speter 81251881Speter 82251881Speter/* The separator between log messages. */ 83251881Speter#define SEP_STRING \ 84251881Speter "------------------------------------------------------------------------\n" 85251881Speter 86251881Speter 87251881Speter/* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as 88251881Speter * it changed in the revision that LOG_ENTRY describes. 89251881Speter * 90251881Speter * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff 91251881Speter * subroutine. 92251881Speter * 93251881Speter * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM. 94251881Speter * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error? 95251881Speter * ### Should we get rid of ERRSTREAM and use svn_error_t instead? 96251881Speter */ 97251881Speterstatic svn_error_t * 98251881Speterdisplay_diff(const svn_log_entry_t *log_entry, 99251881Speter const char *target_path_or_url, 100251881Speter const svn_opt_revision_t *target_peg_revision, 101251881Speter svn_depth_t depth, 102251881Speter const char *diff_extensions, 103251881Speter svn_stream_t *outstream, 104251881Speter svn_stream_t *errstream, 105251881Speter svn_client_ctx_t *ctx, 106251881Speter apr_pool_t *pool) 107251881Speter{ 108251881Speter apr_array_header_t *diff_options; 109251881Speter svn_opt_revision_t start_revision; 110251881Speter svn_opt_revision_t end_revision; 111251881Speter 112251881Speter /* Fall back to "" to get options initialized either way. */ 113251881Speter if (diff_extensions) 114251881Speter diff_options = svn_cstring_split(diff_extensions, " \t\n\r", 115251881Speter TRUE, pool); 116251881Speter else 117251881Speter diff_options = NULL; 118251881Speter 119251881Speter start_revision.kind = svn_opt_revision_number; 120251881Speter start_revision.value.number = log_entry->revision - 1; 121251881Speter end_revision.kind = svn_opt_revision_number; 122251881Speter end_revision.value.number = log_entry->revision; 123251881Speter 124251881Speter SVN_ERR(svn_stream_puts(outstream, "\n")); 125251881Speter SVN_ERR(svn_client_diff_peg6(diff_options, 126251881Speter target_path_or_url, 127251881Speter target_peg_revision, 128251881Speter &start_revision, &end_revision, 129251881Speter NULL, 130251881Speter depth, 131251881Speter FALSE /* ignore ancestry */, 132251881Speter FALSE /* no diff added */, 133251881Speter TRUE /* no diff deleted */, 134251881Speter FALSE /* show copies as adds */, 135251881Speter FALSE /* ignore content type */, 136251881Speter FALSE /* ignore prop diff */, 137251881Speter FALSE /* properties only */, 138251881Speter FALSE /* use git diff format */, 139251881Speter svn_cmdline_output_encoding(pool), 140251881Speter outstream, 141251881Speter errstream, 142251881Speter NULL, 143251881Speter ctx, pool)); 144251881Speter SVN_ERR(svn_stream_puts(outstream, _("\n"))); 145251881Speter return SVN_NO_ERROR; 146251881Speter} 147251881Speter 148251881Speter 149251881Speter/* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE, 150251881Speter * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE. 151251881Speter * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */ 152251881Speterstatic svn_boolean_t 153251881Spetermatch_search_pattern(const char *search_pattern, 154251881Speter const char *author, 155251881Speter const char *date, 156251881Speter const char *log_message, 157251881Speter apr_hash_t *changed_paths, 158251881Speter apr_pool_t *pool) 159251881Speter{ 160251881Speter /* Match any substring containing the pattern, like UNIX 'grep' does. */ 161251881Speter const char *pattern = apr_psprintf(pool, "*%s*", search_pattern); 162251881Speter int flags = 0; 163251881Speter 164251881Speter /* Does the author match the search pattern? */ 165251881Speter if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS) 166251881Speter return TRUE; 167251881Speter 168251881Speter /* Does the date the search pattern? */ 169251881Speter if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS) 170251881Speter return TRUE; 171251881Speter 172251881Speter /* Does the log message the search pattern? */ 173251881Speter if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS) 174251881Speter return TRUE; 175251881Speter 176251881Speter if (changed_paths) 177251881Speter { 178251881Speter apr_hash_index_t *hi; 179251881Speter 180251881Speter /* Does a changed path match the search pattern? */ 181251881Speter for (hi = apr_hash_first(pool, changed_paths); 182251881Speter hi; 183251881Speter hi = apr_hash_next(hi)) 184251881Speter { 185251881Speter const char *path = svn__apr_hash_index_key(hi); 186251881Speter svn_log_changed_path2_t *log_item; 187251881Speter 188251881Speter if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS) 189251881Speter return TRUE; 190251881Speter 191251881Speter /* Match copy-from paths, too. */ 192251881Speter log_item = svn__apr_hash_index_val(hi); 193251881Speter if (log_item->copyfrom_path 194251881Speter && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev) 195251881Speter && apr_fnmatch(pattern, 196251881Speter log_item->copyfrom_path, flags) == APR_SUCCESS) 197251881Speter return TRUE; 198251881Speter } 199251881Speter } 200251881Speter 201251881Speter return FALSE; 202251881Speter} 203251881Speter 204251881Speter/* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE, 205251881Speter * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE. 206251881Speter * SCRACH_POOL is used for temporary allocations. */ 207251881Speterstatic svn_boolean_t 208251881Spetermatch_search_patterns(apr_array_header_t *search_patterns, 209251881Speter const char *author, 210251881Speter const char *date, 211251881Speter const char *message, 212251881Speter apr_hash_t *changed_paths, 213251881Speter apr_pool_t *scratch_pool) 214251881Speter{ 215251881Speter int i; 216251881Speter svn_boolean_t match = FALSE; 217251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 218251881Speter 219251881Speter for (i = 0; i < search_patterns->nelts; i++) 220251881Speter { 221251881Speter apr_array_header_t *pattern_group; 222251881Speter int j; 223251881Speter 224251881Speter pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *); 225251881Speter 226251881Speter /* All patterns within the group must match. */ 227251881Speter for (j = 0; j < pattern_group->nelts; j++) 228251881Speter { 229251881Speter const char *pattern; 230251881Speter 231251881Speter svn_pool_clear(iterpool); 232251881Speter 233251881Speter pattern = APR_ARRAY_IDX(pattern_group, j, const char *); 234251881Speter match = match_search_pattern(pattern, author, date, message, 235251881Speter changed_paths, iterpool); 236251881Speter if (!match) 237251881Speter break; 238251881Speter } 239251881Speter 240251881Speter match = (match && j == pattern_group->nelts); 241251881Speter if (match) 242251881Speter break; 243251881Speter } 244251881Speter svn_pool_destroy(iterpool); 245251881Speter 246251881Speter return match; 247251881Speter} 248251881Speter 249251881Speter/* Implement `svn_log_entry_receiver_t', printing the logs in 250251881Speter * a human-readable and machine-parseable format. 251251881Speter * 252251881Speter * BATON is of type `struct log_receiver_baton'. 253251881Speter * 254251881Speter * First, print a header line. Then if CHANGED_PATHS is non-null, 255251881Speter * print all affected paths in a list headed "Changed paths:\n", 256251881Speter * immediately following the header line. Then print a newline 257251881Speter * followed by the message body, unless BATON->omit_log_message is true. 258251881Speter * 259251881Speter * Here are some examples of the output: 260251881Speter * 261251881Speter * $ svn log -r1847:1846 262251881Speter * ------------------------------------------------------------------------ 263251881Speter * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines 264251881Speter * 265251881Speter * Fix for Issue #694. 266251881Speter * 267251881Speter * * subversion/libsvn_repos/delta.c 268251881Speter * (delta_files): Rework the logic in this function to only call 269251881Speter * send_text_deltas if there are deltas to send, and within that case, 270251881Speter * only use a real delta stream if the caller wants real text deltas. 271251881Speter * 272251881Speter * ------------------------------------------------------------------------ 273251881Speter * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line 274251881Speter * 275251881Speter * imagine an example log message here 276251881Speter * ------------------------------------------------------------------------ 277251881Speter * 278251881Speter * Or: 279251881Speter * 280251881Speter * $ svn log -r1847:1846 -v 281251881Speter * ------------------------------------------------------------------------ 282251881Speter * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines 283251881Speter * Changed paths: 284251881Speter * M /trunk/subversion/libsvn_repos/delta.c 285251881Speter * 286251881Speter * Fix for Issue #694. 287251881Speter * 288251881Speter * * subversion/libsvn_repos/delta.c 289251881Speter * (delta_files): Rework the logic in this function to only call 290251881Speter * send_text_deltas if there are deltas to send, and within that case, 291251881Speter * only use a real delta stream if the caller wants real text deltas. 292251881Speter * 293251881Speter * ------------------------------------------------------------------------ 294251881Speter * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line 295251881Speter * Changed paths: 296251881Speter * M /trunk/notes/fs_dumprestore.txt 297251881Speter * M /trunk/subversion/libsvn_repos/dump.c 298251881Speter * 299251881Speter * imagine an example log message here 300251881Speter * ------------------------------------------------------------------------ 301251881Speter * 302251881Speter * Or: 303251881Speter * 304251881Speter * $ svn log -r1847:1846 -q 305251881Speter * ------------------------------------------------------------------------ 306251881Speter * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 307251881Speter * ------------------------------------------------------------------------ 308251881Speter * rev 1846: whoever | Wed 1 May 2002 15:23:41 309251881Speter * ------------------------------------------------------------------------ 310251881Speter * 311251881Speter * Or: 312251881Speter * 313251881Speter * $ svn log -r1847:1846 -qv 314251881Speter * ------------------------------------------------------------------------ 315251881Speter * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 316251881Speter * Changed paths: 317251881Speter * M /trunk/subversion/libsvn_repos/delta.c 318251881Speter * ------------------------------------------------------------------------ 319251881Speter * rev 1846: whoever | Wed 1 May 2002 15:23:41 320251881Speter * Changed paths: 321251881Speter * M /trunk/notes/fs_dumprestore.txt 322251881Speter * M /trunk/subversion/libsvn_repos/dump.c 323251881Speter * ------------------------------------------------------------------------ 324251881Speter * 325251881Speter */ 326251881Speterstatic svn_error_t * 327251881Speterlog_entry_receiver(void *baton, 328251881Speter svn_log_entry_t *log_entry, 329251881Speter apr_pool_t *pool) 330251881Speter{ 331251881Speter struct log_receiver_baton *lb = baton; 332251881Speter const char *author; 333251881Speter const char *date; 334251881Speter const char *message; 335251881Speter 336251881Speter if (lb->ctx->cancel_func) 337251881Speter SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); 338251881Speter 339251881Speter svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); 340251881Speter 341251881Speter if (log_entry->revision == 0 && message == NULL) 342251881Speter return SVN_NO_ERROR; 343251881Speter 344251881Speter if (! SVN_IS_VALID_REVNUM(log_entry->revision)) 345251881Speter { 346251881Speter apr_array_pop(lb->merge_stack); 347251881Speter return SVN_NO_ERROR; 348251881Speter } 349251881Speter 350251881Speter /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807 351251881Speter for more on the fallback fuzzy conversions below. */ 352251881Speter 353251881Speter if (author == NULL) 354251881Speter author = _("(no author)"); 355251881Speter 356251881Speter if (date && date[0]) 357251881Speter /* Convert date to a format for humans. */ 358251881Speter SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool)); 359251881Speter else 360251881Speter date = _("(no date)"); 361251881Speter 362251881Speter if (! lb->omit_log_message && message == NULL) 363251881Speter message = ""; 364251881Speter 365251881Speter if (lb->search_patterns && 366251881Speter ! match_search_patterns(lb->search_patterns, author, date, message, 367251881Speter log_entry->changed_paths2, pool)) 368251881Speter { 369251881Speter if (log_entry->has_children) 370251881Speter APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 371251881Speter 372251881Speter return SVN_NO_ERROR; 373251881Speter } 374251881Speter 375251881Speter SVN_ERR(svn_cmdline_printf(pool, 376251881Speter SEP_STRING "r%ld | %s | %s", 377251881Speter log_entry->revision, author, date)); 378251881Speter 379251881Speter if (message != NULL) 380251881Speter { 381251881Speter /* Number of lines in the msg. */ 382251881Speter int lines = svn_cstring_count_newlines(message) + 1; 383251881Speter 384251881Speter SVN_ERR(svn_cmdline_printf(pool, 385251881Speter Q_(" | %d line", " | %d lines", lines), 386251881Speter lines)); 387251881Speter } 388251881Speter 389251881Speter SVN_ERR(svn_cmdline_printf(pool, "\n")); 390251881Speter 391251881Speter if (log_entry->changed_paths2) 392251881Speter { 393251881Speter apr_array_header_t *sorted_paths; 394251881Speter int i; 395251881Speter 396251881Speter /* Get an array of sorted hash keys. */ 397251881Speter sorted_paths = svn_sort__hash(log_entry->changed_paths2, 398251881Speter svn_sort_compare_items_as_paths, pool); 399251881Speter 400251881Speter SVN_ERR(svn_cmdline_printf(pool, 401251881Speter _("Changed paths:\n"))); 402251881Speter for (i = 0; i < sorted_paths->nelts; i++) 403251881Speter { 404251881Speter svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i, 405251881Speter svn_sort__item_t)); 406251881Speter const char *path = item->key; 407251881Speter svn_log_changed_path2_t *log_item = item->value; 408251881Speter const char *copy_data = ""; 409251881Speter 410251881Speter if (lb->ctx->cancel_func) 411251881Speter SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); 412251881Speter 413251881Speter if (log_item->copyfrom_path 414251881Speter && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) 415251881Speter { 416251881Speter copy_data 417251881Speter = apr_psprintf(pool, 418251881Speter _(" (from %s:%ld)"), 419251881Speter log_item->copyfrom_path, 420251881Speter log_item->copyfrom_rev); 421251881Speter } 422251881Speter SVN_ERR(svn_cmdline_printf(pool, " %c %s%s\n", 423251881Speter log_item->action, path, 424251881Speter copy_data)); 425251881Speter } 426251881Speter } 427251881Speter 428251881Speter if (lb->merge_stack->nelts > 0) 429251881Speter { 430251881Speter int i; 431251881Speter 432251881Speter /* Print the result of merge line */ 433251881Speter if (log_entry->subtractive_merge) 434251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:"))); 435251881Speter else 436251881Speter SVN_ERR(svn_cmdline_printf(pool, _("Merged via:"))); 437251881Speter for (i = 0; i < lb->merge_stack->nelts; i++) 438251881Speter { 439251881Speter svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t); 440251881Speter 441251881Speter SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev, 442251881Speter i == lb->merge_stack->nelts - 1 ? 443251881Speter '\n' : ',')); 444251881Speter } 445251881Speter } 446251881Speter 447251881Speter if (message != NULL) 448251881Speter { 449251881Speter /* A blank line always precedes the log message. */ 450251881Speter SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message)); 451251881Speter } 452251881Speter 453251881Speter SVN_ERR(svn_cmdline_fflush(stdout)); 454251881Speter SVN_ERR(svn_cmdline_fflush(stderr)); 455251881Speter 456251881Speter /* Print a diff if requested. */ 457251881Speter if (lb->show_diff) 458251881Speter { 459251881Speter svn_stream_t *outstream; 460251881Speter svn_stream_t *errstream; 461251881Speter 462251881Speter SVN_ERR(svn_stream_for_stdout(&outstream, pool)); 463251881Speter SVN_ERR(svn_stream_for_stderr(&errstream, pool)); 464251881Speter 465251881Speter SVN_ERR(display_diff(log_entry, 466251881Speter lb->target_path_or_url, &lb->target_peg_revision, 467251881Speter lb->depth, lb->diff_extensions, 468251881Speter outstream, errstream, 469251881Speter lb->ctx, pool)); 470251881Speter 471251881Speter SVN_ERR(svn_stream_close(outstream)); 472251881Speter SVN_ERR(svn_stream_close(errstream)); 473251881Speter } 474251881Speter 475251881Speter if (log_entry->has_children) 476251881Speter APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 477251881Speter 478251881Speter return SVN_NO_ERROR; 479251881Speter} 480251881Speter 481251881Speter 482251881Speter/* This implements `svn_log_entry_receiver_t', printing the logs in XML. 483251881Speter * 484251881Speter * BATON is of type `struct log_receiver_baton'. 485251881Speter * 486251881Speter * Here is an example of the output; note that the "<log>" and 487251881Speter * "</log>" tags are not emitted by this function: 488251881Speter * 489251881Speter * $ svn log --xml -r 1648:1649 490251881Speter * <log> 491251881Speter * <logentry 492251881Speter * revision="1648"> 493251881Speter * <author>david</author> 494251881Speter * <date>2002-04-06T16:34:51.428043Z</date> 495251881Speter * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36. 496251881Speter * </msg> 497251881Speter * </logentry> 498251881Speter * <logentry 499251881Speter * revision="1649"> 500251881Speter * <author>cmpilato</author> 501251881Speter * <date>2002-04-06T17:01:28.185136Z</date> 502251881Speter * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah 503251881Speter * ... now that's *much* nicer. 504251881Speter * 505251881Speter * * subversion/clients/cmdline/util.c 506251881Speter * (svn_cl__edit_externally): Clean up the "no external editor" 507251881Speter * error message. 508251881Speter * (svn_cl__get_log_message): Wrap "no external editor" 509251881Speter * errors with helpful hints about the -m and -F options. 510251881Speter * 511251881Speter * * subversion/libsvn_client/commit.c 512251881Speter * (svn_client_commit): Actually capture and propagate "no external 513251881Speter * editor" errors.</msg> 514251881Speter * </logentry> 515251881Speter * </log> 516251881Speter * 517251881Speter */ 518251881Speterstatic svn_error_t * 519251881Speterlog_entry_receiver_xml(void *baton, 520251881Speter svn_log_entry_t *log_entry, 521251881Speter apr_pool_t *pool) 522251881Speter{ 523251881Speter struct log_receiver_baton *lb = baton; 524251881Speter /* Collate whole log message into sb before printing. */ 525251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 526251881Speter char *revstr; 527251881Speter const char *author; 528251881Speter const char *date; 529251881Speter const char *message; 530251881Speter 531251881Speter if (lb->ctx->cancel_func) 532251881Speter SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton)); 533251881Speter 534251881Speter svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops); 535251881Speter 536251881Speter if (log_entry->revision == 0 && message == NULL) 537251881Speter return SVN_NO_ERROR; 538251881Speter 539251881Speter if (! SVN_IS_VALID_REVNUM(log_entry->revision)) 540251881Speter { 541251881Speter svn_xml_make_close_tag(&sb, pool, "logentry"); 542251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 543251881Speter apr_array_pop(lb->merge_stack); 544251881Speter 545251881Speter return SVN_NO_ERROR; 546251881Speter } 547251881Speter 548251881Speter /* Match search pattern before XML-escaping. */ 549251881Speter if (lb->search_patterns && 550251881Speter ! match_search_patterns(lb->search_patterns, author, date, message, 551251881Speter log_entry->changed_paths2, pool)) 552251881Speter { 553251881Speter if (log_entry->has_children) 554251881Speter APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 555251881Speter 556251881Speter return SVN_NO_ERROR; 557251881Speter } 558251881Speter 559251881Speter if (author) 560251881Speter author = svn_xml_fuzzy_escape(author, pool); 561251881Speter if (date) 562251881Speter date = svn_xml_fuzzy_escape(date, pool); 563251881Speter if (message) 564251881Speter message = svn_xml_fuzzy_escape(message, pool); 565251881Speter 566251881Speter revstr = apr_psprintf(pool, "%ld", log_entry->revision); 567251881Speter /* <logentry revision="xxx"> */ 568251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry", 569251881Speter "revision", revstr, NULL); 570251881Speter 571251881Speter /* <author>xxx</author> */ 572251881Speter svn_cl__xml_tagged_cdata(&sb, pool, "author", author); 573251881Speter 574251881Speter /* Print the full, uncut, date. This is machine output. */ 575251881Speter /* According to the docs for svn_log_entry_receiver_t, either 576251881Speter NULL or the empty string represents no date. Avoid outputting an 577251881Speter empty date element. */ 578251881Speter if (date && date[0] == '\0') 579251881Speter date = NULL; 580251881Speter /* <date>xxx</date> */ 581251881Speter svn_cl__xml_tagged_cdata(&sb, pool, "date", date); 582251881Speter 583251881Speter if (log_entry->changed_paths2) 584251881Speter { 585251881Speter apr_array_header_t *sorted_paths; 586251881Speter int i; 587251881Speter 588251881Speter /* <paths> */ 589251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", 590251881Speter NULL); 591251881Speter 592251881Speter /* Get an array of sorted hash keys. */ 593251881Speter sorted_paths = svn_sort__hash(log_entry->changed_paths2, 594251881Speter svn_sort_compare_items_as_paths, pool); 595251881Speter 596251881Speter for (i = 0; i < sorted_paths->nelts; i++) 597251881Speter { 598251881Speter svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i, 599251881Speter svn_sort__item_t)); 600251881Speter const char *path = item->key; 601251881Speter svn_log_changed_path2_t *log_item = item->value; 602251881Speter char action[2]; 603251881Speter 604251881Speter action[0] = log_item->action; 605251881Speter action[1] = '\0'; 606251881Speter if (log_item->copyfrom_path 607251881Speter && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)) 608251881Speter { 609251881Speter /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */ 610251881Speter revstr = apr_psprintf(pool, "%ld", 611251881Speter log_item->copyfrom_rev); 612251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", 613251881Speter "action", action, 614251881Speter "copyfrom-path", log_item->copyfrom_path, 615251881Speter "copyfrom-rev", revstr, 616251881Speter "kind", svn_cl__node_kind_str_xml( 617251881Speter log_item->node_kind), 618251881Speter "text-mods", svn_tristate__to_word( 619251881Speter log_item->text_modified), 620251881Speter "prop-mods", svn_tristate__to_word( 621251881Speter log_item->props_modified), 622251881Speter NULL); 623251881Speter } 624251881Speter else 625251881Speter { 626251881Speter /* <path action="X"> */ 627251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path", 628251881Speter "action", action, 629251881Speter "kind", svn_cl__node_kind_str_xml( 630251881Speter log_item->node_kind), 631251881Speter "text-mods", svn_tristate__to_word( 632251881Speter log_item->text_modified), 633251881Speter "prop-mods", svn_tristate__to_word( 634251881Speter log_item->props_modified), 635251881Speter NULL); 636251881Speter } 637251881Speter /* xxx</path> */ 638251881Speter svn_xml_escape_cdata_cstring(&sb, path, pool); 639251881Speter svn_xml_make_close_tag(&sb, pool, "path"); 640251881Speter } 641251881Speter 642251881Speter /* </paths> */ 643251881Speter svn_xml_make_close_tag(&sb, pool, "paths"); 644251881Speter } 645251881Speter 646251881Speter if (message != NULL) 647251881Speter { 648251881Speter /* <msg>xxx</msg> */ 649251881Speter svn_cl__xml_tagged_cdata(&sb, pool, "msg", message); 650251881Speter } 651251881Speter 652251881Speter svn_compat_log_revprops_clear(log_entry->revprops); 653251881Speter if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0) 654251881Speter { 655251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL); 656251881Speter SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops, 657251881Speter FALSE, /* name_only */ 658251881Speter FALSE, pool)); 659251881Speter svn_xml_make_close_tag(&sb, pool, "revprops"); 660251881Speter } 661251881Speter 662251881Speter if (log_entry->has_children) 663251881Speter APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision; 664251881Speter else 665251881Speter svn_xml_make_close_tag(&sb, pool, "logentry"); 666251881Speter 667251881Speter return svn_cl__error_checked_fputs(sb->data, stdout); 668251881Speter} 669251881Speter 670251881Speter 671251881Speter/* This implements the `svn_opt_subcommand_t' interface. */ 672251881Spetersvn_error_t * 673251881Spetersvn_cl__log(apr_getopt_t *os, 674251881Speter void *baton, 675251881Speter apr_pool_t *pool) 676251881Speter{ 677251881Speter svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 678251881Speter svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 679251881Speter apr_array_header_t *targets; 680251881Speter struct log_receiver_baton lb; 681251881Speter const char *target; 682251881Speter int i; 683251881Speter apr_array_header_t *revprops; 684251881Speter 685251881Speter if (!opt_state->xml) 686251881Speter { 687251881Speter if (opt_state->all_revprops) 688251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 689251881Speter _("'with-all-revprops' option only valid in" 690251881Speter " XML mode")); 691251881Speter if (opt_state->no_revprops) 692251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 693251881Speter _("'with-no-revprops' option only valid in" 694251881Speter " XML mode")); 695251881Speter if (opt_state->revprop_table != NULL) 696251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 697251881Speter _("'with-revprop' option only valid in" 698251881Speter " XML mode")); 699251881Speter } 700251881Speter else 701251881Speter { 702251881Speter if (opt_state->show_diff) 703251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 704251881Speter _("'diff' option is not supported in " 705251881Speter "XML mode")); 706251881Speter } 707251881Speter 708251881Speter if (opt_state->quiet && opt_state->show_diff) 709251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 710251881Speter _("'quiet' and 'diff' options are " 711251881Speter "mutually exclusive")); 712251881Speter if (opt_state->diff.diff_cmd && (! opt_state->show_diff)) 713251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 714251881Speter _("'diff-cmd' option requires 'diff' " 715251881Speter "option")); 716251881Speter if (opt_state->diff.internal_diff && (! opt_state->show_diff)) 717251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 718251881Speter _("'internal-diff' option requires " 719251881Speter "'diff' option")); 720251881Speter if (opt_state->extensions && (! opt_state->show_diff)) 721251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 722251881Speter _("'extensions' option requires 'diff' " 723251881Speter "option")); 724251881Speter 725251881Speter if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff)) 726251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 727251881Speter _("'depth' option requires 'diff' option")); 728251881Speter 729251881Speter SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 730251881Speter opt_state->targets, 731251881Speter ctx, FALSE, pool)); 732251881Speter 733251881Speter /* Add "." if user passed 0 arguments */ 734251881Speter svn_opt_push_implicit_dot_target(targets, pool); 735251881Speter 736251881Speter /* Determine if they really want a two-revision range. */ 737251881Speter if (opt_state->used_change_arg) 738251881Speter { 739251881Speter if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1) 740251881Speter { 741251881Speter return svn_error_create 742251881Speter (SVN_ERR_CLIENT_BAD_REVISION, NULL, 743251881Speter _("-c and -r are mutually exclusive")); 744251881Speter } 745251881Speter for (i = 0; i < opt_state->revision_ranges->nelts; i++) 746251881Speter { 747251881Speter svn_opt_revision_range_t *range; 748251881Speter range = APR_ARRAY_IDX(opt_state->revision_ranges, i, 749251881Speter svn_opt_revision_range_t *); 750251881Speter if (range->start.value.number < range->end.value.number) 751251881Speter range->start.value.number++; 752251881Speter else 753251881Speter range->end.value.number++; 754251881Speter } 755251881Speter } 756251881Speter 757251881Speter /* Parse the first target into path-or-url and peg revision. */ 758251881Speter target = APR_ARRAY_IDX(targets, 0, const char *); 759251881Speter SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url, 760251881Speter target, pool)); 761251881Speter if (lb.target_peg_revision.kind == svn_opt_revision_unspecified) 762251881Speter lb.target_peg_revision.kind = (svn_path_is_url(target) 763251881Speter ? svn_opt_revision_head 764251881Speter : svn_opt_revision_working); 765251881Speter APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url; 766251881Speter 767251881Speter if (svn_path_is_url(target)) 768251881Speter { 769251881Speter for (i = 1; i < targets->nelts; i++) 770251881Speter { 771251881Speter target = APR_ARRAY_IDX(targets, i, const char *); 772251881Speter 773251881Speter if (svn_path_is_url(target) || target[0] == '/') 774251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 775251881Speter _("Only relative paths can be specified" 776251881Speter " after a URL for 'svn log', " 777251881Speter "but '%s' is not a relative path"), 778251881Speter target); 779251881Speter } 780251881Speter } 781251881Speter 782251881Speter lb.ctx = ctx; 783251881Speter lb.omit_log_message = opt_state->quiet; 784251881Speter lb.show_diff = opt_state->show_diff; 785251881Speter lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity 786251881Speter : opt_state->depth; 787251881Speter lb.diff_extensions = opt_state->extensions; 788251881Speter lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t)); 789251881Speter lb.search_patterns = opt_state->search_patterns; 790251881Speter lb.pool = pool; 791251881Speter 792251881Speter if (opt_state->xml) 793251881Speter { 794251881Speter /* If output is not incremental, output the XML header and wrap 795251881Speter everything in a top-level element. This makes the output in 796251881Speter its entirety a well-formed XML document. */ 797251881Speter if (! opt_state->incremental) 798251881Speter SVN_ERR(svn_cl__xml_print_header("log", pool)); 799251881Speter 800251881Speter if (opt_state->all_revprops) 801251881Speter revprops = NULL; 802251881Speter else if(opt_state->no_revprops) 803251881Speter { 804251881Speter revprops = apr_array_make(pool, 0, sizeof(char *)); 805251881Speter } 806251881Speter else if (opt_state->revprop_table != NULL) 807251881Speter { 808251881Speter apr_hash_index_t *hi; 809251881Speter revprops = apr_array_make(pool, 810251881Speter apr_hash_count(opt_state->revprop_table), 811251881Speter sizeof(char *)); 812251881Speter for (hi = apr_hash_first(pool, opt_state->revprop_table); 813251881Speter hi != NULL; 814251881Speter hi = apr_hash_next(hi)) 815251881Speter { 816251881Speter const char *property = svn__apr_hash_index_key(hi); 817251881Speter svn_string_t *value = svn__apr_hash_index_val(hi); 818251881Speter 819251881Speter if (value && value->data[0] != '\0') 820251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 821251881Speter _("cannot assign with 'with-revprop'" 822251881Speter " option (drop the '=')")); 823251881Speter APR_ARRAY_PUSH(revprops, const char *) = property; 824251881Speter } 825251881Speter } 826251881Speter else 827251881Speter { 828251881Speter revprops = apr_array_make(pool, 3, sizeof(char *)); 829251881Speter APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; 830251881Speter APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; 831251881Speter if (!opt_state->quiet) 832251881Speter APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; 833251881Speter } 834251881Speter SVN_ERR(svn_client_log5(targets, 835251881Speter &lb.target_peg_revision, 836251881Speter opt_state->revision_ranges, 837251881Speter opt_state->limit, 838251881Speter opt_state->verbose, 839251881Speter opt_state->stop_on_copy, 840251881Speter opt_state->use_merge_history, 841251881Speter revprops, 842251881Speter log_entry_receiver_xml, 843251881Speter &lb, 844251881Speter ctx, 845251881Speter pool)); 846251881Speter 847251881Speter if (! opt_state->incremental) 848251881Speter SVN_ERR(svn_cl__xml_print_footer("log", pool)); 849251881Speter } 850251881Speter else /* default output format */ 851251881Speter { 852251881Speter revprops = apr_array_make(pool, 3, sizeof(char *)); 853251881Speter APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR; 854251881Speter APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE; 855251881Speter if (!opt_state->quiet) 856251881Speter APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG; 857251881Speter SVN_ERR(svn_client_log5(targets, 858251881Speter &lb.target_peg_revision, 859251881Speter opt_state->revision_ranges, 860251881Speter opt_state->limit, 861251881Speter opt_state->verbose, 862251881Speter opt_state->stop_on_copy, 863251881Speter opt_state->use_merge_history, 864251881Speter revprops, 865251881Speter log_entry_receiver, 866251881Speter &lb, 867251881Speter ctx, 868251881Speter pool)); 869251881Speter 870251881Speter if (! opt_state->incremental) 871251881Speter SVN_ERR(svn_cmdline_printf(pool, SEP_STRING)); 872251881Speter } 873251881Speter 874251881Speter return SVN_NO_ERROR; 875251881Speter} 876