1251881Speter/* 2251881Speter * util.c: Subversion command line client utility functions. Any 3251881Speter * functions that need to be shared across subcommands should be put 4251881Speter * in here. 5251881Speter * 6251881Speter * ==================================================================== 7251881Speter * Licensed to the Apache Software Foundation (ASF) under one 8251881Speter * or more contributor license agreements. See the NOTICE file 9251881Speter * distributed with this work for additional information 10251881Speter * regarding copyright ownership. The ASF licenses this file 11251881Speter * to you under the Apache License, Version 2.0 (the 12251881Speter * "License"); you may not use this file except in compliance 13251881Speter * with the License. You may obtain a copy of the License at 14251881Speter * 15251881Speter * http://www.apache.org/licenses/LICENSE-2.0 16251881Speter * 17251881Speter * Unless required by applicable law or agreed to in writing, 18251881Speter * software distributed under the License is distributed on an 19251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20251881Speter * KIND, either express or implied. See the License for the 21251881Speter * specific language governing permissions and limitations 22251881Speter * under the License. 23251881Speter * ==================================================================== 24251881Speter */ 25251881Speter 26251881Speter/* ==================================================================== */ 27251881Speter 28251881Speter 29251881Speter 30251881Speter/*** Includes. ***/ 31251881Speter 32251881Speter#include <string.h> 33251881Speter#include <ctype.h> 34251881Speter#include <assert.h> 35251881Speter 36251881Speter#include <apr_env.h> 37251881Speter#include <apr_errno.h> 38251881Speter#include <apr_file_info.h> 39251881Speter#include <apr_strings.h> 40251881Speter#include <apr_tables.h> 41251881Speter#include <apr_general.h> 42251881Speter#include <apr_lib.h> 43251881Speter 44251881Speter#include "svn_pools.h" 45251881Speter#include "svn_error.h" 46251881Speter#include "svn_ctype.h" 47251881Speter#include "svn_client.h" 48251881Speter#include "svn_cmdline.h" 49251881Speter#include "svn_string.h" 50251881Speter#include "svn_dirent_uri.h" 51251881Speter#include "svn_path.h" 52251881Speter#include "svn_hash.h" 53251881Speter#include "svn_io.h" 54251881Speter#include "svn_utf.h" 55251881Speter#include "svn_subst.h" 56251881Speter#include "svn_config.h" 57251881Speter#include "svn_wc.h" 58251881Speter#include "svn_xml.h" 59251881Speter#include "svn_time.h" 60251881Speter#include "svn_props.h" 61251881Speter#include "svn_private_config.h" 62251881Speter#include "cl.h" 63251881Speter 64251881Speter#include "private/svn_token.h" 65251881Speter#include "private/svn_opt_private.h" 66251881Speter#include "private/svn_client_private.h" 67251881Speter#include "private/svn_cmdline_private.h" 68251881Speter#include "private/svn_string_private.h" 69251896Speter#ifdef HAS_ORGANIZATION_NAME 70251896Speter#include "freebsd-organization.h" 71251896Speter#endif 72251881Speter 73251881Speter 74251881Speter 75251881Speter 76251881Spetersvn_error_t * 77251881Spetersvn_cl__print_commit_info(const svn_commit_info_t *commit_info, 78251881Speter void *baton, 79251881Speter apr_pool_t *pool) 80251881Speter{ 81299742Sdim /* Be very careful with returning errors from this callback as those 82299742Sdim will be returned as errors from editor->close_edit(...), which may 83299742Sdim cause callers to assume that the commit itself failed. 84299742Sdim 85299742Sdim See log message of r1659867 and the svn_ra_get_commit_editor3 86299742Sdim documentation for details on error scenarios. */ 87299742Sdim 88251881Speter if (SVN_IS_VALID_REVNUM(commit_info->revision)) 89299742Sdim SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld%s.\n"), 90251881Speter commit_info->revision, 91251881Speter commit_info->revision == 42 && 92251881Speter getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS") 93251881Speter ? _(" (the answer to life, the universe, " 94251881Speter "and everything)") 95251881Speter : "")); 96251881Speter 97251881Speter /* Writing to stdout, as there maybe systems that consider the 98251881Speter * presence of stderr as an indication of commit failure. 99251881Speter * OTOH, this is only of informational nature to the user as 100251881Speter * the commit has succeeded. */ 101251881Speter if (commit_info->post_commit_err) 102251881Speter SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"), 103251881Speter commit_info->post_commit_err)); 104251881Speter 105251881Speter return SVN_NO_ERROR; 106251881Speter} 107251881Speter 108251881Speter 109251881Spetersvn_error_t * 110251881Spetersvn_cl__merge_file_externally(const char *base_path, 111251881Speter const char *their_path, 112251881Speter const char *my_path, 113251881Speter const char *merged_path, 114251881Speter const char *wc_path, 115251881Speter apr_hash_t *config, 116251881Speter svn_boolean_t *remains_in_conflict, 117251881Speter apr_pool_t *pool) 118251881Speter{ 119251881Speter char *merge_tool; 120251881Speter /* Error if there is no editor specified */ 121251881Speter if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS) 122251881Speter { 123251881Speter struct svn_config_t *cfg; 124251881Speter merge_tool = NULL; 125251881Speter cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 126251881Speter /* apr_env_get wants char **, this wants const char ** */ 127251881Speter svn_config_get(cfg, (const char **)&merge_tool, 128251881Speter SVN_CONFIG_SECTION_HELPERS, 129251881Speter SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL); 130251881Speter } 131251881Speter 132251881Speter if (merge_tool) 133251881Speter { 134251881Speter const char *c; 135251881Speter 136251881Speter for (c = merge_tool; *c; c++) 137251881Speter if (!svn_ctype_isspace(*c)) 138251881Speter break; 139251881Speter 140251881Speter if (! *c) 141251881Speter return svn_error_create 142251881Speter (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL, 143251881Speter _("The SVN_MERGE environment variable is empty or " 144251881Speter "consists solely of whitespace. Expected a shell command.\n")); 145251881Speter } 146251881Speter else 147251881Speter return svn_error_create 148251881Speter (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL, 149251881Speter _("The environment variable SVN_MERGE and the merge-tool-cmd run-time " 150251881Speter "configuration option were not set.\n")); 151251881Speter 152251881Speter { 153251881Speter const char *arguments[7] = { 0 }; 154251881Speter char *cwd; 155251881Speter int exitcode; 156251881Speter 157251881Speter apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool); 158251881Speter if (status != 0) 159251881Speter return svn_error_wrap_apr(status, NULL); 160251881Speter 161251881Speter arguments[0] = merge_tool; 162251881Speter arguments[1] = base_path; 163251881Speter arguments[2] = their_path; 164251881Speter arguments[3] = my_path; 165251881Speter arguments[4] = merged_path; 166251881Speter arguments[5] = wc_path; 167251881Speter arguments[6] = NULL; 168251881Speter 169251881Speter SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool, 170251881Speter arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL, 171251881Speter pool)); 172251881Speter /* Exit code 0 means the merge was successful. 173251881Speter * Exit code 1 means the file was left in conflict but it 174251881Speter * is OK to continue with the merge. 175251881Speter * Any other exit code means there was a real problem. */ 176251881Speter if (exitcode != 0 && exitcode != 1) 177299742Sdim return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL, 178299742Sdim _("The external merge tool '%s' exited with exit code %d."), 179299742Sdim merge_tool, exitcode); 180251881Speter else if (remains_in_conflict) 181251881Speter *remains_in_conflict = exitcode == 1; 182251881Speter } 183251881Speter return SVN_NO_ERROR; 184251881Speter} 185251881Speter 186251881Speter 187251881Speter/* A svn_client_ctx_t's log_msg_baton3, for use with 188251881Speter svn_cl__make_log_msg_baton(). */ 189251881Speterstruct log_msg_baton 190251881Speter{ 191251881Speter const char *editor_cmd; /* editor specified via --editor-cmd, else NULL */ 192251881Speter const char *message; /* the message. */ 193251881Speter const char *message_encoding; /* the locale/encoding of the message. */ 194251881Speter const char *base_dir; /* the base directory for an external edit. UTF-8! */ 195251881Speter const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */ 196251881Speter svn_boolean_t non_interactive; /* if true, don't pop up an editor */ 197251881Speter apr_hash_t *config; /* client configuration hash */ 198251881Speter svn_boolean_t keep_locks; /* Keep repository locks? */ 199251881Speter apr_pool_t *pool; /* a pool. */ 200251881Speter}; 201251881Speter 202251881Speter 203251881Spetersvn_error_t * 204251881Spetersvn_cl__make_log_msg_baton(void **baton, 205251881Speter svn_cl__opt_state_t *opt_state, 206251881Speter const char *base_dir /* UTF-8! */, 207251881Speter apr_hash_t *config, 208251881Speter apr_pool_t *pool) 209251881Speter{ 210299742Sdim struct log_msg_baton *lmb = apr_pcalloc(pool, sizeof(*lmb)); 211251881Speter 212251881Speter if (opt_state->filedata) 213251881Speter { 214251881Speter if (strlen(opt_state->filedata->data) < opt_state->filedata->len) 215251881Speter { 216251881Speter /* The data contains a zero byte, and therefore can't be 217251881Speter represented as a C string. Punt now; it's probably not 218251881Speter a deliberate encoding, and even if it is, we still 219251881Speter can't handle it. */ 220251881Speter return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL, 221251881Speter _("Log message contains a zero byte")); 222251881Speter } 223251881Speter lmb->message = opt_state->filedata->data; 224251881Speter } 225251881Speter else 226251881Speter { 227251881Speter lmb->message = opt_state->message; 228251881Speter } 229251881Speter 230251881Speter lmb->editor_cmd = opt_state->editor_cmd; 231251881Speter if (opt_state->encoding) 232251881Speter { 233251881Speter lmb->message_encoding = opt_state->encoding; 234251881Speter } 235251881Speter else if (config) 236251881Speter { 237251881Speter svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); 238251881Speter svn_config_get(cfg, &(lmb->message_encoding), 239251881Speter SVN_CONFIG_SECTION_MISCELLANY, 240251881Speter SVN_CONFIG_OPTION_LOG_ENCODING, 241251881Speter NULL); 242251881Speter } 243299742Sdim else 244299742Sdim lmb->message_encoding = NULL; 245251881Speter 246299742Sdim lmb->base_dir = base_dir; 247251881Speter lmb->tmpfile_left = NULL; 248251881Speter lmb->config = config; 249251881Speter lmb->keep_locks = opt_state->no_unlock; 250251881Speter lmb->non_interactive = opt_state->non_interactive; 251251881Speter lmb->pool = pool; 252251881Speter *baton = lmb; 253251881Speter return SVN_NO_ERROR; 254251881Speter} 255251881Speter 256251881Speter 257251881Spetersvn_error_t * 258251881Spetersvn_cl__cleanup_log_msg(void *log_msg_baton, 259251881Speter svn_error_t *commit_err, 260251881Speter apr_pool_t *pool) 261251881Speter{ 262251881Speter struct log_msg_baton *lmb = log_msg_baton; 263251881Speter svn_error_t *err; 264251881Speter 265251881Speter /* If there was no tmpfile left, or there is no log message baton, 266251881Speter return COMMIT_ERR. */ 267251881Speter if ((! lmb) || (! lmb->tmpfile_left)) 268251881Speter return commit_err; 269251881Speter 270251881Speter /* If there was no commit error, cleanup the tmpfile and return. */ 271251881Speter if (! commit_err) 272251881Speter return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool); 273251881Speter 274251881Speter /* There was a commit error; there is a tmpfile. Leave the tmpfile 275251881Speter around, and add message about its presence to the commit error 276251881Speter chain. Then return COMMIT_ERR. If the conversion from UTF-8 to 277251881Speter native encoding fails, we have to compose that error with the 278251881Speter commit error chain, too. */ 279251881Speter 280251881Speter err = svn_error_createf(commit_err->apr_err, NULL, 281251881Speter _(" '%s'"), 282251881Speter svn_dirent_local_style(lmb->tmpfile_left, pool)); 283251881Speter svn_error_compose(commit_err, 284251881Speter svn_error_create(commit_err->apr_err, err, 285251881Speter _("Your commit message was left in " 286251881Speter "a temporary file:"))); 287251881Speter return commit_err; 288251881Speter} 289251881Speter 290251881Speter 291251881Speter/* Remove line-starting PREFIX and everything after it from BUFFER. 292251881Speter If NEW_LEN is non-NULL, return the new length of BUFFER in 293251881Speter *NEW_LEN. */ 294251881Speterstatic void 295251881Spetertruncate_buffer_at_prefix(apr_size_t *new_len, 296251881Speter char *buffer, 297251881Speter const char *prefix) 298251881Speter{ 299251881Speter char *substring = buffer; 300251881Speter 301251881Speter assert(buffer && prefix); 302251881Speter 303251881Speter /* Initialize *NEW_LEN. */ 304251881Speter if (new_len) 305251881Speter *new_len = strlen(buffer); 306251881Speter 307251881Speter while (1) 308251881Speter { 309251881Speter /* Find PREFIX in BUFFER. */ 310251881Speter substring = strstr(substring, prefix); 311251881Speter if (! substring) 312251881Speter return; 313251881Speter 314251881Speter /* We found PREFIX. Is it really a PREFIX? Well, if it's the first 315251881Speter thing in the file, or if the character before it is a 316251881Speter line-terminator character, it sure is. */ 317251881Speter if ((substring == buffer) 318251881Speter || (*(substring - 1) == '\r') 319251881Speter || (*(substring - 1) == '\n')) 320251881Speter { 321251881Speter *substring = '\0'; 322251881Speter if (new_len) 323251881Speter *new_len = substring - buffer; 324251881Speter } 325251881Speter else if (substring) 326251881Speter { 327251881Speter /* Well, it wasn't really a prefix, so just advance by 1 328251881Speter character and continue. */ 329251881Speter substring++; 330251881Speter } 331251881Speter } 332251881Speter 333251881Speter /* NOTREACHED */ 334251881Speter} 335251881Speter 336251881Speter 337251896Speter/* 338251896Speter * Since we're adding freebsd-specific tokens to the log message, 339251896Speter * clean out any leftovers to avoid accidently sending them to other 340251896Speter * projects that won't be expecting them. 341251896Speter */ 342251896Speter 343251896Speterstatic const char *prefixes[] = { 344251896Speter "PR:", 345251896Speter "Submitted by:", 346299742Sdim "Reported by:", 347251896Speter "Reviewed by:", 348251896Speter "Approved by:", 349251896Speter "Obtained from:", 350251896Speter "MFC after:", 351299742Sdim "MFH:", 352263655Sgjb "Relnotes:", 353251896Speter "Security:", 354289166Speter "Sponsored by:", 355289166Speter "Differential Revision:", 356251896Speter}; 357251896Speter 358251896Spetervoid 359251896Spetercleanmsg(apr_size_t *l, char *s) 360251896Speter{ 361251896Speter int i; 362251896Speter char *pos; 363251896Speter char *kw; 364251896Speter char *p; 365251896Speter int empty; 366251896Speter 367251896Speter for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) { 368251896Speter pos = s; 369251896Speter while ((kw = strstr(pos, prefixes[i])) != NULL) { 370251896Speter /* Check to see if keyword is at start of line (or buffer) */ 371251896Speter if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) { 372251896Speter pos = kw + 1; 373251896Speter continue; 374251896Speter } 375251896Speter p = kw + strlen(prefixes[i]); 376251896Speter empty = 1; 377251896Speter while (1) { 378251896Speter if (*p == ' ' || *p == '\t') { 379251896Speter p++; 380251896Speter continue; 381251896Speter } 382251896Speter if (*p == '\0' || *p == '\r' || *p == '\n') 383251896Speter break; 384251896Speter empty = 0; 385251896Speter break; 386251896Speter } 387251896Speter if (empty && (*p == '\r' || *p == '\n')) { 388251896Speter memmove(kw, p + 1, strlen(p + 1) + 1); 389251896Speter if (l) 390251896Speter *l -= (p + 1 - kw); 391251896Speter } else if (empty) { 392251896Speter *kw = '\0'; 393251896Speter if (l) 394251896Speter *l -= (p - kw); 395251896Speter } else { 396251896Speter pos = p; 397251896Speter } 398251896Speter } 399251896Speter } 400251896Speter} 401251896Speter 402251881Speter#define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--") 403251881Speter 404251881Spetersvn_error_t * 405251881Spetersvn_cl__get_log_message(const char **log_msg, 406251881Speter const char **tmp_file, 407251881Speter const apr_array_header_t *commit_items, 408251881Speter void *baton, 409251881Speter apr_pool_t *pool) 410251881Speter{ 411251881Speter svn_stringbuf_t *default_msg = NULL; 412251881Speter struct log_msg_baton *lmb = baton; 413251881Speter svn_stringbuf_t *message = NULL; 414289166Speter svn_config_t *cfg; 415289166Speter const char *mfc_after, *sponsored_by; 416251881Speter 417289166Speter cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; 418289166Speter 419251881Speter /* Set default message. */ 420251881Speter default_msg = svn_stringbuf_create(APR_EOL_STR, pool); 421251896Speter svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 422251896Speter svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR); 423251896Speter svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR); 424299742Sdim svn_stringbuf_appendcstr(default_msg, "Reported by:\t" APR_EOL_STR); 425251896Speter svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR); 426251896Speter svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR); 427251896Speter svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR); 428289166Speter svn_stringbuf_appendcstr(default_msg, "MFC after:\t"); 429289166Speter svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL); 430289166Speter if (mfc_after != NULL) 431289166Speter svn_stringbuf_appendcstr(default_msg, mfc_after); 432289166Speter svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 433299742Sdim svn_stringbuf_appendcstr(default_msg, "MFH:\t\t" APR_EOL_STR); 434263655Sgjb svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR); 435251896Speter svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR); 436289166Speter svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t"); 437289166Speter svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by", 438251896Speter#ifdef HAS_ORGANIZATION_NAME 439289166Speter ORGANIZATION_NAME); 440289166Speter#else 441289166Speter NULL); 442251896Speter#endif 443289166Speter if (sponsored_by != NULL) 444289166Speter svn_stringbuf_appendcstr(default_msg, sponsored_by); 445289166Speter svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 446289166Speter svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR); 447251881Speter svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX); 448251896Speter svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 449251896Speter svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR); 450299742Sdim svn_stringbuf_appendcstr(default_msg, "> PR: If and which Problem Report is related." APR_EOL_STR); 451289166Speter svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR); 452299742Sdim svn_stringbuf_appendcstr(default_msg, "> Reported by: If someone else reported the issue." APR_EOL_STR); 453289166Speter svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR); 454289166Speter svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR); 455289166Speter svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR); 456289166Speter svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR); 457289166Speter svn_stringbuf_appendcstr(default_msg, "> MFH: Ports tree branch name. Request approval for merge." APR_EOL_STR); 458289166Speter svn_stringbuf_appendcstr(default_msg, "> Relnotes: Set to 'yes' for mention in release notes." APR_EOL_STR); 459289166Speter svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR); 460289166Speter svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR); 461289166Speter svn_stringbuf_appendcstr(default_msg, "> Differential Revision: https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR); 462251896Speter svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR); 463251896Speter svn_stringbuf_appendcstr(default_msg, APR_EOL_STR); 464251881Speter 465251881Speter *tmp_file = NULL; 466251881Speter if (lmb->message) 467251881Speter { 468299742Sdim svn_string_t *log_msg_str = svn_string_create(lmb->message, pool); 469251881Speter 470299742Sdim SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, NULL, NULL, 471251881Speter log_msg_str, lmb->message_encoding, 472251881Speter FALSE, pool, pool), 473251881Speter _("Error normalizing log message to internal format")); 474251881Speter 475299742Sdim /* Strip off the EOF marker text and the junk that follows it. */ 476299742Sdim truncate_buffer_at_prefix(&(log_msg_str->len), (char *)log_msg_str->data, 477299742Sdim EDITOR_EOF_PREFIX); 478299742Sdim 479299742Sdim cleanmsg(&(log_msg_str->len), (char*)log_msg_str->data); 480299742Sdim 481251881Speter *log_msg = log_msg_str->data; 482251881Speter return SVN_NO_ERROR; 483251881Speter } 484251881Speter 485251881Speter if (! commit_items->nelts) 486251881Speter { 487251881Speter *log_msg = ""; 488251881Speter return SVN_NO_ERROR; 489251881Speter } 490251881Speter 491251881Speter while (! message) 492251881Speter { 493251881Speter /* We still don't have a valid commit message. Use $EDITOR to 494251881Speter get one. Note that svn_cl__edit_string_externally will still 495251881Speter return a UTF-8'ized log message. */ 496251881Speter int i; 497251881Speter svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool); 498251881Speter svn_error_t *err = SVN_NO_ERROR; 499251881Speter svn_string_t *msg_string = svn_string_create_empty(pool); 500251881Speter 501251881Speter for (i = 0; i < commit_items->nelts; i++) 502251881Speter { 503251881Speter svn_client_commit_item3_t *item 504251881Speter = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); 505251881Speter const char *path = item->path; 506251881Speter char text_mod = '_', prop_mod = ' ', unlock = ' '; 507251881Speter 508251881Speter if (! path) 509251881Speter path = item->url; 510299742Sdim else if (lmb->base_dir) 511251881Speter path = svn_dirent_is_child(lmb->base_dir, path, pool); 512251881Speter 513251881Speter /* If still no path, then just use current directory. */ 514299742Sdim if (! path || !*path) 515251881Speter path = "."; 516251881Speter 517251881Speter if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 518251881Speter && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) 519251881Speter text_mod = 'R'; 520251881Speter else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 521251881Speter text_mod = 'A'; 522251881Speter else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) 523251881Speter text_mod = 'D'; 524251881Speter else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) 525251881Speter text_mod = 'M'; 526251881Speter 527251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) 528251881Speter prop_mod = 'M'; 529251881Speter 530251881Speter if (! lmb->keep_locks 531251881Speter && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN) 532251881Speter unlock = 'U'; 533251881Speter 534251881Speter svn_stringbuf_appendbyte(tmp_message, text_mod); 535251881Speter svn_stringbuf_appendbyte(tmp_message, prop_mod); 536251881Speter svn_stringbuf_appendbyte(tmp_message, unlock); 537251881Speter if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) 538251881Speter /* History included via copy/move. */ 539251881Speter svn_stringbuf_appendcstr(tmp_message, "+ "); 540251881Speter else 541251881Speter svn_stringbuf_appendcstr(tmp_message, " "); 542251881Speter svn_stringbuf_appendcstr(tmp_message, path); 543251881Speter svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR); 544251881Speter } 545251881Speter 546251881Speter msg_string->data = tmp_message->data; 547251881Speter msg_string->len = tmp_message->len; 548251881Speter 549251881Speter /* Use the external edit to get a log message. */ 550251881Speter if (! lmb->non_interactive) 551251881Speter { 552251881Speter err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left, 553299742Sdim lmb->editor_cmd, 554299742Sdim lmb->base_dir ? lmb->base_dir : "", 555251881Speter msg_string, "svn-commit", 556251881Speter lmb->config, TRUE, 557251881Speter lmb->message_encoding, 558251881Speter pool); 559251881Speter } 560251881Speter else /* non_interactive flag says we can't pop up an editor, so error */ 561251881Speter { 562251881Speter return svn_error_create 563251881Speter (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 564251881Speter _("Cannot invoke editor to get log message " 565251881Speter "when non-interactive")); 566251881Speter } 567251881Speter 568251881Speter /* Dup the tmpfile path into its baton's pool. */ 569251881Speter *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool, 570251881Speter lmb->tmpfile_left); 571251881Speter 572251881Speter /* If the edit returned an error, handle it. */ 573251881Speter if (err) 574251881Speter { 575251881Speter if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR) 576251881Speter err = svn_error_quick_wrap 577251881Speter (err, _("Could not use external editor to fetch log message; " 578251881Speter "consider setting the $SVN_EDITOR environment variable " 579251881Speter "or using the --message (-m) or --file (-F) options")); 580251881Speter return svn_error_trace(err); 581251881Speter } 582251881Speter 583251881Speter if (msg_string) 584251881Speter message = svn_stringbuf_create_from_string(msg_string, pool); 585251881Speter 586299742Sdim /* Strip off the EOF marker text and the junk that follows it. */ 587251881Speter if (message) 588251881Speter truncate_buffer_at_prefix(&message->len, message->data, 589251881Speter EDITOR_EOF_PREFIX); 590251896Speter /* 591251896Speter * Since we're adding freebsd-specific tokens to the log message, 592251896Speter * clean out any leftovers to avoid accidently sending them to other 593251896Speter * projects that won't be expecting them. 594251896Speter */ 595251896Speter if (message) 596251896Speter cleanmsg(&message->len, message->data); 597251881Speter 598251881Speter if (message) 599251881Speter { 600251881Speter /* We did get message, now check if it is anything more than just 601251881Speter white space as we will consider white space only as empty */ 602251881Speter apr_size_t len; 603251881Speter 604251881Speter for (len = 0; len < message->len; len++) 605251881Speter { 606251881Speter /* FIXME: should really use an UTF-8 whitespace test 607251881Speter rather than svn_ctype_isspace, which is ASCII only */ 608251881Speter if (! svn_ctype_isspace(message->data[len])) 609251881Speter break; 610251881Speter } 611251881Speter if (len == message->len) 612251881Speter message = NULL; 613251881Speter } 614251881Speter 615251881Speter if (! message) 616251881Speter { 617251881Speter const char *reply; 618251881Speter SVN_ERR(svn_cmdline_prompt_user2 619251881Speter (&reply, 620251881Speter _("\nLog message unchanged or not specified\n" 621251881Speter "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool)); 622251881Speter if (reply) 623251881Speter { 624251881Speter int letter = apr_tolower(reply[0]); 625251881Speter 626251881Speter /* If the user chooses to abort, we cleanup the 627251881Speter temporary file and exit the loop with a NULL 628251881Speter message. */ 629251881Speter if ('a' == letter) 630251881Speter { 631251881Speter SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); 632251881Speter *tmp_file = lmb->tmpfile_left = NULL; 633251881Speter break; 634251881Speter } 635251881Speter 636251881Speter /* If the user chooses to continue, we make an empty 637251881Speter message, which will cause us to exit the loop. We 638251881Speter also cleanup the temporary file. */ 639251881Speter if ('c' == letter) 640251881Speter { 641251881Speter SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool)); 642251881Speter *tmp_file = lmb->tmpfile_left = NULL; 643251881Speter message = svn_stringbuf_create_empty(pool); 644251881Speter } 645251881Speter 646251881Speter /* If the user chooses anything else, the loop will 647251881Speter continue on the NULL message. */ 648251881Speter } 649251881Speter } 650251881Speter } 651251881Speter 652251881Speter *log_msg = message ? message->data : NULL; 653251881Speter return SVN_NO_ERROR; 654251881Speter} 655251881Speter 656251881Speter 657251881Speter/* ### The way our error wrapping currently works, the error returned 658251881Speter * from here will look as though it originates in this source file, 659251881Speter * instead of in the caller's source file. This can be a bit 660251881Speter * misleading, until one starts debugging. Ideally, there'd be a way 661251881Speter * to wrap an error while preserving its FILE/LINE info. 662251881Speter */ 663251881Spetersvn_error_t * 664251881Spetersvn_cl__may_need_force(svn_error_t *err) 665251881Speter{ 666251881Speter if (err 667251881Speter && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE || 668251881Speter err->apr_err == SVN_ERR_CLIENT_MODIFIED)) 669251881Speter { 670251881Speter /* Should this svn_error_compose a new error number? Probably not, 671251881Speter the error hasn't changed. */ 672251881Speter err = svn_error_quick_wrap 673251881Speter (err, _("Use --force to override this restriction (local modifications " 674251881Speter "may be lost)")); 675251881Speter } 676251881Speter 677251881Speter return svn_error_trace(err); 678251881Speter} 679251881Speter 680251881Speter 681251881Spetersvn_error_t * 682251881Spetersvn_cl__error_checked_fputs(const char *string, FILE* stream) 683251881Speter{ 684251881Speter /* On POSIX systems, errno will be set on an error in fputs, but this might 685251881Speter not be the case on other platforms. We reset errno and only 686251881Speter use it if it was set by the below fputs call. Else, we just return 687251881Speter a generic error. */ 688251881Speter errno = 0; 689251881Speter 690251881Speter if (fputs(string, stream) == EOF) 691251881Speter { 692299742Sdim if (apr_get_os_error()) /* is errno on POSIX */ 693299742Sdim return svn_error_wrap_apr(apr_get_os_error(), _("Write error")); 694251881Speter else 695251881Speter return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL); 696251881Speter } 697251881Speter 698251881Speter return SVN_NO_ERROR; 699251881Speter} 700251881Speter 701251881Speter 702251881Spetersvn_error_t * 703251881Spetersvn_cl__try(svn_error_t *err, 704251881Speter apr_array_header_t *errors_seen, 705251881Speter svn_boolean_t quiet, 706251881Speter ...) 707251881Speter{ 708251881Speter if (err) 709251881Speter { 710251881Speter apr_status_t apr_err; 711251881Speter va_list ap; 712251881Speter 713251881Speter va_start(ap, quiet); 714251881Speter while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS) 715251881Speter { 716251881Speter if (errors_seen) 717251881Speter { 718251881Speter int i; 719251881Speter svn_boolean_t add = TRUE; 720251881Speter 721251881Speter /* Don't report duplicate error codes. */ 722251881Speter for (i = 0; i < errors_seen->nelts; i++) 723251881Speter { 724251881Speter if (APR_ARRAY_IDX(errors_seen, i, 725251881Speter apr_status_t) == err->apr_err) 726251881Speter { 727251881Speter add = FALSE; 728251881Speter break; 729251881Speter } 730251881Speter } 731251881Speter if (add) 732251881Speter APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err; 733251881Speter } 734251881Speter if (err->apr_err == apr_err) 735251881Speter { 736251881Speter if (! quiet) 737251881Speter svn_handle_warning2(stderr, err, "svn: "); 738251881Speter svn_error_clear(err); 739251881Speter return SVN_NO_ERROR; 740251881Speter } 741251881Speter } 742251881Speter va_end(ap); 743251881Speter } 744251881Speter 745251881Speter return svn_error_trace(err); 746251881Speter} 747251881Speter 748251881Speter 749251881Spetervoid 750251881Spetersvn_cl__xml_tagged_cdata(svn_stringbuf_t **sb, 751251881Speter apr_pool_t *pool, 752251881Speter const char *tagname, 753251881Speter const char *string) 754251881Speter{ 755251881Speter if (string) 756251881Speter { 757251881Speter svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata, 758299742Sdim tagname, SVN_VA_NULL); 759251881Speter svn_xml_escape_cdata_cstring(sb, string, pool); 760251881Speter svn_xml_make_close_tag(sb, pool, tagname); 761251881Speter } 762251881Speter} 763251881Speter 764251881Speter 765251881Spetervoid 766251881Spetersvn_cl__print_xml_commit(svn_stringbuf_t **sb, 767251881Speter svn_revnum_t revision, 768251881Speter const char *author, 769251881Speter const char *date, 770251881Speter apr_pool_t *pool) 771251881Speter{ 772251881Speter /* "<commit ...>" */ 773251881Speter svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit", 774251881Speter "revision", 775299742Sdim apr_psprintf(pool, "%ld", revision), SVN_VA_NULL); 776251881Speter 777251881Speter /* "<author>xx</author>" */ 778251881Speter if (author) 779251881Speter svn_cl__xml_tagged_cdata(sb, pool, "author", author); 780251881Speter 781251881Speter /* "<date>xx</date>" */ 782251881Speter if (date) 783251881Speter svn_cl__xml_tagged_cdata(sb, pool, "date", date); 784251881Speter 785251881Speter /* "</commit>" */ 786251881Speter svn_xml_make_close_tag(sb, pool, "commit"); 787251881Speter} 788251881Speter 789251881Speter 790251881Spetervoid 791251881Spetersvn_cl__print_xml_lock(svn_stringbuf_t **sb, 792251881Speter const svn_lock_t *lock, 793251881Speter apr_pool_t *pool) 794251881Speter{ 795251881Speter /* "<lock>" */ 796299742Sdim svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", SVN_VA_NULL); 797251881Speter 798251881Speter /* "<token>xx</token>" */ 799251881Speter svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token); 800251881Speter 801251881Speter /* "<owner>xx</owner>" */ 802251881Speter svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner); 803251881Speter 804251881Speter /* "<comment>xx</comment>" */ 805251881Speter svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment); 806251881Speter 807251881Speter /* "<created>xx</created>" */ 808251881Speter svn_cl__xml_tagged_cdata(sb, pool, "created", 809251881Speter svn_time_to_cstring(lock->creation_date, pool)); 810251881Speter 811251881Speter /* "<expires>xx</expires>" */ 812251881Speter if (lock->expiration_date != 0) 813251881Speter svn_cl__xml_tagged_cdata(sb, pool, "expires", 814251881Speter svn_time_to_cstring(lock->expiration_date, pool)); 815251881Speter 816251881Speter /* "</lock>" */ 817251881Speter svn_xml_make_close_tag(sb, pool, "lock"); 818251881Speter} 819251881Speter 820251881Speter 821251881Spetersvn_error_t * 822251881Spetersvn_cl__xml_print_header(const char *tagname, 823251881Speter apr_pool_t *pool) 824251881Speter{ 825251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 826251881Speter 827251881Speter /* <?xml version="1.0" encoding="UTF-8"?> */ 828251881Speter svn_xml_make_header2(&sb, "UTF-8", pool); 829251881Speter 830251881Speter /* "<TAGNAME>" */ 831299742Sdim svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, SVN_VA_NULL); 832251881Speter 833251881Speter return svn_cl__error_checked_fputs(sb->data, stdout); 834251881Speter} 835251881Speter 836251881Speter 837251881Spetersvn_error_t * 838251881Spetersvn_cl__xml_print_footer(const char *tagname, 839251881Speter apr_pool_t *pool) 840251881Speter{ 841251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 842251881Speter 843251881Speter /* "</TAGNAME>" */ 844251881Speter svn_xml_make_close_tag(&sb, pool, tagname); 845251881Speter return svn_cl__error_checked_fputs(sb->data, stdout); 846251881Speter} 847251881Speter 848251881Speter 849251881Speter/* A map for svn_node_kind_t values to XML strings */ 850251881Speterstatic const svn_token_map_t map_node_kind_xml[] = 851251881Speter{ 852251881Speter { "none", svn_node_none }, 853251881Speter { "file", svn_node_file }, 854251881Speter { "dir", svn_node_dir }, 855251881Speter { "", svn_node_unknown }, 856251881Speter { NULL, 0 } 857251881Speter}; 858251881Speter 859251881Speter/* A map for svn_node_kind_t values to human-readable strings */ 860251881Speterstatic const svn_token_map_t map_node_kind_human[] = 861251881Speter{ 862251881Speter { N_("none"), svn_node_none }, 863251881Speter { N_("file"), svn_node_file }, 864251881Speter { N_("dir"), svn_node_dir }, 865251881Speter { "", svn_node_unknown }, 866251881Speter { NULL, 0 } 867251881Speter}; 868251881Speter 869251881Speterconst char * 870251881Spetersvn_cl__node_kind_str_xml(svn_node_kind_t kind) 871251881Speter{ 872251881Speter return svn_token__to_word(map_node_kind_xml, kind); 873251881Speter} 874251881Speter 875251881Speterconst char * 876251881Spetersvn_cl__node_kind_str_human_readable(svn_node_kind_t kind) 877251881Speter{ 878251881Speter return _(svn_token__to_word(map_node_kind_human, kind)); 879251881Speter} 880251881Speter 881251881Speter 882251881Speter/* A map for svn_wc_operation_t values to XML strings */ 883251881Speterstatic const svn_token_map_t map_wc_operation_xml[] = 884251881Speter{ 885251881Speter { "none", svn_wc_operation_none }, 886251881Speter { "update", svn_wc_operation_update }, 887251881Speter { "switch", svn_wc_operation_switch }, 888251881Speter { "merge", svn_wc_operation_merge }, 889251881Speter { NULL, 0 } 890251881Speter}; 891251881Speter 892251881Speter/* A map for svn_wc_operation_t values to human-readable strings */ 893251881Speterstatic const svn_token_map_t map_wc_operation_human[] = 894251881Speter{ 895251881Speter { N_("none"), svn_wc_operation_none }, 896251881Speter { N_("update"), svn_wc_operation_update }, 897251881Speter { N_("switch"), svn_wc_operation_switch }, 898251881Speter { N_("merge"), svn_wc_operation_merge }, 899251881Speter { NULL, 0 } 900251881Speter}; 901251881Speter 902251881Speterconst char * 903251881Spetersvn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool) 904251881Speter{ 905251881Speter return svn_token__to_word(map_wc_operation_xml, operation); 906251881Speter} 907251881Speter 908251881Speterconst char * 909251881Spetersvn_cl__operation_str_human_readable(svn_wc_operation_t operation, 910251881Speter apr_pool_t *pool) 911251881Speter{ 912251881Speter return _(svn_token__to_word(map_wc_operation_human, operation)); 913251881Speter} 914251881Speter 915251881Speter 916251881Spetersvn_error_t * 917251881Spetersvn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets, 918251881Speter apr_getopt_t *os, 919251881Speter const apr_array_header_t *known_targets, 920251881Speter svn_client_ctx_t *ctx, 921251881Speter svn_boolean_t keep_last_origpath_on_truepath_collision, 922251881Speter apr_pool_t *pool) 923251881Speter{ 924251881Speter svn_error_t *err = svn_client_args_to_target_array2(targets, 925251881Speter os, 926251881Speter known_targets, 927251881Speter ctx, 928251881Speter keep_last_origpath_on_truepath_collision, 929251881Speter pool); 930251881Speter if (err) 931251881Speter { 932251881Speter if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED) 933251881Speter { 934251881Speter svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: "); 935251881Speter svn_error_clear(err); 936251881Speter } 937251881Speter else 938251881Speter return svn_error_trace(err); 939251881Speter } 940251881Speter return SVN_NO_ERROR; 941251881Speter} 942251881Speter 943251881Speter 944251881Speter/* Helper for svn_cl__get_changelist(); implements 945251881Speter svn_changelist_receiver_t. */ 946251881Speterstatic svn_error_t * 947251881Speterchangelist_receiver(void *baton, 948251881Speter const char *path, 949251881Speter const char *changelist, 950251881Speter apr_pool_t *pool) 951251881Speter{ 952251881Speter /* No need to check CHANGELIST; our caller only asked about one of them. */ 953251881Speter apr_array_header_t *paths = baton; 954251881Speter APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path); 955251881Speter return SVN_NO_ERROR; 956251881Speter} 957251881Speter 958251881Speter 959251881Spetersvn_error_t * 960251881Spetersvn_cl__changelist_paths(apr_array_header_t **paths, 961251881Speter const apr_array_header_t *changelists, 962251881Speter const apr_array_header_t *targets, 963251881Speter svn_depth_t depth, 964251881Speter svn_client_ctx_t *ctx, 965251881Speter apr_pool_t *result_pool, 966251881Speter apr_pool_t *scratch_pool) 967251881Speter{ 968251881Speter apr_array_header_t *found; 969251881Speter apr_hash_t *paths_hash; 970251881Speter apr_pool_t *iterpool; 971251881Speter int i; 972251881Speter 973251881Speter if (! (changelists && changelists->nelts)) 974251881Speter { 975251881Speter *paths = (apr_array_header_t *)targets; 976251881Speter return SVN_NO_ERROR; 977251881Speter } 978251881Speter 979251881Speter found = apr_array_make(scratch_pool, 8, sizeof(const char *)); 980251881Speter iterpool = svn_pool_create(scratch_pool); 981251881Speter for (i = 0; i < targets->nelts; i++) 982251881Speter { 983251881Speter const char *target = APR_ARRAY_IDX(targets, i, const char *); 984251881Speter svn_pool_clear(iterpool); 985251881Speter SVN_ERR(svn_client_get_changelists(target, changelists, depth, 986251881Speter changelist_receiver, found, 987251881Speter ctx, iterpool)); 988251881Speter } 989251881Speter svn_pool_destroy(iterpool); 990251881Speter 991251881Speter SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool)); 992251881Speter return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool)); 993251881Speter} 994251881Speter 995251881Spetersvn_cl__show_revs_t 996251881Spetersvn_cl__show_revs_from_word(const char *word) 997251881Speter{ 998251881Speter if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0) 999251881Speter return svn_cl__show_revs_merged; 1000251881Speter if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0) 1001251881Speter return svn_cl__show_revs_eligible; 1002251881Speter /* word is an invalid flavor. */ 1003251881Speter return svn_cl__show_revs_invalid; 1004251881Speter} 1005251881Speter 1006251881Speter 1007251881Spetersvn_error_t * 1008251881Spetersvn_cl__time_cstring_to_human_cstring(const char **human_cstring, 1009251881Speter const char *data, 1010251881Speter apr_pool_t *pool) 1011251881Speter{ 1012251881Speter svn_error_t *err; 1013251881Speter apr_time_t when; 1014251881Speter 1015251881Speter err = svn_time_from_cstring(&when, data, pool); 1016251881Speter if (err && err->apr_err == SVN_ERR_BAD_DATE) 1017251881Speter { 1018251881Speter svn_error_clear(err); 1019251881Speter 1020251881Speter *human_cstring = _("(invalid date)"); 1021251881Speter return SVN_NO_ERROR; 1022251881Speter } 1023251881Speter else if (err) 1024251881Speter return svn_error_trace(err); 1025251881Speter 1026251881Speter *human_cstring = svn_time_to_human_cstring(when, pool); 1027251881Speter 1028251881Speter return SVN_NO_ERROR; 1029251881Speter} 1030251881Speter 1031251881Speterconst char * 1032251881Spetersvn_cl__node_description(const svn_wc_conflict_version_t *node, 1033251881Speter const char *wc_repos_root_URL, 1034251881Speter apr_pool_t *pool) 1035251881Speter{ 1036251881Speter const char *root_str = "^"; 1037251881Speter const char *path_str = "..."; 1038251881Speter 1039251881Speter if (!node) 1040251881Speter /* Printing "(none)" the harder way to ensure conformity (mostly with 1041251881Speter * translations). */ 1042251881Speter return apr_psprintf(pool, "(%s)", 1043251881Speter svn_cl__node_kind_str_human_readable(svn_node_none)); 1044251881Speter 1045251881Speter /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL. 1046251881Speter * Otherwise show the complete URL, and if we can't, show dots. */ 1047251881Speter 1048251881Speter if (node->repos_url && 1049251881Speter (wc_repos_root_URL == NULL || 1050251881Speter strcmp(node->repos_url, wc_repos_root_URL) != 0)) 1051251881Speter root_str = node->repos_url; 1052251881Speter 1053251881Speter if (node->path_in_repos) 1054251881Speter path_str = node->path_in_repos; 1055251881Speter 1056251881Speter return apr_psprintf(pool, "(%s) %s@%ld", 1057251881Speter svn_cl__node_kind_str_human_readable(node->node_kind), 1058251881Speter svn_path_url_add_component2(root_str, path_str, pool), 1059251881Speter node->peg_rev); 1060251881Speter} 1061251881Speter 1062251881Spetersvn_error_t * 1063251881Spetersvn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p, 1064251881Speter const apr_array_header_t *targets, 1065251881Speter apr_pool_t *pool) 1066251881Speter{ 1067251881Speter int i; 1068251881Speter apr_array_header_t *true_targets; 1069251881Speter 1070251881Speter true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *)); 1071251881Speter 1072251881Speter for (i = 0; i < targets->nelts; i++) 1073251881Speter { 1074251881Speter const char *target = APR_ARRAY_IDX(targets, i, const char *); 1075251881Speter const char *true_target, *peg; 1076251881Speter 1077251881Speter SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg, 1078251881Speter target, pool)); 1079251881Speter if (peg[0] && peg[1]) 1080251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1081251881Speter _("'%s': a peg revision is not allowed here"), 1082251881Speter target); 1083251881Speter APR_ARRAY_PUSH(true_targets, const char *) = true_target; 1084251881Speter } 1085251881Speter 1086251881Speter SVN_ERR_ASSERT(true_targets_p); 1087251881Speter *true_targets_p = true_targets; 1088251881Speter 1089251881Speter return SVN_NO_ERROR; 1090251881Speter} 1091251881Speter 1092251881Spetersvn_error_t * 1093251881Spetersvn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets) 1094251881Speter{ 1095251881Speter svn_error_t *err; 1096251881Speter 1097251881Speter err = svn_client__assert_homogeneous_target_type(targets); 1098251881Speter if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) 1099251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL); 1100251881Speter return err; 1101251881Speter} 1102251881Speter 1103251881Spetersvn_error_t * 1104251881Spetersvn_cl__check_target_is_local_path(const char *target) 1105251881Speter{ 1106251881Speter if (svn_path_is_url(target)) 1107251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1108251881Speter _("'%s' is not a local path"), target); 1109251881Speter return SVN_NO_ERROR; 1110251881Speter} 1111251881Speter 1112251881Spetersvn_error_t * 1113251881Spetersvn_cl__check_targets_are_local_paths(const apr_array_header_t *targets) 1114251881Speter{ 1115251881Speter int i; 1116251881Speter 1117251881Speter for (i = 0; i < targets->nelts; i++) 1118251881Speter { 1119251881Speter const char *target = APR_ARRAY_IDX(targets, i, const char *); 1120251881Speter 1121251881Speter SVN_ERR(svn_cl__check_target_is_local_path(target)); 1122251881Speter } 1123251881Speter return SVN_NO_ERROR; 1124251881Speter} 1125251881Speter 1126251881Speterconst char * 1127251881Spetersvn_cl__local_style_skip_ancestor(const char *parent_path, 1128251881Speter const char *path, 1129251881Speter apr_pool_t *pool) 1130251881Speter{ 1131251881Speter const char *relpath = NULL; 1132251881Speter 1133251881Speter if (parent_path) 1134251881Speter relpath = svn_dirent_skip_ancestor(parent_path, path); 1135251881Speter 1136251881Speter return svn_dirent_local_style(relpath ? relpath : path, pool); 1137251881Speter} 1138251881Speter 1139251881Spetersvn_error_t * 1140251881Spetersvn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets, 1141251881Speter const char *propname, 1142251881Speter const svn_string_t *propval, 1143251881Speter apr_pool_t *scratch_pool) 1144251881Speter{ 1145251881Speter if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0) 1146251881Speter { 1147251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1148251881Speter int i; 1149251881Speter 1150251881Speter for (i = 0; i < targets->nelts; i++) 1151251881Speter { 1152251881Speter const char *detected_mimetype; 1153251881Speter const char *target = APR_ARRAY_IDX(targets, i, const char *); 1154251881Speter const char *local_abspath; 1155251881Speter const svn_string_t *canon_propval; 1156251881Speter svn_node_kind_t node_kind; 1157251881Speter 1158251881Speter svn_pool_clear(iterpool); 1159251881Speter 1160251881Speter SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); 1161251881Speter SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool)); 1162251881Speter if (node_kind != svn_node_file) 1163251881Speter continue; 1164251881Speter 1165251881Speter SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval, 1166251881Speter propname, propval, 1167251881Speter local_abspath, 1168251881Speter svn_node_file, 1169251881Speter FALSE, NULL, NULL, 1170251881Speter iterpool)); 1171251881Speter 1172251881Speter if (svn_mime_type_is_binary(canon_propval->data)) 1173251881Speter { 1174251881Speter SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype, 1175251881Speter local_abspath, NULL, 1176251881Speter iterpool)); 1177251881Speter if (detected_mimetype == NULL || 1178251881Speter !svn_mime_type_is_binary(detected_mimetype)) 1179251881Speter svn_error_clear(svn_cmdline_fprintf(stderr, iterpool, 1180251881Speter _("svn: warning: '%s' is a binary mime-type but file '%s' " 1181251881Speter "looks like text; diff, merge, blame, and other " 1182251881Speter "operations will stop working on this file\n"), 1183251881Speter canon_propval->data, 1184251881Speter svn_dirent_local_style(local_abspath, iterpool))); 1185251881Speter 1186251881Speter } 1187251881Speter } 1188251881Speter svn_pool_destroy(iterpool); 1189251881Speter } 1190251881Speter 1191251881Speter return SVN_NO_ERROR; 1192251881Speter} 1193251881Speter 1194