2 * util.c: Subversion command line client utility functions. Any
3 * functions that need to be shared across subcommands should be put
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements. See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership. The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License. You may obtain a copy of the License at
15 * http://www.apache.org/licenses/LICENSE-2.0
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied. See the License for the
21 * specific language governing permissions and limitations
23 * ====================================================================
26 /* ==================================================================== */
37 #include <apr_errno.h>
38 #include <apr_file_info.h>
39 #include <apr_strings.h>
40 #include <apr_tables.h>
41 #include <apr_general.h>
44 #include "svn_pools.h"
45 #include "svn_error.h"
46 #include "svn_ctype.h"
47 #include "svn_client.h"
48 #include "svn_cmdline.h"
49 #include "svn_string.h"
50 #include "svn_dirent_uri.h"
55 #include "svn_subst.h"
56 #include "svn_config.h"
60 #include "svn_props.h"
61 #include "svn_private_config.h"
64 #include "private/svn_token.h"
65 #include "private/svn_opt_private.h"
66 #include "private/svn_client_private.h"
67 #include "private/svn_cmdline_private.h"
68 #include "private/svn_string_private.h"
69 #ifdef HAS_ORGANIZATION_NAME
70 #include "freebsd-organization.h"
77 svn_cl__print_commit_info(const svn_commit_info_t *commit_info,
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, "
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));
103 svn_cl__merge_file_externally(const char *base_path,
104 const char *their_path,
106 const char *merged_path,
109 svn_boolean_t *remains_in_conflict,
113 /* Error if there is no editor specified */
114 if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS)
116 struct svn_config_t *cfg;
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);
129 for (c = merge_tool; *c; c++)
130 if (!svn_ctype_isspace(*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"));
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"));
146 const char *arguments[7] = { 0 };
150 apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
152 return svn_error_wrap_apr(status, NULL);
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;
162 SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,
163 arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,
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;
180 /* A svn_client_ctx_t's log_msg_baton3, for use with
181 svn_cl__make_log_msg_baton(). */
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. */
197 svn_cl__make_log_msg_baton(void **baton,
198 svn_cl__opt_state_t *opt_state,
199 const char *base_dir /* UTF-8! */,
203 struct log_msg_baton *lmb = apr_palloc(pool, sizeof(*lmb));
205 if (opt_state->filedata)
207 if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
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
213 return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
214 _("Log message contains a zero byte"));
216 lmb->message = opt_state->filedata->data;
220 lmb->message = opt_state->message;
223 lmb->editor_cmd = opt_state->editor_cmd;
224 if (opt_state->encoding)
226 lmb->message_encoding = opt_state->encoding;
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,
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;
249 svn_cl__cleanup_log_msg(void *log_msg_baton,
250 svn_error_t *commit_err,
253 struct log_msg_baton *lmb = log_msg_baton;
256 /* If there was no tmpfile left, or there is no log message baton,
257 return COMMIT_ERR. */
258 if ((! lmb) || (! lmb->tmpfile_left))
261 /* If there was no commit error, cleanup the tmpfile and return. */
263 return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool);
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. */
271 err = svn_error_createf(commit_err->apr_err, NULL,
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:")));
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
286 truncate_buffer_at_prefix(apr_size_t *new_len,
290 char *substring = buffer;
292 assert(buffer && prefix);
294 /* Initialize *NEW_LEN. */
296 *new_len = strlen(buffer);
300 /* Find PREFIX in BUFFER. */
301 substring = strstr(substring, prefix);
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'))
314 *new_len = substring - buffer;
318 /* Well, it wasn't really a prefix, so just advance by 1
319 character and continue. */
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.
334 static const char *prefixes[] = {
346 cleanmsg(apr_size_t *l, char *s)
354 for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
356 while ((kw = strstr(pos, prefixes[i])) != NULL) {
357 /* Check to see if keyword is at start of line (or buffer) */
358 if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
362 p = kw + strlen(prefixes[i]);
365 if (*p == ' ' || *p == '\t') {
369 if (*p == '\0' || *p == '\r' || *p == '\n')
374 if (empty && (*p == '\r' || *p == '\n')) {
375 memmove(kw, p + 1, strlen(p + 1) + 1);
389 #define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
392 svn_cl__get_log_message(const char **log_msg,
393 const char **tmp_file,
394 const apr_array_header_t *commit_items,
398 svn_stringbuf_t *default_msg = NULL;
399 struct log_msg_baton *lmb = baton;
400 svn_stringbuf_t *message = NULL;
402 /* Set default message. */
403 default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
404 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
405 svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
406 svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
407 svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
408 svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
409 svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
410 svn_stringbuf_appendcstr(default_msg, "MFC after:\t" APR_EOL_STR);
411 svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
412 svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t"
413 #ifdef HAS_ORGANIZATION_NAME
417 svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
418 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
419 svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR);
420 svn_stringbuf_appendcstr(default_msg, "> PR: If a GNATS PR is affected by the change." APR_EOL_STR);
421 svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR);
422 svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR);
423 svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR);
424 svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR);
425 svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR);
426 svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR);
427 svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR);
428 svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
429 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
434 svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool);
435 svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str));
437 /* Trim incoming messages of the EOF marker text and the junk
439 truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data,
441 cleanmsg(NULL, (char*)log_msg_buf->data);
443 /* Make a string from a stringbuf, sharing the data allocation. */
444 log_msg_str->data = log_msg_buf->data;
445 log_msg_str->len = log_msg_buf->len;
446 SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE,
447 log_msg_str, lmb->message_encoding,
449 _("Error normalizing log message to internal format"));
451 *log_msg = log_msg_str->data;
455 if (! commit_items->nelts)
463 /* We still don't have a valid commit message. Use $EDITOR to
464 get one. Note that svn_cl__edit_string_externally will still
465 return a UTF-8'ized log message. */
467 svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
468 svn_error_t *err = SVN_NO_ERROR;
469 svn_string_t *msg_string = svn_string_create_empty(pool);
471 for (i = 0; i < commit_items->nelts; i++)
473 svn_client_commit_item3_t *item
474 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
475 const char *path = item->path;
476 char text_mod = '_', prop_mod = ' ', unlock = ' ';
483 if (! svn_path_is_url(path) && lmb->base_dir)
484 path = svn_dirent_is_child(lmb->base_dir, path, pool);
486 /* If still no path, then just use current directory. */
490 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
491 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
493 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
495 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
497 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
500 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
503 if (! lmb->keep_locks
504 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
507 svn_stringbuf_appendbyte(tmp_message, text_mod);
508 svn_stringbuf_appendbyte(tmp_message, prop_mod);
509 svn_stringbuf_appendbyte(tmp_message, unlock);
510 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
511 /* History included via copy/move. */
512 svn_stringbuf_appendcstr(tmp_message, "+ ");
514 svn_stringbuf_appendcstr(tmp_message, " ");
515 svn_stringbuf_appendcstr(tmp_message, path);
516 svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
519 msg_string->data = tmp_message->data;
520 msg_string->len = tmp_message->len;
522 /* Use the external edit to get a log message. */
523 if (! lmb->non_interactive)
525 err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
526 lmb->editor_cmd, lmb->base_dir,
527 msg_string, "svn-commit",
529 lmb->message_encoding,
532 else /* non_interactive flag says we can't pop up an editor, so error */
534 return svn_error_create
535 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
536 _("Cannot invoke editor to get log message "
537 "when non-interactive"));
540 /* Dup the tmpfile path into its baton's pool. */
541 *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
544 /* If the edit returned an error, handle it. */
547 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
548 err = svn_error_quick_wrap
549 (err, _("Could not use external editor to fetch log message; "
550 "consider setting the $SVN_EDITOR environment variable "
551 "or using the --message (-m) or --file (-F) options"));
552 return svn_error_trace(err);
556 message = svn_stringbuf_create_from_string(msg_string, pool);
558 /* Strip the prefix from the buffer. */
560 truncate_buffer_at_prefix(&message->len, message->data,
563 * Since we're adding freebsd-specific tokens to the log message,
564 * clean out any leftovers to avoid accidently sending them to other
565 * projects that won't be expecting them.
568 cleanmsg(&message->len, message->data);
572 /* We did get message, now check if it is anything more than just
573 white space as we will consider white space only as empty */
576 for (len = 0; len < message->len; len++)
578 /* FIXME: should really use an UTF-8 whitespace test
579 rather than svn_ctype_isspace, which is ASCII only */
580 if (! svn_ctype_isspace(message->data[len]))
583 if (len == message->len)
590 SVN_ERR(svn_cmdline_prompt_user2
592 _("\nLog message unchanged or not specified\n"
593 "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
596 int letter = apr_tolower(reply[0]);
598 /* If the user chooses to abort, we cleanup the
599 temporary file and exit the loop with a NULL
603 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
604 *tmp_file = lmb->tmpfile_left = NULL;
608 /* If the user chooses to continue, we make an empty
609 message, which will cause us to exit the loop. We
610 also cleanup the temporary file. */
613 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
614 *tmp_file = lmb->tmpfile_left = NULL;
615 message = svn_stringbuf_create_empty(pool);
618 /* If the user chooses anything else, the loop will
619 continue on the NULL message. */
624 *log_msg = message ? message->data : NULL;
629 /* ### The way our error wrapping currently works, the error returned
630 * from here will look as though it originates in this source file,
631 * instead of in the caller's source file. This can be a bit
632 * misleading, until one starts debugging. Ideally, there'd be a way
633 * to wrap an error while preserving its FILE/LINE info.
636 svn_cl__may_need_force(svn_error_t *err)
639 && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
640 err->apr_err == SVN_ERR_CLIENT_MODIFIED))
642 /* Should this svn_error_compose a new error number? Probably not,
643 the error hasn't changed. */
644 err = svn_error_quick_wrap
645 (err, _("Use --force to override this restriction (local modifications "
649 return svn_error_trace(err);
654 svn_cl__error_checked_fputs(const char *string, FILE* stream)
656 /* On POSIX systems, errno will be set on an error in fputs, but this might
657 not be the case on other platforms. We reset errno and only
658 use it if it was set by the below fputs call. Else, we just return
662 if (fputs(string, stream) == EOF)
665 return svn_error_wrap_apr(errno, _("Write error"));
667 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
675 svn_cl__try(svn_error_t *err,
676 apr_array_header_t *errors_seen,
682 apr_status_t apr_err;
686 while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
691 svn_boolean_t add = TRUE;
693 /* Don't report duplicate error codes. */
694 for (i = 0; i < errors_seen->nelts; i++)
696 if (APR_ARRAY_IDX(errors_seen, i,
697 apr_status_t) == err->apr_err)
704 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
706 if (err->apr_err == apr_err)
709 svn_handle_warning2(stderr, err, "svn: ");
710 svn_error_clear(err);
717 return svn_error_trace(err);
722 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
729 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
731 svn_xml_escape_cdata_cstring(sb, string, pool);
732 svn_xml_make_close_tag(sb, pool, tagname);
738 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
739 svn_revnum_t revision,
745 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
747 apr_psprintf(pool, "%ld", revision), NULL);
749 /* "<author>xx</author>" */
751 svn_cl__xml_tagged_cdata(sb, pool, "author", author);
753 /* "<date>xx</date>" */
755 svn_cl__xml_tagged_cdata(sb, pool, "date", date);
758 svn_xml_make_close_tag(sb, pool, "commit");
763 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
764 const svn_lock_t *lock,
768 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL);
770 /* "<token>xx</token>" */
771 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
773 /* "<owner>xx</owner>" */
774 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
776 /* "<comment>xx</comment>" */
777 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
779 /* "<created>xx</created>" */
780 svn_cl__xml_tagged_cdata(sb, pool, "created",
781 svn_time_to_cstring(lock->creation_date, pool));
783 /* "<expires>xx</expires>" */
784 if (lock->expiration_date != 0)
785 svn_cl__xml_tagged_cdata(sb, pool, "expires",
786 svn_time_to_cstring(lock->expiration_date, pool));
789 svn_xml_make_close_tag(sb, pool, "lock");
794 svn_cl__xml_print_header(const char *tagname,
797 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
799 /* <?xml version="1.0" encoding="UTF-8"?> */
800 svn_xml_make_header2(&sb, "UTF-8", pool);
803 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL);
805 return svn_cl__error_checked_fputs(sb->data, stdout);
810 svn_cl__xml_print_footer(const char *tagname,
813 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
816 svn_xml_make_close_tag(&sb, pool, tagname);
817 return svn_cl__error_checked_fputs(sb->data, stdout);
821 /* A map for svn_node_kind_t values to XML strings */
822 static const svn_token_map_t map_node_kind_xml[] =
824 { "none", svn_node_none },
825 { "file", svn_node_file },
826 { "dir", svn_node_dir },
827 { "", svn_node_unknown },
831 /* A map for svn_node_kind_t values to human-readable strings */
832 static const svn_token_map_t map_node_kind_human[] =
834 { N_("none"), svn_node_none },
835 { N_("file"), svn_node_file },
836 { N_("dir"), svn_node_dir },
837 { "", svn_node_unknown },
842 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
844 return svn_token__to_word(map_node_kind_xml, kind);
848 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
850 return _(svn_token__to_word(map_node_kind_human, kind));
854 /* A map for svn_wc_operation_t values to XML strings */
855 static const svn_token_map_t map_wc_operation_xml[] =
857 { "none", svn_wc_operation_none },
858 { "update", svn_wc_operation_update },
859 { "switch", svn_wc_operation_switch },
860 { "merge", svn_wc_operation_merge },
864 /* A map for svn_wc_operation_t values to human-readable strings */
865 static const svn_token_map_t map_wc_operation_human[] =
867 { N_("none"), svn_wc_operation_none },
868 { N_("update"), svn_wc_operation_update },
869 { N_("switch"), svn_wc_operation_switch },
870 { N_("merge"), svn_wc_operation_merge },
875 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
877 return svn_token__to_word(map_wc_operation_xml, operation);
881 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
884 return _(svn_token__to_word(map_wc_operation_human, operation));
889 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
891 const apr_array_header_t *known_targets,
892 svn_client_ctx_t *ctx,
893 svn_boolean_t keep_last_origpath_on_truepath_collision,
896 svn_error_t *err = svn_client_args_to_target_array2(targets,
900 keep_last_origpath_on_truepath_collision,
904 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
906 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
907 svn_error_clear(err);
910 return svn_error_trace(err);
916 /* Helper for svn_cl__get_changelist(); implements
917 svn_changelist_receiver_t. */
919 changelist_receiver(void *baton,
921 const char *changelist,
924 /* No need to check CHANGELIST; our caller only asked about one of them. */
925 apr_array_header_t *paths = baton;
926 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
932 svn_cl__changelist_paths(apr_array_header_t **paths,
933 const apr_array_header_t *changelists,
934 const apr_array_header_t *targets,
936 svn_client_ctx_t *ctx,
937 apr_pool_t *result_pool,
938 apr_pool_t *scratch_pool)
940 apr_array_header_t *found;
941 apr_hash_t *paths_hash;
942 apr_pool_t *iterpool;
945 if (! (changelists && changelists->nelts))
947 *paths = (apr_array_header_t *)targets;
951 found = apr_array_make(scratch_pool, 8, sizeof(const char *));
952 iterpool = svn_pool_create(scratch_pool);
953 for (i = 0; i < targets->nelts; i++)
955 const char *target = APR_ARRAY_IDX(targets, i, const char *);
956 svn_pool_clear(iterpool);
957 SVN_ERR(svn_client_get_changelists(target, changelists, depth,
958 changelist_receiver, found,
961 svn_pool_destroy(iterpool);
963 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
964 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
968 svn_cl__show_revs_from_word(const char *word)
970 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
971 return svn_cl__show_revs_merged;
972 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
973 return svn_cl__show_revs_eligible;
974 /* word is an invalid flavor. */
975 return svn_cl__show_revs_invalid;
980 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
987 err = svn_time_from_cstring(&when, data, pool);
988 if (err && err->apr_err == SVN_ERR_BAD_DATE)
990 svn_error_clear(err);
992 *human_cstring = _("(invalid date)");
996 return svn_error_trace(err);
998 *human_cstring = svn_time_to_human_cstring(when, pool);
1000 return SVN_NO_ERROR;
1004 svn_cl__node_description(const svn_wc_conflict_version_t *node,
1005 const char *wc_repos_root_URL,
1008 const char *root_str = "^";
1009 const char *path_str = "...";
1012 /* Printing "(none)" the harder way to ensure conformity (mostly with
1014 return apr_psprintf(pool, "(%s)",
1015 svn_cl__node_kind_str_human_readable(svn_node_none));
1017 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1018 * Otherwise show the complete URL, and if we can't, show dots. */
1020 if (node->repos_url &&
1021 (wc_repos_root_URL == NULL ||
1022 strcmp(node->repos_url, wc_repos_root_URL) != 0))
1023 root_str = node->repos_url;
1025 if (node->path_in_repos)
1026 path_str = node->path_in_repos;
1028 return apr_psprintf(pool, "(%s) %s@%ld",
1029 svn_cl__node_kind_str_human_readable(node->node_kind),
1030 svn_path_url_add_component2(root_str, path_str, pool),
1035 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1036 const apr_array_header_t *targets,
1040 apr_array_header_t *true_targets;
1042 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1044 for (i = 0; i < targets->nelts; i++)
1046 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1047 const char *true_target, *peg;
1049 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1051 if (peg[0] && peg[1])
1052 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1053 _("'%s': a peg revision is not allowed here"),
1055 APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1058 SVN_ERR_ASSERT(true_targets_p);
1059 *true_targets_p = true_targets;
1061 return SVN_NO_ERROR;
1065 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1069 err = svn_client__assert_homogeneous_target_type(targets);
1070 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1071 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1076 svn_cl__check_target_is_local_path(const char *target)
1078 if (svn_path_is_url(target))
1079 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1080 _("'%s' is not a local path"), target);
1081 return SVN_NO_ERROR;
1085 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1089 for (i = 0; i < targets->nelts; i++)
1091 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1093 SVN_ERR(svn_cl__check_target_is_local_path(target));
1095 return SVN_NO_ERROR;
1099 svn_cl__local_style_skip_ancestor(const char *parent_path,
1103 const char *relpath = NULL;
1106 relpath = svn_dirent_skip_ancestor(parent_path, path);
1108 return svn_dirent_local_style(relpath ? relpath : path, pool);
1112 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1113 const char *propname,
1114 const svn_string_t *propval,
1115 apr_pool_t *scratch_pool)
1117 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1119 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1122 for (i = 0; i < targets->nelts; i++)
1124 const char *detected_mimetype;
1125 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1126 const char *local_abspath;
1127 const svn_string_t *canon_propval;
1128 svn_node_kind_t node_kind;
1130 svn_pool_clear(iterpool);
1132 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1133 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1134 if (node_kind != svn_node_file)
1137 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1144 if (svn_mime_type_is_binary(canon_propval->data))
1146 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1147 local_abspath, NULL,
1149 if (detected_mimetype == NULL ||
1150 !svn_mime_type_is_binary(detected_mimetype))
1151 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1152 _("svn: warning: '%s' is a binary mime-type but file '%s' "
1153 "looks like text; diff, merge, blame, and other "
1154 "operations will stop working on this file\n"),
1155 canon_propval->data,
1156 svn_dirent_local_style(local_abspath, iterpool)));
1160 svn_pool_destroy(iterpool);
1163 return SVN_NO_ERROR;