util.c revision 263655
1236769Sobrien/* 2236769Sobrien * util.c: Subversion command line client utility functions. Any 3236769Sobrien * functions that need to be shared across subcommands should be put 4236769Sobrien * in here. 5236769Sobrien * 6236769Sobrien * ==================================================================== 7236769Sobrien * Licensed to the Apache Software Foundation (ASF) under one 8236769Sobrien * or more contributor license agreements. See the NOTICE file 9236769Sobrien * distributed with this work for additional information 10236769Sobrien * regarding copyright ownership. The ASF licenses this file 11236769Sobrien * to you under the Apache License, Version 2.0 (the 12236769Sobrien * "License"); you may not use this file except in compliance 13236769Sobrien * with the License. You may obtain a copy of the License at 14236769Sobrien * 15236769Sobrien * http://www.apache.org/licenses/LICENSE-2.0 16236769Sobrien * 17236769Sobrien * Unless required by applicable law or agreed to in writing, 18236769Sobrien * software distributed under the License is distributed on an 19236769Sobrien * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20236769Sobrien * KIND, either express or implied. See the License for the 21236769Sobrien * specific language governing permissions and limitations 22236769Sobrien * under the License. 23236769Sobrien * ==================================================================== 24236769Sobrien */ 25236769Sobrien 26236769Sobrien/* ==================================================================== */ 27236769Sobrien 28236769Sobrien 29236769Sobrien 30236769Sobrien/*** Includes. ***/ 31236769Sobrien 32236769Sobrien#include <string.h> 33236769Sobrien#include <ctype.h> 34236769Sobrien#include <assert.h> 35236769Sobrien 36236769Sobrien#include <apr_env.h> 37236769Sobrien#include <apr_errno.h> 38236769Sobrien#include <apr_file_info.h> 39236769Sobrien#include <apr_strings.h> 40236769Sobrien#include <apr_tables.h> 41236769Sobrien#include <apr_general.h> 42236769Sobrien#include <apr_lib.h> 43236769Sobrien 44236769Sobrien#include "svn_pools.h" 45236769Sobrien#include "svn_error.h" 46236769Sobrien#include "svn_ctype.h" 47236769Sobrien#include "svn_client.h" 48236769Sobrien#include "svn_cmdline.h" 49236769Sobrien#include "svn_string.h" 50236769Sobrien#include "svn_dirent_uri.h" 51236769Sobrien#include "svn_path.h" 52236769Sobrien#include "svn_hash.h" 53236769Sobrien#include "svn_io.h" 54236769Sobrien#include "svn_utf.h" 55236769Sobrien#include "svn_subst.h" 56236769Sobrien#include "svn_config.h" 57236769Sobrien#include "svn_wc.h" 58236769Sobrien#include "svn_xml.h" 59236769Sobrien#include "svn_time.h" 60236769Sobrien#include "svn_props.h" 61236769Sobrien#include "svn_private_config.h" 62236769Sobrien#include "cl.h" 63236769Sobrien 64236769Sobrien#include "private/svn_token.h" 65236769Sobrien#include "private/svn_opt_private.h" 66236769Sobrien#include "private/svn_client_private.h" 67236769Sobrien#include "private/svn_cmdline_private.h" 68236769Sobrien#include "private/svn_string_private.h" 69236769Sobrien#ifdef HAS_ORGANIZATION_NAME 70236769Sobrien#include "freebsd-organization.h" 71236769Sobrien#endif 72236769Sobrien 73236769Sobrien 74236769Sobrien 75236769Sobrien 76236769Sobriensvn_error_t * 77236769Sobriensvn_cl__print_commit_info(const svn_commit_info_t *commit_info, 78236769Sobrien void *baton, 79236769Sobrien 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}; 345 346void 347cleanmsg(apr_size_t *l, char *s) 348{ 349 int i; 350 char *pos; 351 char *kw; 352 char *p; 353 int empty; 354 355 for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) { 356 pos = s; 357 while ((kw = strstr(pos, prefixes[i])) != NULL) { 358 /* Check to see if keyword is at start of line (or buffer) */ 359 if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) { 360 pos = kw + 1; 361 continue; 362 } 363 p = kw + strlen(prefixes[i]); 364 empty = 1; 365 while (1) { 366 if (*p == ' ' || *p == '\t') { 367 p++; 368 continue; 369 } 370 if (*p == '\0' || *p == '\r' || *p == '\n') 371 break; 372 empty = 0; 373 break; 374 } 375 if (empty && (*p == '\r' || *p == '\n')) { 376 memmove(kw, p + 1, strlen(p + 1) + 1); 377 if (l) 378 *l -= (p + 1 - kw); 379 } else if (empty) { 380 *kw = '\0'; 381 if (l) 382 *l -= (p - kw); 383 } else { 384 pos = p; 385 } 386 } 387 } 388} 389 390#define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--") 391 392svn_error_t * 393svn_cl__get_log_message(const char **log_msg, 394 const char **tmp_file, 395 const apr_array_header_t *commit_items, 396 void *baton, 397 apr_pool_t *pool) 398{ 399 svn_stringbuf_t *default_msg = NULL; 400 struct log_msg_baton *lmb = baton; 401 svn_stringbuf_t *message = NULL; 402 403 /* Set default message. */ 404 default_msg = svn_stringbuf_create(APR_EOL_STR, pool); 405 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 406 svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR); 407 svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR); 408 svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR); 409 svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR); 410 svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR); 411 svn_stringbuf_appendcstr(default_msg, "MFC after:\t" APR_EOL_STR); 412 svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR); 413 svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR); 414 svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t" 415#ifdef HAS_ORGANIZATION_NAME 416 ORGANIZATION_NAME 417#endif 418 APR_EOL_STR); 419 svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX); 420 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 421 svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR); 422 svn_stringbuf_appendcstr(default_msg, "> PR: If a GNATS PR is affected by the change." APR_EOL_STR); 423 svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR); 424 svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR); 425 svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR); 426 svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR); 427 svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR); 428 svn_stringbuf_appendcstr(default_msg, "> Relnotes: Set to 'yes' for mention in release notes." APR_EOL_STR); 429 svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR); 430 svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR); 431 svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR); 432 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 433 434 *tmp_file = NULL; 435 if (lmb->message) 436 { 437 svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool); 438 svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str)); 439 440 /* Trim incoming messages of the EOF marker text and the junk 441 that follows it. */ 442 truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data, 443 EDITOR_EOF_PREFIX); 444 cleanmsg(NULL, (char*)log_msg_buf->data); 445 446 /* Make a string from a stringbuf, sharing the data allocation. */ 447 log_msg_str->data = log_msg_buf->data; 448 log_msg_str->len = log_msg_buf->len; 449 SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE, 450 log_msg_str, lmb->message_encoding, 451 FALSE, pool, pool), 452 _("Error normalizing log message to internal format")); 453 454 *log_msg = log_msg_str->data; 455 return SVN_NO_ERROR; 456 } 457 458 if (! commit_items->nelts) 459 { 460 *log_msg = ""; 461 return SVN_NO_ERROR; 462 } 463 464 while (! message) 465 { 466 /* We still don't have a valid commit message. Use $EDITOR to 467 get one. Note that svn_cl__edit_string_externally will still 468 return a UTF-8'ized log message. */ 469 int i; 470 svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool); 471 svn_error_t *err = SVN_NO_ERROR; 472 svn_string_t *msg_string = svn_string_create_empty(pool); 473 474 for (i = 0; i < commit_items->nelts; i++) 475 { 476 svn_client_commit_item3_t *item 477 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 478 const char *path = item->path; 479 char text_mod = '_', prop_mod = ' ', unlock = ' '; 480 481 if (! path) 482 path = item->url; 483 else if (! *path) 484 path = "."; 485 486 if (! svn_path_is_url(path) && lmb->base_dir) 487 path = svn_dirent_is_child(lmb->base_dir, path, pool); 488 489 /* If still no path, then just use current directory. */ 490 if (! path) 491 path = "."; 492 493 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 494 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 495 text_mod = 'R'; 496 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 497 text_mod = 'A'; 498 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 499 text_mod = 'D'; 500 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 501 text_mod = 'M'; 502 503 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 504 prop_mod = 'M'; 505 506 if (! lmb->keep_locks 507 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN) 508 unlock = 'U'; 509 510 svn_stringbuf_appendbyte(tmp_message, text_mod); 511 svn_stringbuf_appendbyte(tmp_message, prop_mod); 512 svn_stringbuf_appendbyte(tmp_message, unlock); 513 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 514 /* History included via copy/move. */ 515 svn_stringbuf_appendcstr(tmp_message, "+ "); 516 else 517 svn_stringbuf_appendcstr(tmp_message, " "); 518 svn_stringbuf_appendcstr(tmp_message, path); 519 svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR); 520 } 521 522 msg_string->data = tmp_message->data; 523 msg_string->len = tmp_message->len; 524 525 /* Use the external edit to get a log message. */ 526 if (! lmb->non_interactive) 527 { 528 err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left, 529 lmb->editor_cmd, lmb->base_dir, 530 msg_string, "svn-commit", 531 lmb->config, TRUE, 532 lmb->message_encoding, 533 pool); 534 } 535 else /* non_interactive flag says we can't pop up an editor, so error */ 536 { 537 return svn_error_create 538 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 539 _("Cannot invoke editor to get log message " 540 "when non-interactive")); 541 } 542 543 /* Dup the tmpfile path into its baton's pool. */ 544 *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool, 545 lmb->tmpfile_left); 546 547 /* If the edit returned an error, handle it. */ 548 if (err) 549 { 550 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR) 551 err = svn_error_quick_wrap 552 (err, _("Could not use external editor to fetch log message; " 553 "consider setting the $SVN_EDITOR environment variable " 554 "or using the --message (-m) or --file (-F) options")); 555 return svn_error_trace(err); 556 } 557 558 if (msg_string) 559 message = svn_stringbuf_create_from_string(msg_string, pool); 560 561 /* Strip the prefix from the buffer. */ 562 if (message) 563 truncate_buffer_at_prefix(&message->len, message->data, 564 EDITOR_EOF_PREFIX); 565 /* 566 * Since we're adding freebsd-specific tokens to the log message, 567 * clean out any leftovers to avoid accidently sending them to other 568 * projects that won't be expecting them. 569 */ 570 if (message) 571 cleanmsg(&message->len, message->data); 572 573 if (message) 574 { 575 /* We did get message, now check if it is anything more than just 576 white space as we will consider white space only as empty */ 577 apr_size_t len; 578 579 for (len = 0; len < message->len; len++) 580 { 581 /* FIXME: should really use an UTF-8 whitespace test 582 rather than svn_ctype_isspace, which is ASCII only */ 583 if (! svn_ctype_isspace(message->data[len])) 584 break; 585 } 586 if (len == message->len) 587 message = NULL; 588 } 589 590 if (! message) 591 { 592 const char *reply; 593 SVN_ERR(svn_cmdline_prompt_user2 594 (&reply, 595 _("\nLog message unchanged or not specified\n" 596 "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool)); 597 if (reply) 598 { 599 int letter = apr_tolower(reply[0]); 600 601 /* If the user chooses to abort, we cleanup the 602 temporary file and exit the loop with a NULL 603 message. */ 604 if ('a' == letter) 605 { 606 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); 607 *tmp_file = lmb->tmpfile_left = NULL; 608 break; 609 } 610 611 /* If the user chooses to continue, we make an empty 612 message, which will cause us to exit the loop. We 613 also cleanup the temporary file. */ 614 if ('c' == letter) 615 { 616 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); 617 *tmp_file = lmb->tmpfile_left = NULL; 618 message = svn_stringbuf_create_empty(pool); 619 } 620 621 /* If the user chooses anything else, the loop will 622 continue on the NULL message. */ 623 } 624 } 625 } 626 627 *log_msg = message ? message->data : NULL; 628 return SVN_NO_ERROR; 629} 630 631 632/* ### The way our error wrapping currently works, the error returned 633 * from here will look as though it originates in this source file, 634 * instead of in the caller's source file. This can be a bit 635 * misleading, until one starts debugging. Ideally, there'd be a way 636 * to wrap an error while preserving its FILE/LINE info. 637 */ 638svn_error_t * 639svn_cl__may_need_force(svn_error_t *err) 640{ 641 if (err 642 && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE || 643 err->apr_err == SVN_ERR_CLIENT_MODIFIED)) 644 { 645 /* Should this svn_error_compose a new error number? Probably not, 646 the error hasn't changed. */ 647 err = svn_error_quick_wrap 648 (err, _("Use --force to override this restriction (local modifications " 649 "may be lost)")); 650 } 651 652 return svn_error_trace(err); 653} 654 655 656svn_error_t * 657svn_cl__error_checked_fputs(const char *string, FILE* stream) 658{ 659 /* On POSIX systems, errno will be set on an error in fputs, but this might 660 not be the case on other platforms. We reset errno and only 661 use it if it was set by the below fputs call. Else, we just return 662 a generic error. */ 663 errno = 0; 664 665 if (fputs(string, stream) == EOF) 666 { 667 if (errno) 668 return svn_error_wrap_apr(errno, _("Write error")); 669 else 670 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 671 } 672 673 return SVN_NO_ERROR; 674} 675 676 677svn_error_t * 678svn_cl__try(svn_error_t *err, 679 apr_array_header_t *errors_seen, 680 svn_boolean_t quiet, 681 ...) 682{ 683 if (err) 684 { 685 apr_status_t apr_err; 686 va_list ap; 687 688 va_start(ap, quiet); 689 while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS) 690 { 691 if (errors_seen) 692 { 693 int i; 694 svn_boolean_t add = TRUE; 695 696 /* Don't report duplicate error codes. */ 697 for (i = 0; i < errors_seen->nelts; i++) 698 { 699 if (APR_ARRAY_IDX(errors_seen, i, 700 apr_status_t) == err->apr_err) 701 { 702 add = FALSE; 703 break; 704 } 705 } 706 if (add) 707 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err; 708 } 709 if (err->apr_err == apr_err) 710 { 711 if (! quiet) 712 svn_handle_warning2(stderr, err, "svn: "); 713 svn_error_clear(err); 714 return SVN_NO_ERROR; 715 } 716 } 717 va_end(ap); 718 } 719 720 return svn_error_trace(err); 721} 722 723 724void 725svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb, 726 apr_pool_t *pool, 727 const char *tagname, 728 const char *string) 729{ 730 if (string) 731 { 732 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata, 733 tagname, NULL); 734 svn_xml_escape_cdata_cstring(sb, string, pool); 735 svn_xml_make_close_tag(sb, pool, tagname); 736 } 737} 738 739 740void 741svn_cl__print_xml_commit(svn_stringbuf_t **sb, 742 svn_revnum_t revision, 743 const char *author, 744 const char *date, 745 apr_pool_t *pool) 746{ 747 /* "<commit ...>" */ 748 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit", 749 "revision", 750 apr_psprintf(pool, "%ld", revision), NULL); 751 752 /* "<author>xx</author>" */ 753 if (author) 754 svn_cl__xml_tagged_cdata(sb, pool, "author", author); 755 756 /* "<date>xx</date>" */ 757 if (date) 758 svn_cl__xml_tagged_cdata(sb, pool, "date", date); 759 760 /* "</commit>" */ 761 svn_xml_make_close_tag(sb, pool, "commit"); 762} 763 764 765void 766svn_cl__print_xml_lock(svn_stringbuf_t **sb, 767 const svn_lock_t *lock, 768 apr_pool_t *pool) 769{ 770 /* "<lock>" */ 771 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL); 772 773 /* "<token>xx</token>" */ 774 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token); 775 776 /* "<owner>xx</owner>" */ 777 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner); 778 779 /* "<comment>xx</comment>" */ 780 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment); 781 782 /* "<created>xx</created>" */ 783 svn_cl__xml_tagged_cdata(sb, pool, "created", 784 svn_time_to_cstring(lock->creation_date, pool)); 785 786 /* "<expires>xx</expires>" */ 787 if (lock->expiration_date != 0) 788 svn_cl__xml_tagged_cdata(sb, pool, "expires", 789 svn_time_to_cstring(lock->expiration_date, pool)); 790 791 /* "</lock>" */ 792 svn_xml_make_close_tag(sb, pool, "lock"); 793} 794 795 796svn_error_t * 797svn_cl__xml_print_header(const char *tagname, 798 apr_pool_t *pool) 799{ 800 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 801 802 /* <?xml version="1.0" encoding="UTF-8"?> */ 803 svn_xml_make_header2(&sb, "UTF-8", pool); 804 805 /* "<TAGNAME>" */ 806 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL); 807 808 return svn_cl__error_checked_fputs(sb->data, stdout); 809} 810 811 812svn_error_t * 813svn_cl__xml_print_footer(const char *tagname, 814 apr_pool_t *pool) 815{ 816 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 817 818 /* "</TAGNAME>" */ 819 svn_xml_make_close_tag(&sb, pool, tagname); 820 return svn_cl__error_checked_fputs(sb->data, stdout); 821} 822 823 824/* A map for svn_node_kind_t values to XML strings */ 825static const svn_token_map_t map_node_kind_xml[] = 826{ 827 { "none", svn_node_none }, 828 { "file", svn_node_file }, 829 { "dir", svn_node_dir }, 830 { "", svn_node_unknown }, 831 { NULL, 0 } 832}; 833 834/* A map for svn_node_kind_t values to human-readable strings */ 835static const svn_token_map_t map_node_kind_human[] = 836{ 837 { N_("none"), svn_node_none }, 838 { N_("file"), svn_node_file }, 839 { N_("dir"), svn_node_dir }, 840 { "", svn_node_unknown }, 841 { NULL, 0 } 842}; 843 844const char * 845svn_cl__node_kind_str_xml(svn_node_kind_t kind) 846{ 847 return svn_token__to_word(map_node_kind_xml, kind); 848} 849 850const char * 851svn_cl__node_kind_str_human_readable(svn_node_kind_t kind) 852{ 853 return _(svn_token__to_word(map_node_kind_human, kind)); 854} 855 856 857/* A map for svn_wc_operation_t values to XML strings */ 858static const svn_token_map_t map_wc_operation_xml[] = 859{ 860 { "none", svn_wc_operation_none }, 861 { "update", svn_wc_operation_update }, 862 { "switch", svn_wc_operation_switch }, 863 { "merge", svn_wc_operation_merge }, 864 { NULL, 0 } 865}; 866 867/* A map for svn_wc_operation_t values to human-readable strings */ 868static const svn_token_map_t map_wc_operation_human[] = 869{ 870 { N_("none"), svn_wc_operation_none }, 871 { N_("update"), svn_wc_operation_update }, 872 { N_("switch"), svn_wc_operation_switch }, 873 { N_("merge"), svn_wc_operation_merge }, 874 { NULL, 0 } 875}; 876 877const char * 878svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool) 879{ 880 return svn_token__to_word(map_wc_operation_xml, operation); 881} 882 883const char * 884svn_cl__operation_str_human_readable(svn_wc_operation_t operation, 885 apr_pool_t *pool) 886{ 887 return _(svn_token__to_word(map_wc_operation_human, operation)); 888} 889 890 891svn_error_t * 892svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets, 893 apr_getopt_t *os, 894 const apr_array_header_t *known_targets, 895 svn_client_ctx_t *ctx, 896 svn_boolean_t keep_last_origpath_on_truepath_collision, 897 apr_pool_t *pool) 898{ 899 svn_error_t *err = svn_client_args_to_target_array2(targets, 900 os, 901 known_targets, 902 ctx, 903 keep_last_origpath_on_truepath_collision, 904 pool); 905 if (err) 906 { 907 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED) 908 { 909 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: "); 910 svn_error_clear(err); 911 } 912 else 913 return svn_error_trace(err); 914 } 915 return SVN_NO_ERROR; 916} 917 918 919/* Helper for svn_cl__get_changelist(); implements 920 svn_changelist_receiver_t. */ 921static svn_error_t * 922changelist_receiver(void *baton, 923 const char *path, 924 const char *changelist, 925 apr_pool_t *pool) 926{ 927 /* No need to check CHANGELIST; our caller only asked about one of them. */ 928 apr_array_header_t *paths = baton; 929 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path); 930 return SVN_NO_ERROR; 931} 932 933 934svn_error_t * 935svn_cl__changelist_paths(apr_array_header_t **paths, 936 const apr_array_header_t *changelists, 937 const apr_array_header_t *targets, 938 svn_depth_t depth, 939 svn_client_ctx_t *ctx, 940 apr_pool_t *result_pool, 941 apr_pool_t *scratch_pool) 942{ 943 apr_array_header_t *found; 944 apr_hash_t *paths_hash; 945 apr_pool_t *iterpool; 946 int i; 947 948 if (! (changelists && changelists->nelts)) 949 { 950 *paths = (apr_array_header_t *)targets; 951 return SVN_NO_ERROR; 952 } 953 954 found = apr_array_make(scratch_pool, 8, sizeof(const char *)); 955 iterpool = svn_pool_create(scratch_pool); 956 for (i = 0; i < targets->nelts; i++) 957 { 958 const char *target = APR_ARRAY_IDX(targets, i, const char *); 959 svn_pool_clear(iterpool); 960 SVN_ERR(svn_client_get_changelists(target, changelists, depth, 961 changelist_receiver, found, 962 ctx, iterpool)); 963 } 964 svn_pool_destroy(iterpool); 965 966 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool)); 967 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool)); 968} 969 970svn_cl__show_revs_t 971svn_cl__show_revs_from_word(const char *word) 972{ 973 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0) 974 return svn_cl__show_revs_merged; 975 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0) 976 return svn_cl__show_revs_eligible; 977 /* word is an invalid flavor. */ 978 return svn_cl__show_revs_invalid; 979} 980 981 982svn_error_t * 983svn_cl__time_cstring_to_human_cstring(const char **human_cstring, 984 const char *data, 985 apr_pool_t *pool) 986{ 987 svn_error_t *err; 988 apr_time_t when; 989 990 err = svn_time_from_cstring(&when, data, pool); 991 if (err && err->apr_err == SVN_ERR_BAD_DATE) 992 { 993 svn_error_clear(err); 994 995 *human_cstring = _("(invalid date)"); 996 return SVN_NO_ERROR; 997 } 998 else if (err) 999 return svn_error_trace(err); 1000 1001 *human_cstring = svn_time_to_human_cstring(when, pool); 1002 1003 return SVN_NO_ERROR; 1004} 1005 1006const char * 1007svn_cl__node_description(const svn_wc_conflict_version_t *node, 1008 const char *wc_repos_root_URL, 1009 apr_pool_t *pool) 1010{ 1011 const char *root_str = "^"; 1012 const char *path_str = "..."; 1013 1014 if (!node) 1015 /* Printing "(none)" the harder way to ensure conformity (mostly with 1016 * translations). */ 1017 return apr_psprintf(pool, "(%s)", 1018 svn_cl__node_kind_str_human_readable(svn_node_none)); 1019 1020 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL. 1021 * Otherwise show the complete URL, and if we can't, show dots. */ 1022 1023 if (node->repos_url && 1024 (wc_repos_root_URL == NULL || 1025 strcmp(node->repos_url, wc_repos_root_URL) != 0)) 1026 root_str = node->repos_url; 1027 1028 if (node->path_in_repos) 1029 path_str = node->path_in_repos; 1030 1031 return apr_psprintf(pool, "(%s) %s@%ld", 1032 svn_cl__node_kind_str_human_readable(node->node_kind), 1033 svn_path_url_add_component2(root_str, path_str, pool), 1034 node->peg_rev); 1035} 1036 1037svn_error_t * 1038svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p, 1039 const apr_array_header_t *targets, 1040 apr_pool_t *pool) 1041{ 1042 int i; 1043 apr_array_header_t *true_targets; 1044 1045 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *)); 1046 1047 for (i = 0; i < targets->nelts; i++) 1048 { 1049 const char *target = APR_ARRAY_IDX(targets, i, const char *); 1050 const char *true_target, *peg; 1051 1052 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg, 1053 target, pool)); 1054 if (peg[0] && peg[1]) 1055 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1056 _("'%s': a peg revision is not allowed here"), 1057 target); 1058 APR_ARRAY_PUSH(true_targets, const char *) = true_target; 1059 } 1060 1061 SVN_ERR_ASSERT(true_targets_p); 1062 *true_targets_p = true_targets; 1063 1064 return SVN_NO_ERROR; 1065} 1066 1067svn_error_t * 1068svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets) 1069{ 1070 svn_error_t *err; 1071 1072 err = svn_client__assert_homogeneous_target_type(targets); 1073 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) 1074 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL); 1075 return err; 1076} 1077 1078svn_error_t * 1079svn_cl__check_target_is_local_path(const char *target) 1080{ 1081 if (svn_path_is_url(target)) 1082 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1083 _("'%s' is not a local path"), target); 1084 return SVN_NO_ERROR; 1085} 1086 1087svn_error_t * 1088svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets) 1089{ 1090 int i; 1091 1092 for (i = 0; i < targets->nelts; i++) 1093 { 1094 const char *target = APR_ARRAY_IDX(targets, i, const char *); 1095 1096 SVN_ERR(svn_cl__check_target_is_local_path(target)); 1097 } 1098 return SVN_NO_ERROR; 1099} 1100 1101const char * 1102svn_cl__local_style_skip_ancestor(const char *parent_path, 1103 const char *path, 1104 apr_pool_t *pool) 1105{ 1106 const char *relpath = NULL; 1107 1108 if (parent_path) 1109 relpath = svn_dirent_skip_ancestor(parent_path, path); 1110 1111 return svn_dirent_local_style(relpath ? relpath : path, pool); 1112} 1113 1114svn_error_t * 1115svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets, 1116 const char *propname, 1117 const svn_string_t *propval, 1118 apr_pool_t *scratch_pool) 1119{ 1120 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0) 1121 { 1122 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1123 int i; 1124 1125 for (i = 0; i < targets->nelts; i++) 1126 { 1127 const char *detected_mimetype; 1128 const char *target = APR_ARRAY_IDX(targets, i, const char *); 1129 const char *local_abspath; 1130 const svn_string_t *canon_propval; 1131 svn_node_kind_t node_kind; 1132 1133 svn_pool_clear(iterpool); 1134 1135 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); 1136 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool)); 1137 if (node_kind != svn_node_file) 1138 continue; 1139 1140 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval, 1141 propname, propval, 1142 local_abspath, 1143 svn_node_file, 1144 FALSE, NULL, NULL, 1145 iterpool)); 1146 1147 if (svn_mime_type_is_binary(canon_propval->data)) 1148 { 1149 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype, 1150 local_abspath, NULL, 1151 iterpool)); 1152 if (detected_mimetype == NULL || 1153 !svn_mime_type_is_binary(detected_mimetype)) 1154 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool, 1155 _("svn: warning: '%s' is a binary mime-type but file '%s' " 1156 "looks like text; diff, merge, blame, and other " 1157 "operations will stop working on this file\n"), 1158 canon_propval->data, 1159 svn_dirent_local_style(local_abspath, iterpool))); 1160 1161 } 1162 } 1163 svn_pool_destroy(iterpool); 1164 } 1165 1166 return SVN_NO_ERROR; 1167} 1168 1169