util.c revision 289166
1/* 2 * util.c: Subversion command line client utility functions. Any 3 * functions that need to be shared across subcommands should be put 4 * in here. 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 */ 25 26/* ==================================================================== */ 27 28 29 30/*** Includes. ***/ 31 32#include <string.h> 33#include <ctype.h> 34#include <assert.h> 35 36#include <apr_env.h> 37#include <apr_errno.h> 38#include <apr_file_info.h> 39#include <apr_strings.h> 40#include <apr_tables.h> 41#include <apr_general.h> 42#include <apr_lib.h> 43 44#include "svn_pools.h" 45#include "svn_error.h" 46#include "svn_ctype.h" 47#include "svn_client.h" 48#include "svn_cmdline.h" 49#include "svn_string.h" 50#include "svn_dirent_uri.h" 51#include "svn_path.h" 52#include "svn_hash.h" 53#include "svn_io.h" 54#include "svn_utf.h" 55#include "svn_subst.h" 56#include "svn_config.h" 57#include "svn_wc.h" 58#include "svn_xml.h" 59#include "svn_time.h" 60#include "svn_props.h" 61#include "svn_private_config.h" 62#include "cl.h" 63 64#include "private/svn_token.h" 65#include "private/svn_opt_private.h" 66#include "private/svn_client_private.h" 67#include "private/svn_cmdline_private.h" 68#include "private/svn_string_private.h" 69#ifdef HAS_ORGANIZATION_NAME 70#include "freebsd-organization.h" 71#endif 72 73 74 75 76svn_error_t * 77svn_cl__print_commit_info(const svn_commit_info_t *commit_info, 78 void *baton, 79 apr_pool_t *pool) 80{ 81 if (SVN_IS_VALID_REVNUM(commit_info->revision)) 82 SVN_ERR(svn_cmdline_printf(pool, _("\nCommitted revision %ld%s.\n"), 83 commit_info->revision, 84 commit_info->revision == 42 && 85 getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS") 86 ? _(" (the answer to life, the universe, " 87 "and everything)") 88 : "")); 89 90 /* Writing to stdout, as there maybe systems that consider the 91 * presence of stderr as an indication of commit failure. 92 * OTOH, this is only of informational nature to the user as 93 * the commit has succeeded. */ 94 if (commit_info->post_commit_err) 95 SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"), 96 commit_info->post_commit_err)); 97 98 return SVN_NO_ERROR; 99} 100 101 102svn_error_t * 103svn_cl__merge_file_externally(const char *base_path, 104 const char *their_path, 105 const char *my_path, 106 const char *merged_path, 107 const char *wc_path, 108 apr_hash_t *config, 109 svn_boolean_t *remains_in_conflict, 110 apr_pool_t *pool) 111{ 112 char *merge_tool; 113 /* Error if there is no editor specified */ 114 if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS) 115 { 116 struct svn_config_t *cfg; 117 merge_tool = NULL; 118 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 119 /* apr_env_get wants char **, this wants const char ** */ 120 svn_config_get(cfg, (const char **)&merge_tool, 121 SVN_CONFIG_SECTION_HELPERS, 122 SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL); 123 } 124 125 if (merge_tool) 126 { 127 const char *c; 128 129 for (c = merge_tool; *c; c++) 130 if (!svn_ctype_isspace(*c)) 131 break; 132 133 if (! *c) 134 return svn_error_create 135 (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL, 136 _("The SVN_MERGE environment variable is empty or " 137 "consists solely of whitespace. Expected a shell command.\n")); 138 } 139 else 140 return svn_error_create 141 (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL, 142 _("The environment variable SVN_MERGE and the merge-tool-cmd run-time " 143 "configuration option were not set.\n")); 144 145 { 146 const char *arguments[7] = { 0 }; 147 char *cwd; 148 int exitcode; 149 150 apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool); 151 if (status != 0) 152 return svn_error_wrap_apr(status, NULL); 153 154 arguments[0] = merge_tool; 155 arguments[1] = base_path; 156 arguments[2] = their_path; 157 arguments[3] = my_path; 158 arguments[4] = merged_path; 159 arguments[5] = wc_path; 160 arguments[6] = NULL; 161 162 SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool, 163 arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL, 164 pool)); 165 /* Exit code 0 means the merge was successful. 166 * Exit code 1 means the file was left in conflict but it 167 * is OK to continue with the merge. 168 * Any other exit code means there was a real problem. */ 169 if (exitcode != 0 && exitcode != 1) 170 return svn_error_createf 171 (SVN_ERR_EXTERNAL_PROGRAM, NULL, 172 _("The external merge tool exited with exit code %d"), exitcode); 173 else if (remains_in_conflict) 174 *remains_in_conflict = exitcode == 1; 175 } 176 return SVN_NO_ERROR; 177} 178 179 180/* A svn_client_ctx_t's log_msg_baton3, for use with 181 svn_cl__make_log_msg_baton(). */ 182struct log_msg_baton 183{ 184 const char *editor_cmd; /* editor specified via --editor-cmd, else NULL */ 185 const char *message; /* the message. */ 186 const char *message_encoding; /* the locale/encoding of the message. */ 187 const char *base_dir; /* the base directory for an external edit. UTF-8! */ 188 const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */ 189 svn_boolean_t non_interactive; /* if true, don't pop up an editor */ 190 apr_hash_t *config; /* client configuration hash */ 191 svn_boolean_t keep_locks; /* Keep repository locks? */ 192 apr_pool_t *pool; /* a pool. */ 193}; 194 195 196svn_error_t * 197svn_cl__make_log_msg_baton(void **baton, 198 svn_cl__opt_state_t *opt_state, 199 const char *base_dir /* UTF-8! */, 200 apr_hash_t *config, 201 apr_pool_t *pool) 202{ 203 struct log_msg_baton *lmb = apr_palloc(pool, sizeof(*lmb)); 204 205 if (opt_state->filedata) 206 { 207 if (strlen(opt_state->filedata->data) < opt_state->filedata->len) 208 { 209 /* The data contains a zero byte, and therefore can't be 210 represented as a C string. Punt now; it's probably not 211 a deliberate encoding, and even if it is, we still 212 can't handle it. */ 213 return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL, 214 _("Log message contains a zero byte")); 215 } 216 lmb->message = opt_state->filedata->data; 217 } 218 else 219 { 220 lmb->message = opt_state->message; 221 } 222 223 lmb->editor_cmd = opt_state->editor_cmd; 224 if (opt_state->encoding) 225 { 226 lmb->message_encoding = opt_state->encoding; 227 } 228 else if (config) 229 { 230 svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); 231 svn_config_get(cfg, &(lmb->message_encoding), 232 SVN_CONFIG_SECTION_MISCELLANY, 233 SVN_CONFIG_OPTION_LOG_ENCODING, 234 NULL); 235 } 236 237 lmb->base_dir = base_dir ? base_dir : ""; 238 lmb->tmpfile_left = NULL; 239 lmb->config = config; 240 lmb->keep_locks = opt_state->no_unlock; 241 lmb->non_interactive = opt_state->non_interactive; 242 lmb->pool = pool; 243 *baton = lmb; 244 return SVN_NO_ERROR; 245} 246 247 248svn_error_t * 249svn_cl__cleanup_log_msg(void *log_msg_baton, 250 svn_error_t *commit_err, 251 apr_pool_t *pool) 252{ 253 struct log_msg_baton *lmb = log_msg_baton; 254 svn_error_t *err; 255 256 /* If there was no tmpfile left, or there is no log message baton, 257 return COMMIT_ERR. */ 258 if ((! lmb) || (! lmb->tmpfile_left)) 259 return commit_err; 260 261 /* If there was no commit error, cleanup the tmpfile and return. */ 262 if (! commit_err) 263 return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool); 264 265 /* There was a commit error; there is a tmpfile. Leave the tmpfile 266 around, and add message about its presence to the commit error 267 chain. Then return COMMIT_ERR. If the conversion from UTF-8 to 268 native encoding fails, we have to compose that error with the 269 commit error chain, too. */ 270 271 err = svn_error_createf(commit_err->apr_err, NULL, 272 _(" '%s'"), 273 svn_dirent_local_style(lmb->tmpfile_left, pool)); 274 svn_error_compose(commit_err, 275 svn_error_create(commit_err->apr_err, err, 276 _("Your commit message was left in " 277 "a temporary file:"))); 278 return commit_err; 279} 280 281 282/* Remove line-starting PREFIX and everything after it from BUFFER. 283 If NEW_LEN is non-NULL, return the new length of BUFFER in 284 *NEW_LEN. */ 285static void 286truncate_buffer_at_prefix(apr_size_t *new_len, 287 char *buffer, 288 const char *prefix) 289{ 290 char *substring = buffer; 291 292 assert(buffer && prefix); 293 294 /* Initialize *NEW_LEN. */ 295 if (new_len) 296 *new_len = strlen(buffer); 297 298 while (1) 299 { 300 /* Find PREFIX in BUFFER. */ 301 substring = strstr(substring, prefix); 302 if (! substring) 303 return; 304 305 /* We found PREFIX. Is it really a PREFIX? Well, if it's the first 306 thing in the file, or if the character before it is a 307 line-terminator character, it sure is. */ 308 if ((substring == buffer) 309 || (*(substring - 1) == '\r') 310 || (*(substring - 1) == '\n')) 311 { 312 *substring = '\0'; 313 if (new_len) 314 *new_len = substring - buffer; 315 } 316 else if (substring) 317 { 318 /* Well, it wasn't really a prefix, so just advance by 1 319 character and continue. */ 320 substring++; 321 } 322 } 323 324 /* NOTREACHED */ 325} 326 327 328/* 329 * Since we're adding freebsd-specific tokens to the log message, 330 * clean out any leftovers to avoid accidently sending them to other 331 * projects that won't be expecting them. 332 */ 333 334static const char *prefixes[] = { 335 "PR:", 336 "Submitted by:", 337 "Reviewed by:", 338 "Approved by:", 339 "Obtained from:", 340 "MFC after:", 341 "Relnotes:", 342 "Security:", 343 "Sponsored by:", 344 "Differential Revision:", 345}; 346 347void 348cleanmsg(apr_size_t *l, char *s) 349{ 350 int i; 351 char *pos; 352 char *kw; 353 char *p; 354 int empty; 355 356 for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) { 357 pos = s; 358 while ((kw = strstr(pos, prefixes[i])) != NULL) { 359 /* Check to see if keyword is at start of line (or buffer) */ 360 if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) { 361 pos = kw + 1; 362 continue; 363 } 364 p = kw + strlen(prefixes[i]); 365 empty = 1; 366 while (1) { 367 if (*p == ' ' || *p == '\t') { 368 p++; 369 continue; 370 } 371 if (*p == '\0' || *p == '\r' || *p == '\n') 372 break; 373 empty = 0; 374 break; 375 } 376 if (empty && (*p == '\r' || *p == '\n')) { 377 memmove(kw, p + 1, strlen(p + 1) + 1); 378 if (l) 379 *l -= (p + 1 - kw); 380 } else if (empty) { 381 *kw = '\0'; 382 if (l) 383 *l -= (p - kw); 384 } else { 385 pos = p; 386 } 387 } 388 } 389} 390 391#define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--") 392 393svn_error_t * 394svn_cl__get_log_message(const char **log_msg, 395 const char **tmp_file, 396 const apr_array_header_t *commit_items, 397 void *baton, 398 apr_pool_t *pool) 399{ 400 svn_stringbuf_t *default_msg = NULL; 401 struct log_msg_baton *lmb = baton; 402 svn_stringbuf_t *message = NULL; 403 svn_config_t *cfg; 404 const char *mfc_after, *sponsored_by; 405 406 cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 407 408 /* Set default message. */ 409 default_msg = svn_stringbuf_create(APR_EOL_STR, pool); 410 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 411 svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR); 412 svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR); 413 svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR); 414 svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR); 415 svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR); 416 svn_stringbuf_appendcstr(default_msg, "MFC after:\t"); 417 svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL); 418 if (mfc_after != NULL) 419 svn_stringbuf_appendcstr(default_msg, mfc_after); 420 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 421 svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR); 422 svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR); 423 svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t"); 424 svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by", 425#ifdef HAS_ORGANIZATION_NAME 426 ORGANIZATION_NAME); 427#else 428 NULL); 429#endif 430 if (sponsored_by != NULL) 431 svn_stringbuf_appendcstr(default_msg, sponsored_by); 432 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 433 svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR); 434 svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX); 435 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 436 svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR); 437 svn_stringbuf_appendcstr(default_msg, "> PR: If a Bugzilla PR is affected by the change." APR_EOL_STR); 438 svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR); 439 svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR); 440 svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR); 441 svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR); 442 svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR); 443 svn_stringbuf_appendcstr(default_msg, "> MFH: Ports tree branch name. Request approval for merge." APR_EOL_STR); 444 svn_stringbuf_appendcstr(default_msg, "> Relnotes: Set to 'yes' for mention in release notes." APR_EOL_STR); 445 svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR); 446 svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR); 447 svn_stringbuf_appendcstr(default_msg, "> Differential Revision: https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR); 448 svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR); 449 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 450 451 *tmp_file = NULL; 452 if (lmb->message) 453 { 454 svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool); 455 svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str)); 456 457 /* Trim incoming messages of the EOF marker text and the junk 458 that follows it. */ 459 truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data, 460 EDITOR_EOF_PREFIX); 461 cleanmsg(NULL, (char*)log_msg_buf->data); 462 463 /* Make a string from a stringbuf, sharing the data allocation. */ 464 log_msg_str->data = log_msg_buf->data; 465 log_msg_str->len = log_msg_buf->len; 466 SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE, 467 log_msg_str, lmb->message_encoding, 468 FALSE, pool, pool), 469 _("Error normalizing log message to internal format")); 470 471 *log_msg = log_msg_str->data; 472 return SVN_NO_ERROR; 473 } 474 475 if (! commit_items->nelts) 476 { 477 *log_msg = ""; 478 return SVN_NO_ERROR; 479 } 480 481 while (! message) 482 { 483 /* We still don't have a valid commit message. Use $EDITOR to 484 get one. Note that svn_cl__edit_string_externally will still 485 return a UTF-8'ized log message. */ 486 int i; 487 svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool); 488 svn_error_t *err = SVN_NO_ERROR; 489 svn_string_t *msg_string = svn_string_create_empty(pool); 490 491 for (i = 0; i < commit_items->nelts; i++) 492 { 493 svn_client_commit_item3_t *item 494 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 495 const char *path = item->path; 496 char text_mod = '_', prop_mod = ' ', unlock = ' '; 497 498 if (! path) 499 path = item->url; 500 else if (! *path) 501 path = "."; 502 503 if (! svn_path_is_url(path) && lmb->base_dir) 504 path = svn_dirent_is_child(lmb->base_dir, path, pool); 505 506 /* If still no path, then just use current directory. */ 507 if (! path) 508 path = "."; 509 510 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 511 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 512 text_mod = 'R'; 513 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 514 text_mod = 'A'; 515 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 516 text_mod = 'D'; 517 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 518 text_mod = 'M'; 519 520 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 521 prop_mod = 'M'; 522 523 if (! lmb->keep_locks 524 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN) 525 unlock = 'U'; 526 527 svn_stringbuf_appendbyte(tmp_message, text_mod); 528 svn_stringbuf_appendbyte(tmp_message, prop_mod); 529 svn_stringbuf_appendbyte(tmp_message, unlock); 530 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 531 /* History included via copy/move. */ 532 svn_stringbuf_appendcstr(tmp_message, "+ "); 533 else 534 svn_stringbuf_appendcstr(tmp_message, " "); 535 svn_stringbuf_appendcstr(tmp_message, path); 536 svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR); 537 } 538 539 msg_string->data = tmp_message->data; 540 msg_string->len = tmp_message->len; 541 542 /* Use the external edit to get a log message. */ 543 if (! lmb->non_interactive) 544 { 545 err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left, 546 lmb->editor_cmd, lmb->base_dir, 547 msg_string, "svn-commit", 548 lmb->config, TRUE, 549 lmb->message_encoding, 550 pool); 551 } 552 else /* non_interactive flag says we can't pop up an editor, so error */ 553 { 554 return svn_error_create 555 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 556 _("Cannot invoke editor to get log message " 557 "when non-interactive")); 558 } 559 560 /* Dup the tmpfile path into its baton's pool. */ 561 *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool, 562 lmb->tmpfile_left); 563 564 /* If the edit returned an error, handle it. */ 565 if (err) 566 { 567 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR) 568 err = svn_error_quick_wrap 569 (err, _("Could not use external editor to fetch log message; " 570 "consider setting the $SVN_EDITOR environment variable " 571 "or using the --message (-m) or --file (-F) options")); 572 return svn_error_trace(err); 573 } 574 575 if (msg_string) 576 message = svn_stringbuf_create_from_string(msg_string, pool); 577 578 /* Strip the prefix from the buffer. */ 579 if (message) 580 truncate_buffer_at_prefix(&message->len, message->data, 581 EDITOR_EOF_PREFIX); 582 /* 583 * Since we're adding freebsd-specific tokens to the log message, 584 * clean out any leftovers to avoid accidently sending them to other 585 * projects that won't be expecting them. 586 */ 587 if (message) 588 cleanmsg(&message->len, message->data); 589 590 if (message) 591 { 592 /* We did get message, now check if it is anything more than just 593 white space as we will consider white space only as empty */ 594 apr_size_t len; 595 596 for (len = 0; len < message->len; len++) 597 { 598 /* FIXME: should really use an UTF-8 whitespace test 599 rather than svn_ctype_isspace, which is ASCII only */ 600 if (! svn_ctype_isspace(message->data[len])) 601 break; 602 } 603 if (len == message->len) 604 message = NULL; 605 } 606 607 if (! message) 608 { 609 const char *reply; 610 SVN_ERR(svn_cmdline_prompt_user2 611 (&reply, 612 _("\nLog message unchanged or not specified\n" 613 "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool)); 614 if (reply) 615 { 616 int letter = apr_tolower(reply[0]); 617 618 /* If the user chooses to abort, we cleanup the 619 temporary file and exit the loop with a NULL 620 message. */ 621 if ('a' == letter) 622 { 623 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); 624 *tmp_file = lmb->tmpfile_left = NULL; 625 break; 626 } 627 628 /* If the user chooses to continue, we make an empty 629 message, which will cause us to exit the loop. We 630 also cleanup the temporary file. */ 631 if ('c' == letter) 632 { 633 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); 634 *tmp_file = lmb->tmpfile_left = NULL; 635 message = svn_stringbuf_create_empty(pool); 636 } 637 638 /* If the user chooses anything else, the loop will 639 continue on the NULL message. */ 640 } 641 } 642 } 643 644 *log_msg = message ? message->data : NULL; 645 return SVN_NO_ERROR; 646} 647 648 649/* ### The way our error wrapping currently works, the error returned 650 * from here will look as though it originates in this source file, 651 * instead of in the caller's source file. This can be a bit 652 * misleading, until one starts debugging. Ideally, there'd be a way 653 * to wrap an error while preserving its FILE/LINE info. 654 */ 655svn_error_t * 656svn_cl__may_need_force(svn_error_t *err) 657{ 658 if (err 659 && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE || 660 err->apr_err == SVN_ERR_CLIENT_MODIFIED)) 661 { 662 /* Should this svn_error_compose a new error number? Probably not, 663 the error hasn't changed. */ 664 err = svn_error_quick_wrap 665 (err, _("Use --force to override this restriction (local modifications " 666 "may be lost)")); 667 } 668 669 return svn_error_trace(err); 670} 671 672 673svn_error_t * 674svn_cl__error_checked_fputs(const char *string, FILE* stream) 675{ 676 /* On POSIX systems, errno will be set on an error in fputs, but this might 677 not be the case on other platforms. We reset errno and only 678 use it if it was set by the below fputs call. Else, we just return 679 a generic error. */ 680 errno = 0; 681 682 if (fputs(string, stream) == EOF) 683 { 684 if (errno) 685 return svn_error_wrap_apr(errno, _("Write error")); 686 else 687 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 688 } 689 690 return SVN_NO_ERROR; 691} 692 693 694svn_error_t * 695svn_cl__try(svn_error_t *err, 696 apr_array_header_t *errors_seen, 697 svn_boolean_t quiet, 698 ...) 699{ 700 if (err) 701 { 702 apr_status_t apr_err; 703 va_list ap; 704 705 va_start(ap, quiet); 706 while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS) 707 { 708 if (errors_seen) 709 { 710 int i; 711 svn_boolean_t add = TRUE; 712 713 /* Don't report duplicate error codes. */ 714 for (i = 0; i < errors_seen->nelts; i++) 715 { 716 if (APR_ARRAY_IDX(errors_seen, i, 717 apr_status_t) == err->apr_err) 718 { 719 add = FALSE; 720 break; 721 } 722 } 723 if (add) 724 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err; 725 } 726 if (err->apr_err == apr_err) 727 { 728 if (! quiet) 729 svn_handle_warning2(stderr, err, "svn: "); 730 svn_error_clear(err); 731 return SVN_NO_ERROR; 732 } 733 } 734 va_end(ap); 735 } 736 737 return svn_error_trace(err); 738} 739 740 741void 742svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb, 743 apr_pool_t *pool, 744 const char *tagname, 745 const char *string) 746{ 747 if (string) 748 { 749 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata, 750 tagname, NULL); 751 svn_xml_escape_cdata_cstring(sb, string, pool); 752 svn_xml_make_close_tag(sb, pool, tagname); 753 } 754} 755 756 757void 758svn_cl__print_xml_commit(svn_stringbuf_t **sb, 759 svn_revnum_t revision, 760 const char *author, 761 const char *date, 762 apr_pool_t *pool) 763{ 764 /* "<commit ...>" */ 765 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit", 766 "revision", 767 apr_psprintf(pool, "%ld", revision), NULL); 768 769 /* "<author>xx</author>" */ 770 if (author) 771 svn_cl__xml_tagged_cdata(sb, pool, "author", author); 772 773 /* "<date>xx</date>" */ 774 if (date) 775 svn_cl__xml_tagged_cdata(sb, pool, "date", date); 776 777 /* "</commit>" */ 778 svn_xml_make_close_tag(sb, pool, "commit"); 779} 780 781 782void 783svn_cl__print_xml_lock(svn_stringbuf_t **sb, 784 const svn_lock_t *lock, 785 apr_pool_t *pool) 786{ 787 /* "<lock>" */ 788 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL); 789 790 /* "<token>xx</token>" */ 791 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token); 792 793 /* "<owner>xx</owner>" */ 794 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner); 795 796 /* "<comment>xx</comment>" */ 797 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment); 798 799 /* "<created>xx</created>" */ 800 svn_cl__xml_tagged_cdata(sb, pool, "created", 801 svn_time_to_cstring(lock->creation_date, pool)); 802 803 /* "<expires>xx</expires>" */ 804 if (lock->expiration_date != 0) 805 svn_cl__xml_tagged_cdata(sb, pool, "expires", 806 svn_time_to_cstring(lock->expiration_date, pool)); 807 808 /* "</lock>" */ 809 svn_xml_make_close_tag(sb, pool, "lock"); 810} 811 812 813svn_error_t * 814svn_cl__xml_print_header(const char *tagname, 815 apr_pool_t *pool) 816{ 817 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 818 819 /* <?xml version="1.0" encoding="UTF-8"?> */ 820 svn_xml_make_header2(&sb, "UTF-8", pool); 821 822 /* "<TAGNAME>" */ 823 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL); 824 825 return svn_cl__error_checked_fputs(sb->data, stdout); 826} 827 828 829svn_error_t * 830svn_cl__xml_print_footer(const char *tagname, 831 apr_pool_t *pool) 832{ 833 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 834 835 /* "</TAGNAME>" */ 836 svn_xml_make_close_tag(&sb, pool, tagname); 837 return svn_cl__error_checked_fputs(sb->data, stdout); 838} 839 840 841/* A map for svn_node_kind_t values to XML strings */ 842static const svn_token_map_t map_node_kind_xml[] = 843{ 844 { "none", svn_node_none }, 845 { "file", svn_node_file }, 846 { "dir", svn_node_dir }, 847 { "", svn_node_unknown }, 848 { NULL, 0 } 849}; 850 851/* A map for svn_node_kind_t values to human-readable strings */ 852static const svn_token_map_t map_node_kind_human[] = 853{ 854 { N_("none"), svn_node_none }, 855 { N_("file"), svn_node_file }, 856 { N_("dir"), svn_node_dir }, 857 { "", svn_node_unknown }, 858 { NULL, 0 } 859}; 860 861const char * 862svn_cl__node_kind_str_xml(svn_node_kind_t kind) 863{ 864 return svn_token__to_word(map_node_kind_xml, kind); 865} 866 867const char * 868svn_cl__node_kind_str_human_readable(svn_node_kind_t kind) 869{ 870 return _(svn_token__to_word(map_node_kind_human, kind)); 871} 872 873 874/* A map for svn_wc_operation_t values to XML strings */ 875static const svn_token_map_t map_wc_operation_xml[] = 876{ 877 { "none", svn_wc_operation_none }, 878 { "update", svn_wc_operation_update }, 879 { "switch", svn_wc_operation_switch }, 880 { "merge", svn_wc_operation_merge }, 881 { NULL, 0 } 882}; 883 884/* A map for svn_wc_operation_t values to human-readable strings */ 885static const svn_token_map_t map_wc_operation_human[] = 886{ 887 { N_("none"), svn_wc_operation_none }, 888 { N_("update"), svn_wc_operation_update }, 889 { N_("switch"), svn_wc_operation_switch }, 890 { N_("merge"), svn_wc_operation_merge }, 891 { NULL, 0 } 892}; 893 894const char * 895svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool) 896{ 897 return svn_token__to_word(map_wc_operation_xml, operation); 898} 899 900const char * 901svn_cl__operation_str_human_readable(svn_wc_operation_t operation, 902 apr_pool_t *pool) 903{ 904 return _(svn_token__to_word(map_wc_operation_human, operation)); 905} 906 907 908svn_error_t * 909svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets, 910 apr_getopt_t *os, 911 const apr_array_header_t *known_targets, 912 svn_client_ctx_t *ctx, 913 svn_boolean_t keep_last_origpath_on_truepath_collision, 914 apr_pool_t *pool) 915{ 916 svn_error_t *err = svn_client_args_to_target_array2(targets, 917 os, 918 known_targets, 919 ctx, 920 keep_last_origpath_on_truepath_collision, 921 pool); 922 if (err) 923 { 924 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED) 925 { 926 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: "); 927 svn_error_clear(err); 928 } 929 else 930 return svn_error_trace(err); 931 } 932 return SVN_NO_ERROR; 933} 934 935 936/* Helper for svn_cl__get_changelist(); implements 937 svn_changelist_receiver_t. */ 938static svn_error_t * 939changelist_receiver(void *baton, 940 const char *path, 941 const char *changelist, 942 apr_pool_t *pool) 943{ 944 /* No need to check CHANGELIST; our caller only asked about one of them. */ 945 apr_array_header_t *paths = baton; 946 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path); 947 return SVN_NO_ERROR; 948} 949 950 951svn_error_t * 952svn_cl__changelist_paths(apr_array_header_t **paths, 953 const apr_array_header_t *changelists, 954 const apr_array_header_t *targets, 955 svn_depth_t depth, 956 svn_client_ctx_t *ctx, 957 apr_pool_t *result_pool, 958 apr_pool_t *scratch_pool) 959{ 960 apr_array_header_t *found; 961 apr_hash_t *paths_hash; 962 apr_pool_t *iterpool; 963 int i; 964 965 if (! (changelists && changelists->nelts)) 966 { 967 *paths = (apr_array_header_t *)targets; 968 return SVN_NO_ERROR; 969 } 970 971 found = apr_array_make(scratch_pool, 8, sizeof(const char *)); 972 iterpool = svn_pool_create(scratch_pool); 973 for (i = 0; i < targets->nelts; i++) 974 { 975 const char *target = APR_ARRAY_IDX(targets, i, const char *); 976 svn_pool_clear(iterpool); 977 SVN_ERR(svn_client_get_changelists(target, changelists, depth, 978 changelist_receiver, found, 979 ctx, iterpool)); 980 } 981 svn_pool_destroy(iterpool); 982 983 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool)); 984 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool)); 985} 986 987svn_cl__show_revs_t 988svn_cl__show_revs_from_word(const char *word) 989{ 990 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0) 991 return svn_cl__show_revs_merged; 992 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0) 993 return svn_cl__show_revs_eligible; 994 /* word is an invalid flavor. */ 995 return svn_cl__show_revs_invalid; 996} 997 998 999svn_error_t * 1000svn_cl__time_cstring_to_human_cstring(const char **human_cstring, 1001 const char *data, 1002 apr_pool_t *pool) 1003{ 1004 svn_error_t *err; 1005 apr_time_t when; 1006 1007 err = svn_time_from_cstring(&when, data, pool); 1008 if (err && err->apr_err == SVN_ERR_BAD_DATE) 1009 { 1010 svn_error_clear(err); 1011 1012 *human_cstring = _("(invalid date)"); 1013 return SVN_NO_ERROR; 1014 } 1015 else if (err) 1016 return svn_error_trace(err); 1017 1018 *human_cstring = svn_time_to_human_cstring(when, pool); 1019 1020 return SVN_NO_ERROR; 1021} 1022 1023const char * 1024svn_cl__node_description(const svn_wc_conflict_version_t *node, 1025 const char *wc_repos_root_URL, 1026 apr_pool_t *pool) 1027{ 1028 const char *root_str = "^"; 1029 const char *path_str = "..."; 1030 1031 if (!node) 1032 /* Printing "(none)" the harder way to ensure conformity (mostly with 1033 * translations). */ 1034 return apr_psprintf(pool, "(%s)", 1035 svn_cl__node_kind_str_human_readable(svn_node_none)); 1036 1037 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL. 1038 * Otherwise show the complete URL, and if we can't, show dots. */ 1039 1040 if (node->repos_url && 1041 (wc_repos_root_URL == NULL || 1042 strcmp(node->repos_url, wc_repos_root_URL) != 0)) 1043 root_str = node->repos_url; 1044 1045 if (node->path_in_repos) 1046 path_str = node->path_in_repos; 1047 1048 return apr_psprintf(pool, "(%s) %s@%ld", 1049 svn_cl__node_kind_str_human_readable(node->node_kind), 1050 svn_path_url_add_component2(root_str, path_str, pool), 1051 node->peg_rev); 1052} 1053 1054svn_error_t * 1055svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p, 1056 const apr_array_header_t *targets, 1057 apr_pool_t *pool) 1058{ 1059 int i; 1060 apr_array_header_t *true_targets; 1061 1062 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *)); 1063 1064 for (i = 0; i < targets->nelts; i++) 1065 { 1066 const char *target = APR_ARRAY_IDX(targets, i, const char *); 1067 const char *true_target, *peg; 1068 1069 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg, 1070 target, pool)); 1071 if (peg[0] && peg[1]) 1072 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1073 _("'%s': a peg revision is not allowed here"), 1074 target); 1075 APR_ARRAY_PUSH(true_targets, const char *) = true_target; 1076 } 1077 1078 SVN_ERR_ASSERT(true_targets_p); 1079 *true_targets_p = true_targets; 1080 1081 return SVN_NO_ERROR; 1082} 1083 1084svn_error_t * 1085svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets) 1086{ 1087 svn_error_t *err; 1088 1089 err = svn_client__assert_homogeneous_target_type(targets); 1090 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) 1091 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL); 1092 return err; 1093} 1094 1095svn_error_t * 1096svn_cl__check_target_is_local_path(const char *target) 1097{ 1098 if (svn_path_is_url(target)) 1099 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1100 _("'%s' is not a local path"), target); 1101 return SVN_NO_ERROR; 1102} 1103 1104svn_error_t * 1105svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets) 1106{ 1107 int i; 1108 1109 for (i = 0; i < targets->nelts; i++) 1110 { 1111 const char *target = APR_ARRAY_IDX(targets, i, const char *); 1112 1113 SVN_ERR(svn_cl__check_target_is_local_path(target)); 1114 } 1115 return SVN_NO_ERROR; 1116} 1117 1118const char * 1119svn_cl__local_style_skip_ancestor(const char *parent_path, 1120 const char *path, 1121 apr_pool_t *pool) 1122{ 1123 const char *relpath = NULL; 1124 1125 if (parent_path) 1126 relpath = svn_dirent_skip_ancestor(parent_path, path); 1127 1128 return svn_dirent_local_style(relpath ? relpath : path, pool); 1129} 1130 1131svn_error_t * 1132svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets, 1133 const char *propname, 1134 const svn_string_t *propval, 1135 apr_pool_t *scratch_pool) 1136{ 1137 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0) 1138 { 1139 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1140 int i; 1141 1142 for (i = 0; i < targets->nelts; i++) 1143 { 1144 const char *detected_mimetype; 1145 const char *target = APR_ARRAY_IDX(targets, i, const char *); 1146 const char *local_abspath; 1147 const svn_string_t *canon_propval; 1148 svn_node_kind_t node_kind; 1149 1150 svn_pool_clear(iterpool); 1151 1152 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); 1153 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool)); 1154 if (node_kind != svn_node_file) 1155 continue; 1156 1157 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval, 1158 propname, propval, 1159 local_abspath, 1160 svn_node_file, 1161 FALSE, NULL, NULL, 1162 iterpool)); 1163 1164 if (svn_mime_type_is_binary(canon_propval->data)) 1165 { 1166 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype, 1167 local_abspath, NULL, 1168 iterpool)); 1169 if (detected_mimetype == NULL || 1170 !svn_mime_type_is_binary(detected_mimetype)) 1171 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool, 1172 _("svn: warning: '%s' is a binary mime-type but file '%s' " 1173 "looks like text; diff, merge, blame, and other " 1174 "operations will stop working on this file\n"), 1175 canon_propval->data, 1176 svn_dirent_local_style(local_abspath, iterpool))); 1177 1178 } 1179 } 1180 svn_pool_destroy(iterpool); 1181 } 1182 1183 return SVN_NO_ERROR; 1184} 1185 1186