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[] = {
347 cleanmsg(apr_size_t *l, char *s)
355 for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
357 while ((kw = strstr(pos, prefixes[i])) != NULL) {
358 /* Check to see if keyword is at start of line (or buffer) */
359 if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
363 p = kw + strlen(prefixes[i]);
366 if (*p == ' ' || *p == '\t') {
370 if (*p == '\0' || *p == '\r' || *p == '\n')
375 if (empty && (*p == '\r' || *p == '\n')) {
376 memmove(kw, p + 1, strlen(p + 1) + 1);
390 #define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
393 svn_cl__get_log_message(const char **log_msg,
394 const char **tmp_file,
395 const apr_array_header_t *commit_items,
399 svn_stringbuf_t *default_msg = NULL;
400 struct log_msg_baton *lmb = baton;
401 svn_stringbuf_t *message = NULL;
403 /* Set default message. */
404 default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
405 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
406 svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
407 svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
408 svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
409 svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
410 svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
411 svn_stringbuf_appendcstr(default_msg, "MFC after:\t" APR_EOL_STR);
412 svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
413 svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
414 svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t"
415 #ifdef HAS_ORGANIZATION_NAME
419 svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
420 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
421 svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR);
422 svn_stringbuf_appendcstr(default_msg, "> PR: If a Bugzilla PR is affected by the change." APR_EOL_STR);
423 svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR);
424 svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR);
425 svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR);
426 svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR);
427 svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR);
428 svn_stringbuf_appendcstr(default_msg, "> Relnotes: Set to 'yes' for mention in release notes." APR_EOL_STR);
429 svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR);
430 svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR);
431 svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
432 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
437 svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool);
438 svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str));
440 /* Trim incoming messages of the EOF marker text and the junk
442 truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data,
444 cleanmsg(NULL, (char*)log_msg_buf->data);
446 /* Make a string from a stringbuf, sharing the data allocation. */
447 log_msg_str->data = log_msg_buf->data;
448 log_msg_str->len = log_msg_buf->len;
449 SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE,
450 log_msg_str, lmb->message_encoding,
452 _("Error normalizing log message to internal format"));
454 *log_msg = log_msg_str->data;
458 if (! commit_items->nelts)
466 /* We still don't have a valid commit message. Use $EDITOR to
467 get one. Note that svn_cl__edit_string_externally will still
468 return a UTF-8'ized log message. */
470 svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
471 svn_error_t *err = SVN_NO_ERROR;
472 svn_string_t *msg_string = svn_string_create_empty(pool);
474 for (i = 0; i < commit_items->nelts; i++)
476 svn_client_commit_item3_t *item
477 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
478 const char *path = item->path;
479 char text_mod = '_', prop_mod = ' ', unlock = ' ';
486 if (! svn_path_is_url(path) && lmb->base_dir)
487 path = svn_dirent_is_child(lmb->base_dir, path, pool);
489 /* If still no path, then just use current directory. */
493 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
494 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
496 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
498 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
500 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
503 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
506 if (! lmb->keep_locks
507 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
510 svn_stringbuf_appendbyte(tmp_message, text_mod);
511 svn_stringbuf_appendbyte(tmp_message, prop_mod);
512 svn_stringbuf_appendbyte(tmp_message, unlock);
513 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
514 /* History included via copy/move. */
515 svn_stringbuf_appendcstr(tmp_message, "+ ");
517 svn_stringbuf_appendcstr(tmp_message, " ");
518 svn_stringbuf_appendcstr(tmp_message, path);
519 svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
522 msg_string->data = tmp_message->data;
523 msg_string->len = tmp_message->len;
525 /* Use the external edit to get a log message. */
526 if (! lmb->non_interactive)
528 err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
529 lmb->editor_cmd, lmb->base_dir,
530 msg_string, "svn-commit",
532 lmb->message_encoding,
535 else /* non_interactive flag says we can't pop up an editor, so error */
537 return svn_error_create
538 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
539 _("Cannot invoke editor to get log message "
540 "when non-interactive"));
543 /* Dup the tmpfile path into its baton's pool. */
544 *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
547 /* If the edit returned an error, handle it. */
550 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
551 err = svn_error_quick_wrap
552 (err, _("Could not use external editor to fetch log message; "
553 "consider setting the $SVN_EDITOR environment variable "
554 "or using the --message (-m) or --file (-F) options"));
555 return svn_error_trace(err);
559 message = svn_stringbuf_create_from_string(msg_string, pool);
561 /* Strip the prefix from the buffer. */
563 truncate_buffer_at_prefix(&message->len, message->data,
566 * Since we're adding freebsd-specific tokens to the log message,
567 * clean out any leftovers to avoid accidently sending them to other
568 * projects that won't be expecting them.
571 cleanmsg(&message->len, message->data);
575 /* We did get message, now check if it is anything more than just
576 white space as we will consider white space only as empty */
579 for (len = 0; len < message->len; len++)
581 /* FIXME: should really use an UTF-8 whitespace test
582 rather than svn_ctype_isspace, which is ASCII only */
583 if (! svn_ctype_isspace(message->data[len]))
586 if (len == message->len)
593 SVN_ERR(svn_cmdline_prompt_user2
595 _("\nLog message unchanged or not specified\n"
596 "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
599 int letter = apr_tolower(reply[0]);
601 /* If the user chooses to abort, we cleanup the
602 temporary file and exit the loop with a NULL
606 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
607 *tmp_file = lmb->tmpfile_left = NULL;
611 /* If the user chooses to continue, we make an empty
612 message, which will cause us to exit the loop. We
613 also cleanup the temporary file. */
616 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
617 *tmp_file = lmb->tmpfile_left = NULL;
618 message = svn_stringbuf_create_empty(pool);
621 /* If the user chooses anything else, the loop will
622 continue on the NULL message. */
627 *log_msg = message ? message->data : NULL;
632 /* ### The way our error wrapping currently works, the error returned
633 * from here will look as though it originates in this source file,
634 * instead of in the caller's source file. This can be a bit
635 * misleading, until one starts debugging. Ideally, there'd be a way
636 * to wrap an error while preserving its FILE/LINE info.
639 svn_cl__may_need_force(svn_error_t *err)
642 && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
643 err->apr_err == SVN_ERR_CLIENT_MODIFIED))
645 /* Should this svn_error_compose a new error number? Probably not,
646 the error hasn't changed. */
647 err = svn_error_quick_wrap
648 (err, _("Use --force to override this restriction (local modifications "
652 return svn_error_trace(err);
657 svn_cl__error_checked_fputs(const char *string, FILE* stream)
659 /* On POSIX systems, errno will be set on an error in fputs, but this might
660 not be the case on other platforms. We reset errno and only
661 use it if it was set by the below fputs call. Else, we just return
665 if (fputs(string, stream) == EOF)
668 return svn_error_wrap_apr(errno, _("Write error"));
670 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
678 svn_cl__try(svn_error_t *err,
679 apr_array_header_t *errors_seen,
685 apr_status_t apr_err;
689 while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
694 svn_boolean_t add = TRUE;
696 /* Don't report duplicate error codes. */
697 for (i = 0; i < errors_seen->nelts; i++)
699 if (APR_ARRAY_IDX(errors_seen, i,
700 apr_status_t) == err->apr_err)
707 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
709 if (err->apr_err == apr_err)
712 svn_handle_warning2(stderr, err, "svn: ");
713 svn_error_clear(err);
720 return svn_error_trace(err);
725 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
732 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
734 svn_xml_escape_cdata_cstring(sb, string, pool);
735 svn_xml_make_close_tag(sb, pool, tagname);
741 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
742 svn_revnum_t revision,
748 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
750 apr_psprintf(pool, "%ld", revision), NULL);
752 /* "<author>xx</author>" */
754 svn_cl__xml_tagged_cdata(sb, pool, "author", author);
756 /* "<date>xx</date>" */
758 svn_cl__xml_tagged_cdata(sb, pool, "date", date);
761 svn_xml_make_close_tag(sb, pool, "commit");
766 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
767 const svn_lock_t *lock,
771 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL);
773 /* "<token>xx</token>" */
774 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
776 /* "<owner>xx</owner>" */
777 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
779 /* "<comment>xx</comment>" */
780 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
782 /* "<created>xx</created>" */
783 svn_cl__xml_tagged_cdata(sb, pool, "created",
784 svn_time_to_cstring(lock->creation_date, pool));
786 /* "<expires>xx</expires>" */
787 if (lock->expiration_date != 0)
788 svn_cl__xml_tagged_cdata(sb, pool, "expires",
789 svn_time_to_cstring(lock->expiration_date, pool));
792 svn_xml_make_close_tag(sb, pool, "lock");
797 svn_cl__xml_print_header(const char *tagname,
800 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
802 /* <?xml version="1.0" encoding="UTF-8"?> */
803 svn_xml_make_header2(&sb, "UTF-8", pool);
806 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL);
808 return svn_cl__error_checked_fputs(sb->data, stdout);
813 svn_cl__xml_print_footer(const char *tagname,
816 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
819 svn_xml_make_close_tag(&sb, pool, tagname);
820 return svn_cl__error_checked_fputs(sb->data, stdout);
824 /* A map for svn_node_kind_t values to XML strings */
825 static const svn_token_map_t map_node_kind_xml[] =
827 { "none", svn_node_none },
828 { "file", svn_node_file },
829 { "dir", svn_node_dir },
830 { "", svn_node_unknown },
834 /* A map for svn_node_kind_t values to human-readable strings */
835 static const svn_token_map_t map_node_kind_human[] =
837 { N_("none"), svn_node_none },
838 { N_("file"), svn_node_file },
839 { N_("dir"), svn_node_dir },
840 { "", svn_node_unknown },
845 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
847 return svn_token__to_word(map_node_kind_xml, kind);
851 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
853 return _(svn_token__to_word(map_node_kind_human, kind));
857 /* A map for svn_wc_operation_t values to XML strings */
858 static const svn_token_map_t map_wc_operation_xml[] =
860 { "none", svn_wc_operation_none },
861 { "update", svn_wc_operation_update },
862 { "switch", svn_wc_operation_switch },
863 { "merge", svn_wc_operation_merge },
867 /* A map for svn_wc_operation_t values to human-readable strings */
868 static const svn_token_map_t map_wc_operation_human[] =
870 { N_("none"), svn_wc_operation_none },
871 { N_("update"), svn_wc_operation_update },
872 { N_("switch"), svn_wc_operation_switch },
873 { N_("merge"), svn_wc_operation_merge },
878 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
880 return svn_token__to_word(map_wc_operation_xml, operation);
884 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
887 return _(svn_token__to_word(map_wc_operation_human, operation));
892 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
894 const apr_array_header_t *known_targets,
895 svn_client_ctx_t *ctx,
896 svn_boolean_t keep_last_origpath_on_truepath_collision,
899 svn_error_t *err = svn_client_args_to_target_array2(targets,
903 keep_last_origpath_on_truepath_collision,
907 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
909 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
910 svn_error_clear(err);
913 return svn_error_trace(err);
919 /* Helper for svn_cl__get_changelist(); implements
920 svn_changelist_receiver_t. */
922 changelist_receiver(void *baton,
924 const char *changelist,
927 /* No need to check CHANGELIST; our caller only asked about one of them. */
928 apr_array_header_t *paths = baton;
929 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
935 svn_cl__changelist_paths(apr_array_header_t **paths,
936 const apr_array_header_t *changelists,
937 const apr_array_header_t *targets,
939 svn_client_ctx_t *ctx,
940 apr_pool_t *result_pool,
941 apr_pool_t *scratch_pool)
943 apr_array_header_t *found;
944 apr_hash_t *paths_hash;
945 apr_pool_t *iterpool;
948 if (! (changelists && changelists->nelts))
950 *paths = (apr_array_header_t *)targets;
954 found = apr_array_make(scratch_pool, 8, sizeof(const char *));
955 iterpool = svn_pool_create(scratch_pool);
956 for (i = 0; i < targets->nelts; i++)
958 const char *target = APR_ARRAY_IDX(targets, i, const char *);
959 svn_pool_clear(iterpool);
960 SVN_ERR(svn_client_get_changelists(target, changelists, depth,
961 changelist_receiver, found,
964 svn_pool_destroy(iterpool);
966 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
967 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
971 svn_cl__show_revs_from_word(const char *word)
973 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
974 return svn_cl__show_revs_merged;
975 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
976 return svn_cl__show_revs_eligible;
977 /* word is an invalid flavor. */
978 return svn_cl__show_revs_invalid;
983 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
990 err = svn_time_from_cstring(&when, data, pool);
991 if (err && err->apr_err == SVN_ERR_BAD_DATE)
993 svn_error_clear(err);
995 *human_cstring = _("(invalid date)");
999 return svn_error_trace(err);
1001 *human_cstring = svn_time_to_human_cstring(when, pool);
1003 return SVN_NO_ERROR;
1007 svn_cl__node_description(const svn_wc_conflict_version_t *node,
1008 const char *wc_repos_root_URL,
1011 const char *root_str = "^";
1012 const char *path_str = "...";
1015 /* Printing "(none)" the harder way to ensure conformity (mostly with
1017 return apr_psprintf(pool, "(%s)",
1018 svn_cl__node_kind_str_human_readable(svn_node_none));
1020 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1021 * Otherwise show the complete URL, and if we can't, show dots. */
1023 if (node->repos_url &&
1024 (wc_repos_root_URL == NULL ||
1025 strcmp(node->repos_url, wc_repos_root_URL) != 0))
1026 root_str = node->repos_url;
1028 if (node->path_in_repos)
1029 path_str = node->path_in_repos;
1031 return apr_psprintf(pool, "(%s) %s@%ld",
1032 svn_cl__node_kind_str_human_readable(node->node_kind),
1033 svn_path_url_add_component2(root_str, path_str, pool),
1038 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1039 const apr_array_header_t *targets,
1043 apr_array_header_t *true_targets;
1045 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1047 for (i = 0; i < targets->nelts; i++)
1049 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1050 const char *true_target, *peg;
1052 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1054 if (peg[0] && peg[1])
1055 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1056 _("'%s': a peg revision is not allowed here"),
1058 APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1061 SVN_ERR_ASSERT(true_targets_p);
1062 *true_targets_p = true_targets;
1064 return SVN_NO_ERROR;
1068 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1072 err = svn_client__assert_homogeneous_target_type(targets);
1073 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1074 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1079 svn_cl__check_target_is_local_path(const char *target)
1081 if (svn_path_is_url(target))
1082 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1083 _("'%s' is not a local path"), target);
1084 return SVN_NO_ERROR;
1088 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1092 for (i = 0; i < targets->nelts; i++)
1094 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1096 SVN_ERR(svn_cl__check_target_is_local_path(target));
1098 return SVN_NO_ERROR;
1102 svn_cl__local_style_skip_ancestor(const char *parent_path,
1106 const char *relpath = NULL;
1109 relpath = svn_dirent_skip_ancestor(parent_path, path);
1111 return svn_dirent_local_style(relpath ? relpath : path, pool);
1115 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1116 const char *propname,
1117 const svn_string_t *propval,
1118 apr_pool_t *scratch_pool)
1120 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1122 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1125 for (i = 0; i < targets->nelts; i++)
1127 const char *detected_mimetype;
1128 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1129 const char *local_abspath;
1130 const svn_string_t *canon_propval;
1131 svn_node_kind_t node_kind;
1133 svn_pool_clear(iterpool);
1135 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1136 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1137 if (node_kind != svn_node_file)
1140 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1147 if (svn_mime_type_is_binary(canon_propval->data))
1149 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1150 local_abspath, NULL,
1152 if (detected_mimetype == NULL ||
1153 !svn_mime_type_is_binary(detected_mimetype))
1154 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1155 _("svn: warning: '%s' is a binary mime-type but file '%s' "
1156 "looks like text; diff, merge, blame, and other "
1157 "operations will stop working on this file\n"),
1158 canon_propval->data,
1159 svn_dirent_local_style(local_abspath, iterpool)));
1163 svn_pool_destroy(iterpool);
1166 return SVN_NO_ERROR;