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