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 /* Be very careful with returning errors from this callback as those
82 will be returned as errors from editor->close_edit(...), which may
83 cause callers to assume that the commit itself failed.
85 See log message of r1659867 and the svn_ra_get_commit_editor3
86 documentation for details on error scenarios. */
88 if (SVN_IS_VALID_REVNUM(commit_info->revision))
89 SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld%s.\n"),
90 commit_info->revision,
91 commit_info->revision == 42 &&
92 getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS")
93 ? _(" (the answer to life, the universe, "
97 /* Writing to stdout, as there maybe systems that consider the
98 * presence of stderr as an indication of commit failure.
99 * OTOH, this is only of informational nature to the user as
100 * the commit has succeeded. */
101 if (commit_info->post_commit_err)
102 SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"),
103 commit_info->post_commit_err));
110 svn_cl__merge_file_externally(const char *base_path,
111 const char *their_path,
113 const char *merged_path,
116 svn_boolean_t *remains_in_conflict,
120 /* Error if there is no editor specified */
121 if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS)
123 struct svn_config_t *cfg;
125 cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
126 /* apr_env_get wants char **, this wants const char ** */
127 svn_config_get(cfg, (const char **)&merge_tool,
128 SVN_CONFIG_SECTION_HELPERS,
129 SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL);
136 for (c = merge_tool; *c; c++)
137 if (!svn_ctype_isspace(*c))
141 return svn_error_create
142 (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
143 _("The SVN_MERGE environment variable is empty or "
144 "consists solely of whitespace. Expected a shell command.\n"));
147 return svn_error_create
148 (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
149 _("The environment variable SVN_MERGE and the merge-tool-cmd run-time "
150 "configuration option were not set.\n"));
153 const char *arguments[7] = { 0 };
157 apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
159 return svn_error_wrap_apr(status, NULL);
161 arguments[0] = merge_tool;
162 arguments[1] = base_path;
163 arguments[2] = their_path;
164 arguments[3] = my_path;
165 arguments[4] = merged_path;
166 arguments[5] = wc_path;
169 SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,
170 arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,
172 /* Exit code 0 means the merge was successful.
173 * Exit code 1 means the file was left in conflict but it
174 * is OK to continue with the merge.
175 * Any other exit code means there was a real problem. */
176 if (exitcode != 0 && exitcode != 1)
177 return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
178 _("The external merge tool '%s' exited with exit code %d."),
179 merge_tool, exitcode);
180 else if (remains_in_conflict)
181 *remains_in_conflict = exitcode == 1;
187 /* A svn_client_ctx_t's log_msg_baton3, for use with
188 svn_cl__make_log_msg_baton(). */
191 const char *editor_cmd; /* editor specified via --editor-cmd, else NULL */
192 const char *message; /* the message. */
193 const char *message_encoding; /* the locale/encoding of the message. */
194 const char *base_dir; /* the base directory for an external edit. UTF-8! */
195 const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */
196 svn_boolean_t non_interactive; /* if true, don't pop up an editor */
197 apr_hash_t *config; /* client configuration hash */
198 svn_boolean_t keep_locks; /* Keep repository locks? */
199 apr_pool_t *pool; /* a pool. */
204 svn_cl__make_log_msg_baton(void **baton,
205 svn_cl__opt_state_t *opt_state,
206 const char *base_dir /* UTF-8! */,
210 struct log_msg_baton *lmb = apr_pcalloc(pool, sizeof(*lmb));
212 if (opt_state->filedata)
214 if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
216 /* The data contains a zero byte, and therefore can't be
217 represented as a C string. Punt now; it's probably not
218 a deliberate encoding, and even if it is, we still
220 return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
221 _("Log message contains a zero byte"));
223 lmb->message = opt_state->filedata->data;
227 lmb->message = opt_state->message;
230 lmb->editor_cmd = opt_state->editor_cmd;
231 if (opt_state->encoding)
233 lmb->message_encoding = opt_state->encoding;
237 svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
238 svn_config_get(cfg, &(lmb->message_encoding),
239 SVN_CONFIG_SECTION_MISCELLANY,
240 SVN_CONFIG_OPTION_LOG_ENCODING,
244 lmb->message_encoding = NULL;
246 lmb->base_dir = base_dir;
247 lmb->tmpfile_left = NULL;
248 lmb->config = config;
249 lmb->keep_locks = opt_state->no_unlock;
250 lmb->non_interactive = opt_state->non_interactive;
258 svn_cl__cleanup_log_msg(void *log_msg_baton,
259 svn_error_t *commit_err,
262 struct log_msg_baton *lmb = log_msg_baton;
265 /* If there was no tmpfile left, or there is no log message baton,
266 return COMMIT_ERR. */
267 if ((! lmb) || (! lmb->tmpfile_left))
270 /* If there was no commit error, cleanup the tmpfile and return. */
272 return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool);
274 /* There was a commit error; there is a tmpfile. Leave the tmpfile
275 around, and add message about its presence to the commit error
276 chain. Then return COMMIT_ERR. If the conversion from UTF-8 to
277 native encoding fails, we have to compose that error with the
278 commit error chain, too. */
280 err = svn_error_createf(commit_err->apr_err, NULL,
282 svn_dirent_local_style(lmb->tmpfile_left, pool));
283 svn_error_compose(commit_err,
284 svn_error_create(commit_err->apr_err, err,
285 _("Your commit message was left in "
286 "a temporary file:")));
291 /* Remove line-starting PREFIX and everything after it from BUFFER.
292 If NEW_LEN is non-NULL, return the new length of BUFFER in
295 truncate_buffer_at_prefix(apr_size_t *new_len,
299 char *substring = buffer;
301 assert(buffer && prefix);
303 /* Initialize *NEW_LEN. */
305 *new_len = strlen(buffer);
309 /* Find PREFIX in BUFFER. */
310 substring = strstr(substring, prefix);
314 /* We found PREFIX. Is it really a PREFIX? Well, if it's the first
315 thing in the file, or if the character before it is a
316 line-terminator character, it sure is. */
317 if ((substring == buffer)
318 || (*(substring - 1) == '\r')
319 || (*(substring - 1) == '\n'))
323 *new_len = substring - buffer;
327 /* Well, it wasn't really a prefix, so just advance by 1
328 character and continue. */
338 * Since we're adding freebsd-specific tokens to the log message,
339 * clean out any leftovers to avoid accidently sending them to other
340 * projects that won't be expecting them.
343 static const char *prefixes[] = {
355 "Differential Revision:",
359 cleanmsg(apr_size_t *l, char *s)
367 for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
369 while ((kw = strstr(pos, prefixes[i])) != NULL) {
370 /* Check to see if keyword is at start of line (or buffer) */
371 if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
375 p = kw + strlen(prefixes[i]);
378 if (*p == ' ' || *p == '\t') {
382 if (*p == '\0' || *p == '\r' || *p == '\n')
387 if (empty && (*p == '\r' || *p == '\n')) {
388 memmove(kw, p + 1, strlen(p + 1) + 1);
402 #define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
405 svn_cl__get_log_message(const char **log_msg,
406 const char **tmp_file,
407 const apr_array_header_t *commit_items,
411 svn_stringbuf_t *default_msg = NULL;
412 struct log_msg_baton *lmb = baton;
413 svn_stringbuf_t *message = NULL;
415 const char *mfc_after, *sponsored_by;
417 cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
419 /* Set default message. */
420 default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
421 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
422 svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
423 svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
424 svn_stringbuf_appendcstr(default_msg, "Reported by:\t" APR_EOL_STR);
425 svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
426 svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
427 svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
428 svn_stringbuf_appendcstr(default_msg, "MFC after:\t");
429 svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL);
430 if (mfc_after != NULL)
431 svn_stringbuf_appendcstr(default_msg, mfc_after);
432 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
433 svn_stringbuf_appendcstr(default_msg, "MFH:\t\t" APR_EOL_STR);
434 svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
435 svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
436 svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t");
437 svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by",
438 #ifdef HAS_ORGANIZATION_NAME
443 if (sponsored_by != NULL)
444 svn_stringbuf_appendcstr(default_msg, sponsored_by);
445 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
446 svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR);
447 svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
448 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
449 svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR);
450 svn_stringbuf_appendcstr(default_msg, "> PR: If and which Problem Report is related." APR_EOL_STR);
451 svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR);
452 svn_stringbuf_appendcstr(default_msg, "> Reported by: If someone else reported the issue." APR_EOL_STR);
453 svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR);
454 svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR);
455 svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR);
456 svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR);
457 svn_stringbuf_appendcstr(default_msg, "> MFH: Ports tree branch name. Request approval for merge." APR_EOL_STR);
458 svn_stringbuf_appendcstr(default_msg, "> Relnotes: Set to 'yes' for mention in release notes." APR_EOL_STR);
459 svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR);
460 svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR);
461 svn_stringbuf_appendcstr(default_msg, "> Differential Revision: https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR);
462 svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
463 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
468 svn_string_t *log_msg_str = svn_string_create(lmb->message, pool);
470 SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, NULL, NULL,
471 log_msg_str, lmb->message_encoding,
473 _("Error normalizing log message to internal format"));
475 /* Strip off the EOF marker text and the junk that follows it. */
476 truncate_buffer_at_prefix(&(log_msg_str->len), (char *)log_msg_str->data,
479 cleanmsg(&(log_msg_str->len), (char*)log_msg_str->data);
481 *log_msg = log_msg_str->data;
485 if (! commit_items->nelts)
493 /* We still don't have a valid commit message. Use $EDITOR to
494 get one. Note that svn_cl__edit_string_externally will still
495 return a UTF-8'ized log message. */
497 svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
498 svn_error_t *err = SVN_NO_ERROR;
499 svn_string_t *msg_string = svn_string_create_empty(pool);
501 for (i = 0; i < commit_items->nelts; i++)
503 svn_client_commit_item3_t *item
504 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
505 const char *path = item->path;
506 char text_mod = '_', prop_mod = ' ', unlock = ' ';
510 else if (lmb->base_dir)
511 path = svn_dirent_is_child(lmb->base_dir, path, pool);
513 /* If still no path, then just use current directory. */
514 if (! path || !*path)
517 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
518 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
520 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
522 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
524 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
527 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
530 if (! lmb->keep_locks
531 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
534 svn_stringbuf_appendbyte(tmp_message, text_mod);
535 svn_stringbuf_appendbyte(tmp_message, prop_mod);
536 svn_stringbuf_appendbyte(tmp_message, unlock);
537 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
538 /* History included via copy/move. */
539 svn_stringbuf_appendcstr(tmp_message, "+ ");
541 svn_stringbuf_appendcstr(tmp_message, " ");
542 svn_stringbuf_appendcstr(tmp_message, path);
543 svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
546 msg_string->data = tmp_message->data;
547 msg_string->len = tmp_message->len;
549 /* Use the external edit to get a log message. */
550 if (! lmb->non_interactive)
552 err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
554 lmb->base_dir ? lmb->base_dir : "",
555 msg_string, "svn-commit",
557 lmb->message_encoding,
560 else /* non_interactive flag says we can't pop up an editor, so error */
562 return svn_error_create
563 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
564 _("Cannot invoke editor to get log message "
565 "when non-interactive"));
568 /* Dup the tmpfile path into its baton's pool. */
569 *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
572 /* If the edit returned an error, handle it. */
575 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
576 err = svn_error_quick_wrap
577 (err, _("Could not use external editor to fetch log message; "
578 "consider setting the $SVN_EDITOR environment variable "
579 "or using the --message (-m) or --file (-F) options"));
580 return svn_error_trace(err);
584 message = svn_stringbuf_create_from_string(msg_string, pool);
586 /* Strip off the EOF marker text and the junk that follows it. */
588 truncate_buffer_at_prefix(&message->len, message->data,
591 * Since we're adding freebsd-specific tokens to the log message,
592 * clean out any leftovers to avoid accidently sending them to other
593 * projects that won't be expecting them.
596 cleanmsg(&message->len, message->data);
600 /* We did get message, now check if it is anything more than just
601 white space as we will consider white space only as empty */
604 for (len = 0; len < message->len; len++)
606 /* FIXME: should really use an UTF-8 whitespace test
607 rather than svn_ctype_isspace, which is ASCII only */
608 if (! svn_ctype_isspace(message->data[len]))
611 if (len == message->len)
618 SVN_ERR(svn_cmdline_prompt_user2
620 _("\nLog message unchanged or not specified\n"
621 "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
624 int letter = apr_tolower(reply[0]);
626 /* If the user chooses to abort, we cleanup the
627 temporary file and exit the loop with a NULL
631 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
632 *tmp_file = lmb->tmpfile_left = NULL;
636 /* If the user chooses to continue, we make an empty
637 message, which will cause us to exit the loop. We
638 also cleanup the temporary file. */
641 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
642 *tmp_file = lmb->tmpfile_left = NULL;
643 message = svn_stringbuf_create_empty(pool);
646 /* If the user chooses anything else, the loop will
647 continue on the NULL message. */
652 *log_msg = message ? message->data : NULL;
657 /* ### The way our error wrapping currently works, the error returned
658 * from here will look as though it originates in this source file,
659 * instead of in the caller's source file. This can be a bit
660 * misleading, until one starts debugging. Ideally, there'd be a way
661 * to wrap an error while preserving its FILE/LINE info.
664 svn_cl__may_need_force(svn_error_t *err)
667 && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
668 err->apr_err == SVN_ERR_CLIENT_MODIFIED))
670 /* Should this svn_error_compose a new error number? Probably not,
671 the error hasn't changed. */
672 err = svn_error_quick_wrap
673 (err, _("Use --force to override this restriction (local modifications "
677 return svn_error_trace(err);
682 svn_cl__error_checked_fputs(const char *string, FILE* stream)
684 /* On POSIX systems, errno will be set on an error in fputs, but this might
685 not be the case on other platforms. We reset errno and only
686 use it if it was set by the below fputs call. Else, we just return
690 if (fputs(string, stream) == EOF)
692 if (apr_get_os_error()) /* is errno on POSIX */
693 return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
695 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
703 svn_cl__try(svn_error_t *err,
704 apr_array_header_t *errors_seen,
710 apr_status_t apr_err;
714 while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
719 svn_boolean_t add = TRUE;
721 /* Don't report duplicate error codes. */
722 for (i = 0; i < errors_seen->nelts; i++)
724 if (APR_ARRAY_IDX(errors_seen, i,
725 apr_status_t) == err->apr_err)
732 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
734 if (err->apr_err == apr_err)
737 svn_handle_warning2(stderr, err, "svn: ");
738 svn_error_clear(err);
745 return svn_error_trace(err);
750 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
757 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
758 tagname, SVN_VA_NULL);
759 svn_xml_escape_cdata_cstring(sb, string, pool);
760 svn_xml_make_close_tag(sb, pool, tagname);
766 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
767 svn_revnum_t revision,
773 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
775 apr_psprintf(pool, "%ld", revision), SVN_VA_NULL);
777 /* "<author>xx</author>" */
779 svn_cl__xml_tagged_cdata(sb, pool, "author", author);
781 /* "<date>xx</date>" */
783 svn_cl__xml_tagged_cdata(sb, pool, "date", date);
786 svn_xml_make_close_tag(sb, pool, "commit");
791 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
792 const svn_lock_t *lock,
796 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", SVN_VA_NULL);
798 /* "<token>xx</token>" */
799 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
801 /* "<owner>xx</owner>" */
802 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
804 /* "<comment>xx</comment>" */
805 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
807 /* "<created>xx</created>" */
808 svn_cl__xml_tagged_cdata(sb, pool, "created",
809 svn_time_to_cstring(lock->creation_date, pool));
811 /* "<expires>xx</expires>" */
812 if (lock->expiration_date != 0)
813 svn_cl__xml_tagged_cdata(sb, pool, "expires",
814 svn_time_to_cstring(lock->expiration_date, pool));
817 svn_xml_make_close_tag(sb, pool, "lock");
822 svn_cl__xml_print_header(const char *tagname,
825 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
827 /* <?xml version="1.0" encoding="UTF-8"?> */
828 svn_xml_make_header2(&sb, "UTF-8", pool);
831 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, SVN_VA_NULL);
833 return svn_cl__error_checked_fputs(sb->data, stdout);
838 svn_cl__xml_print_footer(const char *tagname,
841 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
844 svn_xml_make_close_tag(&sb, pool, tagname);
845 return svn_cl__error_checked_fputs(sb->data, stdout);
849 /* A map for svn_node_kind_t values to XML strings */
850 static const svn_token_map_t map_node_kind_xml[] =
852 { "none", svn_node_none },
853 { "file", svn_node_file },
854 { "dir", svn_node_dir },
855 { "", svn_node_unknown },
859 /* A map for svn_node_kind_t values to human-readable strings */
860 static const svn_token_map_t map_node_kind_human[] =
862 { N_("none"), svn_node_none },
863 { N_("file"), svn_node_file },
864 { N_("dir"), svn_node_dir },
865 { "", svn_node_unknown },
870 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
872 return svn_token__to_word(map_node_kind_xml, kind);
876 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
878 return _(svn_token__to_word(map_node_kind_human, kind));
882 /* A map for svn_wc_operation_t values to XML strings */
883 static const svn_token_map_t map_wc_operation_xml[] =
885 { "none", svn_wc_operation_none },
886 { "update", svn_wc_operation_update },
887 { "switch", svn_wc_operation_switch },
888 { "merge", svn_wc_operation_merge },
892 /* A map for svn_wc_operation_t values to human-readable strings */
893 static const svn_token_map_t map_wc_operation_human[] =
895 { N_("none"), svn_wc_operation_none },
896 { N_("update"), svn_wc_operation_update },
897 { N_("switch"), svn_wc_operation_switch },
898 { N_("merge"), svn_wc_operation_merge },
903 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
905 return svn_token__to_word(map_wc_operation_xml, operation);
909 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
912 return _(svn_token__to_word(map_wc_operation_human, operation));
917 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
919 const apr_array_header_t *known_targets,
920 svn_client_ctx_t *ctx,
921 svn_boolean_t keep_last_origpath_on_truepath_collision,
924 svn_error_t *err = svn_client_args_to_target_array2(targets,
928 keep_last_origpath_on_truepath_collision,
932 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
934 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
935 svn_error_clear(err);
938 return svn_error_trace(err);
944 /* Helper for svn_cl__get_changelist(); implements
945 svn_changelist_receiver_t. */
947 changelist_receiver(void *baton,
949 const char *changelist,
952 /* No need to check CHANGELIST; our caller only asked about one of them. */
953 apr_array_header_t *paths = baton;
954 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
960 svn_cl__changelist_paths(apr_array_header_t **paths,
961 const apr_array_header_t *changelists,
962 const apr_array_header_t *targets,
964 svn_client_ctx_t *ctx,
965 apr_pool_t *result_pool,
966 apr_pool_t *scratch_pool)
968 apr_array_header_t *found;
969 apr_hash_t *paths_hash;
970 apr_pool_t *iterpool;
973 if (! (changelists && changelists->nelts))
975 *paths = (apr_array_header_t *)targets;
979 found = apr_array_make(scratch_pool, 8, sizeof(const char *));
980 iterpool = svn_pool_create(scratch_pool);
981 for (i = 0; i < targets->nelts; i++)
983 const char *target = APR_ARRAY_IDX(targets, i, const char *);
984 svn_pool_clear(iterpool);
985 SVN_ERR(svn_client_get_changelists(target, changelists, depth,
986 changelist_receiver, found,
989 svn_pool_destroy(iterpool);
991 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
992 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
996 svn_cl__show_revs_from_word(const char *word)
998 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
999 return svn_cl__show_revs_merged;
1000 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
1001 return svn_cl__show_revs_eligible;
1002 /* word is an invalid flavor. */
1003 return svn_cl__show_revs_invalid;
1008 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
1015 err = svn_time_from_cstring(&when, data, pool);
1016 if (err && err->apr_err == SVN_ERR_BAD_DATE)
1018 svn_error_clear(err);
1020 *human_cstring = _("(invalid date)");
1021 return SVN_NO_ERROR;
1024 return svn_error_trace(err);
1026 *human_cstring = svn_time_to_human_cstring(when, pool);
1028 return SVN_NO_ERROR;
1032 svn_cl__node_description(const svn_wc_conflict_version_t *node,
1033 const char *wc_repos_root_URL,
1036 const char *root_str = "^";
1037 const char *path_str = "...";
1040 /* Printing "(none)" the harder way to ensure conformity (mostly with
1042 return apr_psprintf(pool, "(%s)",
1043 svn_cl__node_kind_str_human_readable(svn_node_none));
1045 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1046 * Otherwise show the complete URL, and if we can't, show dots. */
1048 if (node->repos_url &&
1049 (wc_repos_root_URL == NULL ||
1050 strcmp(node->repos_url, wc_repos_root_URL) != 0))
1051 root_str = node->repos_url;
1053 if (node->path_in_repos)
1054 path_str = node->path_in_repos;
1056 return apr_psprintf(pool, "(%s) %s@%ld",
1057 svn_cl__node_kind_str_human_readable(node->node_kind),
1058 svn_path_url_add_component2(root_str, path_str, pool),
1063 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1064 const apr_array_header_t *targets,
1068 apr_array_header_t *true_targets;
1070 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1072 for (i = 0; i < targets->nelts; i++)
1074 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1075 const char *true_target, *peg;
1077 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1079 if (peg[0] && peg[1])
1080 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1081 _("'%s': a peg revision is not allowed here"),
1083 APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1086 SVN_ERR_ASSERT(true_targets_p);
1087 *true_targets_p = true_targets;
1089 return SVN_NO_ERROR;
1093 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1097 err = svn_client__assert_homogeneous_target_type(targets);
1098 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1099 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1104 svn_cl__check_target_is_local_path(const char *target)
1106 if (svn_path_is_url(target))
1107 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1108 _("'%s' is not a local path"), target);
1109 return SVN_NO_ERROR;
1113 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1117 for (i = 0; i < targets->nelts; i++)
1119 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1121 SVN_ERR(svn_cl__check_target_is_local_path(target));
1123 return SVN_NO_ERROR;
1127 svn_cl__local_style_skip_ancestor(const char *parent_path,
1131 const char *relpath = NULL;
1134 relpath = svn_dirent_skip_ancestor(parent_path, path);
1136 return svn_dirent_local_style(relpath ? relpath : path, pool);
1140 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1141 const char *propname,
1142 const svn_string_t *propval,
1143 apr_pool_t *scratch_pool)
1145 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1147 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1150 for (i = 0; i < targets->nelts; i++)
1152 const char *detected_mimetype;
1153 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1154 const char *local_abspath;
1155 const svn_string_t *canon_propval;
1156 svn_node_kind_t node_kind;
1158 svn_pool_clear(iterpool);
1160 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1161 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1162 if (node_kind != svn_node_file)
1165 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1172 if (svn_mime_type_is_binary(canon_propval->data))
1174 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1175 local_abspath, NULL,
1177 if (detected_mimetype == NULL ||
1178 !svn_mime_type_is_binary(detected_mimetype))
1179 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1180 _("svn: warning: '%s' is a binary mime-type but file '%s' "
1181 "looks like text; diff, merge, blame, and other "
1182 "operations will stop working on this file\n"),
1183 canon_propval->data,
1184 svn_dirent_local_style(local_abspath, iterpool)));
1188 svn_pool_destroy(iterpool);
1191 return SVN_NO_ERROR;