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[] = {
344 "Differential Revision:",
348 cleanmsg(apr_size_t *l, char *s)
356 for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
358 while ((kw = strstr(pos, prefixes[i])) != NULL) {
359 /* Check to see if keyword is at start of line (or buffer) */
360 if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
364 p = kw + strlen(prefixes[i]);
367 if (*p == ' ' || *p == '\t') {
371 if (*p == '\0' || *p == '\r' || *p == '\n')
376 if (empty && (*p == '\r' || *p == '\n')) {
377 memmove(kw, p + 1, strlen(p + 1) + 1);
391 #define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
394 svn_cl__get_log_message(const char **log_msg,
395 const char **tmp_file,
396 const apr_array_header_t *commit_items,
400 svn_stringbuf_t *default_msg = NULL;
401 struct log_msg_baton *lmb = baton;
402 svn_stringbuf_t *message = NULL;
404 const char *mfc_after, *sponsored_by;
406 cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
408 /* Set default message. */
409 default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
410 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
411 svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
412 svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
413 svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
414 svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
415 svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
416 svn_stringbuf_appendcstr(default_msg, "MFC after:\t");
417 svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL);
418 if (mfc_after != NULL)
419 svn_stringbuf_appendcstr(default_msg, mfc_after);
420 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
421 svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
422 svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
423 svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t");
424 svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by",
425 #ifdef HAS_ORGANIZATION_NAME
430 if (sponsored_by != NULL)
431 svn_stringbuf_appendcstr(default_msg, sponsored_by);
432 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
433 svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR);
434 svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
435 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
436 svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR);
437 svn_stringbuf_appendcstr(default_msg, "> PR: If a Bugzilla PR is affected by the change." APR_EOL_STR);
438 svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR);
439 svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR);
440 svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR);
441 svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR);
442 svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR);
443 svn_stringbuf_appendcstr(default_msg, "> MFH: Ports tree branch name. Request approval for merge." APR_EOL_STR);
444 svn_stringbuf_appendcstr(default_msg, "> Relnotes: Set to 'yes' for mention in release notes." APR_EOL_STR);
445 svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR);
446 svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR);
447 svn_stringbuf_appendcstr(default_msg, "> Differential Revision: https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR);
448 svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
449 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
454 svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool);
455 svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str));
457 /* Trim incoming messages of the EOF marker text and the junk
459 truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data,
461 cleanmsg(NULL, (char*)log_msg_buf->data);
463 /* Make a string from a stringbuf, sharing the data allocation. */
464 log_msg_str->data = log_msg_buf->data;
465 log_msg_str->len = log_msg_buf->len;
466 SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE,
467 log_msg_str, lmb->message_encoding,
469 _("Error normalizing log message to internal format"));
471 *log_msg = log_msg_str->data;
475 if (! commit_items->nelts)
483 /* We still don't have a valid commit message. Use $EDITOR to
484 get one. Note that svn_cl__edit_string_externally will still
485 return a UTF-8'ized log message. */
487 svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
488 svn_error_t *err = SVN_NO_ERROR;
489 svn_string_t *msg_string = svn_string_create_empty(pool);
491 for (i = 0; i < commit_items->nelts; i++)
493 svn_client_commit_item3_t *item
494 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
495 const char *path = item->path;
496 char text_mod = '_', prop_mod = ' ', unlock = ' ';
503 if (! svn_path_is_url(path) && lmb->base_dir)
504 path = svn_dirent_is_child(lmb->base_dir, path, pool);
506 /* If still no path, then just use current directory. */
510 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
511 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
513 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
515 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
517 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
520 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
523 if (! lmb->keep_locks
524 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
527 svn_stringbuf_appendbyte(tmp_message, text_mod);
528 svn_stringbuf_appendbyte(tmp_message, prop_mod);
529 svn_stringbuf_appendbyte(tmp_message, unlock);
530 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
531 /* History included via copy/move. */
532 svn_stringbuf_appendcstr(tmp_message, "+ ");
534 svn_stringbuf_appendcstr(tmp_message, " ");
535 svn_stringbuf_appendcstr(tmp_message, path);
536 svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
539 msg_string->data = tmp_message->data;
540 msg_string->len = tmp_message->len;
542 /* Use the external edit to get a log message. */
543 if (! lmb->non_interactive)
545 err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
546 lmb->editor_cmd, lmb->base_dir,
547 msg_string, "svn-commit",
549 lmb->message_encoding,
552 else /* non_interactive flag says we can't pop up an editor, so error */
554 return svn_error_create
555 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
556 _("Cannot invoke editor to get log message "
557 "when non-interactive"));
560 /* Dup the tmpfile path into its baton's pool. */
561 *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
564 /* If the edit returned an error, handle it. */
567 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
568 err = svn_error_quick_wrap
569 (err, _("Could not use external editor to fetch log message; "
570 "consider setting the $SVN_EDITOR environment variable "
571 "or using the --message (-m) or --file (-F) options"));
572 return svn_error_trace(err);
576 message = svn_stringbuf_create_from_string(msg_string, pool);
578 /* Strip the prefix from the buffer. */
580 truncate_buffer_at_prefix(&message->len, message->data,
583 * Since we're adding freebsd-specific tokens to the log message,
584 * clean out any leftovers to avoid accidently sending them to other
585 * projects that won't be expecting them.
588 cleanmsg(&message->len, message->data);
592 /* We did get message, now check if it is anything more than just
593 white space as we will consider white space only as empty */
596 for (len = 0; len < message->len; len++)
598 /* FIXME: should really use an UTF-8 whitespace test
599 rather than svn_ctype_isspace, which is ASCII only */
600 if (! svn_ctype_isspace(message->data[len]))
603 if (len == message->len)
610 SVN_ERR(svn_cmdline_prompt_user2
612 _("\nLog message unchanged or not specified\n"
613 "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
616 int letter = apr_tolower(reply[0]);
618 /* If the user chooses to abort, we cleanup the
619 temporary file and exit the loop with a NULL
623 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
624 *tmp_file = lmb->tmpfile_left = NULL;
628 /* If the user chooses to continue, we make an empty
629 message, which will cause us to exit the loop. We
630 also cleanup the temporary file. */
633 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
634 *tmp_file = lmb->tmpfile_left = NULL;
635 message = svn_stringbuf_create_empty(pool);
638 /* If the user chooses anything else, the loop will
639 continue on the NULL message. */
644 *log_msg = message ? message->data : NULL;
649 /* ### The way our error wrapping currently works, the error returned
650 * from here will look as though it originates in this source file,
651 * instead of in the caller's source file. This can be a bit
652 * misleading, until one starts debugging. Ideally, there'd be a way
653 * to wrap an error while preserving its FILE/LINE info.
656 svn_cl__may_need_force(svn_error_t *err)
659 && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
660 err->apr_err == SVN_ERR_CLIENT_MODIFIED))
662 /* Should this svn_error_compose a new error number? Probably not,
663 the error hasn't changed. */
664 err = svn_error_quick_wrap
665 (err, _("Use --force to override this restriction (local modifications "
669 return svn_error_trace(err);
674 svn_cl__error_checked_fputs(const char *string, FILE* stream)
676 /* On POSIX systems, errno will be set on an error in fputs, but this might
677 not be the case on other platforms. We reset errno and only
678 use it if it was set by the below fputs call. Else, we just return
682 if (fputs(string, stream) == EOF)
685 return svn_error_wrap_apr(errno, _("Write error"));
687 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
695 svn_cl__try(svn_error_t *err,
696 apr_array_header_t *errors_seen,
702 apr_status_t apr_err;
706 while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
711 svn_boolean_t add = TRUE;
713 /* Don't report duplicate error codes. */
714 for (i = 0; i < errors_seen->nelts; i++)
716 if (APR_ARRAY_IDX(errors_seen, i,
717 apr_status_t) == err->apr_err)
724 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
726 if (err->apr_err == apr_err)
729 svn_handle_warning2(stderr, err, "svn: ");
730 svn_error_clear(err);
737 return svn_error_trace(err);
742 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
749 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
751 svn_xml_escape_cdata_cstring(sb, string, pool);
752 svn_xml_make_close_tag(sb, pool, tagname);
758 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
759 svn_revnum_t revision,
765 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
767 apr_psprintf(pool, "%ld", revision), NULL);
769 /* "<author>xx</author>" */
771 svn_cl__xml_tagged_cdata(sb, pool, "author", author);
773 /* "<date>xx</date>" */
775 svn_cl__xml_tagged_cdata(sb, pool, "date", date);
778 svn_xml_make_close_tag(sb, pool, "commit");
783 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
784 const svn_lock_t *lock,
788 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL);
790 /* "<token>xx</token>" */
791 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
793 /* "<owner>xx</owner>" */
794 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
796 /* "<comment>xx</comment>" */
797 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
799 /* "<created>xx</created>" */
800 svn_cl__xml_tagged_cdata(sb, pool, "created",
801 svn_time_to_cstring(lock->creation_date, pool));
803 /* "<expires>xx</expires>" */
804 if (lock->expiration_date != 0)
805 svn_cl__xml_tagged_cdata(sb, pool, "expires",
806 svn_time_to_cstring(lock->expiration_date, pool));
809 svn_xml_make_close_tag(sb, pool, "lock");
814 svn_cl__xml_print_header(const char *tagname,
817 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
819 /* <?xml version="1.0" encoding="UTF-8"?> */
820 svn_xml_make_header2(&sb, "UTF-8", pool);
823 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL);
825 return svn_cl__error_checked_fputs(sb->data, stdout);
830 svn_cl__xml_print_footer(const char *tagname,
833 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
836 svn_xml_make_close_tag(&sb, pool, tagname);
837 return svn_cl__error_checked_fputs(sb->data, stdout);
841 /* A map for svn_node_kind_t values to XML strings */
842 static const svn_token_map_t map_node_kind_xml[] =
844 { "none", svn_node_none },
845 { "file", svn_node_file },
846 { "dir", svn_node_dir },
847 { "", svn_node_unknown },
851 /* A map for svn_node_kind_t values to human-readable strings */
852 static const svn_token_map_t map_node_kind_human[] =
854 { N_("none"), svn_node_none },
855 { N_("file"), svn_node_file },
856 { N_("dir"), svn_node_dir },
857 { "", svn_node_unknown },
862 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
864 return svn_token__to_word(map_node_kind_xml, kind);
868 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
870 return _(svn_token__to_word(map_node_kind_human, kind));
874 /* A map for svn_wc_operation_t values to XML strings */
875 static const svn_token_map_t map_wc_operation_xml[] =
877 { "none", svn_wc_operation_none },
878 { "update", svn_wc_operation_update },
879 { "switch", svn_wc_operation_switch },
880 { "merge", svn_wc_operation_merge },
884 /* A map for svn_wc_operation_t values to human-readable strings */
885 static const svn_token_map_t map_wc_operation_human[] =
887 { N_("none"), svn_wc_operation_none },
888 { N_("update"), svn_wc_operation_update },
889 { N_("switch"), svn_wc_operation_switch },
890 { N_("merge"), svn_wc_operation_merge },
895 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
897 return svn_token__to_word(map_wc_operation_xml, operation);
901 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
904 return _(svn_token__to_word(map_wc_operation_human, operation));
909 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
911 const apr_array_header_t *known_targets,
912 svn_client_ctx_t *ctx,
913 svn_boolean_t keep_last_origpath_on_truepath_collision,
916 svn_error_t *err = svn_client_args_to_target_array2(targets,
920 keep_last_origpath_on_truepath_collision,
924 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
926 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
927 svn_error_clear(err);
930 return svn_error_trace(err);
936 /* Helper for svn_cl__get_changelist(); implements
937 svn_changelist_receiver_t. */
939 changelist_receiver(void *baton,
941 const char *changelist,
944 /* No need to check CHANGELIST; our caller only asked about one of them. */
945 apr_array_header_t *paths = baton;
946 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
952 svn_cl__changelist_paths(apr_array_header_t **paths,
953 const apr_array_header_t *changelists,
954 const apr_array_header_t *targets,
956 svn_client_ctx_t *ctx,
957 apr_pool_t *result_pool,
958 apr_pool_t *scratch_pool)
960 apr_array_header_t *found;
961 apr_hash_t *paths_hash;
962 apr_pool_t *iterpool;
965 if (! (changelists && changelists->nelts))
967 *paths = (apr_array_header_t *)targets;
971 found = apr_array_make(scratch_pool, 8, sizeof(const char *));
972 iterpool = svn_pool_create(scratch_pool);
973 for (i = 0; i < targets->nelts; i++)
975 const char *target = APR_ARRAY_IDX(targets, i, const char *);
976 svn_pool_clear(iterpool);
977 SVN_ERR(svn_client_get_changelists(target, changelists, depth,
978 changelist_receiver, found,
981 svn_pool_destroy(iterpool);
983 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
984 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
988 svn_cl__show_revs_from_word(const char *word)
990 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
991 return svn_cl__show_revs_merged;
992 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
993 return svn_cl__show_revs_eligible;
994 /* word is an invalid flavor. */
995 return svn_cl__show_revs_invalid;
1000 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
1007 err = svn_time_from_cstring(&when, data, pool);
1008 if (err && err->apr_err == SVN_ERR_BAD_DATE)
1010 svn_error_clear(err);
1012 *human_cstring = _("(invalid date)");
1013 return SVN_NO_ERROR;
1016 return svn_error_trace(err);
1018 *human_cstring = svn_time_to_human_cstring(when, pool);
1020 return SVN_NO_ERROR;
1024 svn_cl__node_description(const svn_wc_conflict_version_t *node,
1025 const char *wc_repos_root_URL,
1028 const char *root_str = "^";
1029 const char *path_str = "...";
1032 /* Printing "(none)" the harder way to ensure conformity (mostly with
1034 return apr_psprintf(pool, "(%s)",
1035 svn_cl__node_kind_str_human_readable(svn_node_none));
1037 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1038 * Otherwise show the complete URL, and if we can't, show dots. */
1040 if (node->repos_url &&
1041 (wc_repos_root_URL == NULL ||
1042 strcmp(node->repos_url, wc_repos_root_URL) != 0))
1043 root_str = node->repos_url;
1045 if (node->path_in_repos)
1046 path_str = node->path_in_repos;
1048 return apr_psprintf(pool, "(%s) %s@%ld",
1049 svn_cl__node_kind_str_human_readable(node->node_kind),
1050 svn_path_url_add_component2(root_str, path_str, pool),
1055 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1056 const apr_array_header_t *targets,
1060 apr_array_header_t *true_targets;
1062 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1064 for (i = 0; i < targets->nelts; i++)
1066 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1067 const char *true_target, *peg;
1069 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1071 if (peg[0] && peg[1])
1072 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1073 _("'%s': a peg revision is not allowed here"),
1075 APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1078 SVN_ERR_ASSERT(true_targets_p);
1079 *true_targets_p = true_targets;
1081 return SVN_NO_ERROR;
1085 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1089 err = svn_client__assert_homogeneous_target_type(targets);
1090 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1091 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1096 svn_cl__check_target_is_local_path(const char *target)
1098 if (svn_path_is_url(target))
1099 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1100 _("'%s' is not a local path"), target);
1101 return SVN_NO_ERROR;
1105 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1109 for (i = 0; i < targets->nelts; i++)
1111 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1113 SVN_ERR(svn_cl__check_target_is_local_path(target));
1115 return SVN_NO_ERROR;
1119 svn_cl__local_style_skip_ancestor(const char *parent_path,
1123 const char *relpath = NULL;
1126 relpath = svn_dirent_skip_ancestor(parent_path, path);
1128 return svn_dirent_local_style(relpath ? relpath : path, pool);
1132 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1133 const char *propname,
1134 const svn_string_t *propval,
1135 apr_pool_t *scratch_pool)
1137 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1139 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1142 for (i = 0; i < targets->nelts; i++)
1144 const char *detected_mimetype;
1145 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1146 const char *local_abspath;
1147 const svn_string_t *canon_propval;
1148 svn_node_kind_t node_kind;
1150 svn_pool_clear(iterpool);
1152 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1153 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1154 if (node_kind != svn_node_file)
1157 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1164 if (svn_mime_type_is_binary(canon_propval->data))
1166 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1167 local_abspath, NULL,
1169 if (detected_mimetype == NULL ||
1170 !svn_mime_type_is_binary(detected_mimetype))
1171 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1172 _("svn: warning: '%s' is a binary mime-type but file '%s' "
1173 "looks like text; diff, merge, blame, and other "
1174 "operations will stop working on this file\n"),
1175 canon_propval->data,
1176 svn_dirent_local_style(local_abspath, iterpool)));
1180 svn_pool_destroy(iterpool);
1183 return SVN_NO_ERROR;