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[] = {
356 "Differential Revision:",
360 cleanmsg(apr_size_t *l, char *s)
368 for (i = 0; i < sizeof(prefixes) / sizeof(prefixes[0]); i++) {
370 while ((kw = strstr(pos, prefixes[i])) != NULL) {
371 /* Check to see if keyword is at start of line (or buffer) */
372 if (!(kw == s || kw[-1] == '\r' || kw[-1] == '\n')) {
376 p = kw + strlen(prefixes[i]);
379 if (*p == ' ' || *p == '\t') {
383 if (*p == '\0' || *p == '\r' || *p == '\n')
388 if (empty && (*p == '\r' || *p == '\n')) {
389 memmove(kw, p + 1, strlen(p + 1) + 1);
403 #define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
406 svn_cl__get_log_message(const char **log_msg,
407 const char **tmp_file,
408 const apr_array_header_t *commit_items,
412 svn_stringbuf_t *default_msg = NULL;
413 struct log_msg_baton *lmb = baton;
414 svn_stringbuf_t *message = NULL;
416 const char *mfc_after, *sponsored_by;
418 cfg = lmb->config ? svn_hash_gets(lmb->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
420 /* Set default message. */
421 default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
422 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
423 svn_stringbuf_appendcstr(default_msg, "PR:\t\t" APR_EOL_STR);
424 svn_stringbuf_appendcstr(default_msg, "Submitted by:\t" APR_EOL_STR);
425 svn_stringbuf_appendcstr(default_msg, "Reported by:\t" APR_EOL_STR);
426 svn_stringbuf_appendcstr(default_msg, "Reviewed by:\t" APR_EOL_STR);
427 svn_stringbuf_appendcstr(default_msg, "Approved by:\t" APR_EOL_STR);
428 svn_stringbuf_appendcstr(default_msg, "Obtained from:\t" APR_EOL_STR);
429 svn_stringbuf_appendcstr(default_msg, "MFC after:\t");
430 svn_config_get(cfg, &mfc_after, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-mfc-after", NULL);
431 if (mfc_after != NULL)
432 svn_stringbuf_appendcstr(default_msg, mfc_after);
433 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
434 svn_stringbuf_appendcstr(default_msg, "MFH:\t\t" APR_EOL_STR);
435 svn_stringbuf_appendcstr(default_msg, "Relnotes:\t" APR_EOL_STR);
436 svn_stringbuf_appendcstr(default_msg, "Security:\t" APR_EOL_STR);
437 svn_stringbuf_appendcstr(default_msg, "Sponsored by:\t");
438 svn_config_get(cfg, &sponsored_by, SVN_CONFIG_SECTION_MISCELLANY, "freebsd-sponsored-by",
439 #ifdef HAS_ORGANIZATION_NAME
444 if (sponsored_by != NULL)
445 svn_stringbuf_appendcstr(default_msg, sponsored_by);
446 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
447 svn_stringbuf_appendcstr(default_msg, "Pull Request:\t" APR_EOL_STR);
448 svn_stringbuf_appendcstr(default_msg, "Differential Revision:\t" APR_EOL_STR);
449 svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
450 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
451 svn_stringbuf_appendcstr(default_msg, "> Description of fields to fill in above: 76 columns --|" APR_EOL_STR);
452 svn_stringbuf_appendcstr(default_msg, "> PR: If and which Problem Report is related." APR_EOL_STR);
453 svn_stringbuf_appendcstr(default_msg, "> Submitted by: If someone else sent in the change." APR_EOL_STR);
454 svn_stringbuf_appendcstr(default_msg, "> Reported by: If someone else reported the issue." APR_EOL_STR);
455 svn_stringbuf_appendcstr(default_msg, "> Reviewed by: If someone else reviewed your modification." APR_EOL_STR);
456 svn_stringbuf_appendcstr(default_msg, "> Approved by: If you needed approval for this commit." APR_EOL_STR);
457 svn_stringbuf_appendcstr(default_msg, "> Obtained from: If the change is from a third party." APR_EOL_STR);
458 svn_stringbuf_appendcstr(default_msg, "> MFC after: N [day[s]|week[s]|month[s]]. Request a reminder email." APR_EOL_STR);
459 svn_stringbuf_appendcstr(default_msg, "> MFH: Ports tree branch name. Request approval for merge." APR_EOL_STR);
460 svn_stringbuf_appendcstr(default_msg, "> Relnotes: Set to 'yes' for mention in release notes." APR_EOL_STR);
461 svn_stringbuf_appendcstr(default_msg, "> Security: Vulnerability reference (one per line) or description." APR_EOL_STR);
462 svn_stringbuf_appendcstr(default_msg, "> Sponsored by: If the change was sponsored by an organization." APR_EOL_STR);
463 svn_stringbuf_appendcstr(default_msg, "> Pull Request: https://github.com/freebsd/freebsd/pull/### (*full* GitHub URL needed)." APR_EOL_STR);
464 svn_stringbuf_appendcstr(default_msg, "> Differential Revision: https://reviews.freebsd.org/D### (*full* phabric URL needed)." APR_EOL_STR);
465 svn_stringbuf_appendcstr(default_msg, "> Empty fields above will be automatically removed." APR_EOL_STR);
466 svn_stringbuf_appendcstr(default_msg, APR_EOL_STR);
471 svn_string_t *log_msg_str = svn_string_create(lmb->message, pool);
473 SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, NULL, NULL,
474 log_msg_str, lmb->message_encoding,
476 _("Error normalizing log message to internal format"));
478 /* Strip off the EOF marker text and the junk that follows it. */
479 truncate_buffer_at_prefix(&(log_msg_str->len), (char *)log_msg_str->data,
482 cleanmsg(&(log_msg_str->len), (char*)log_msg_str->data);
484 *log_msg = log_msg_str->data;
488 if (! commit_items->nelts)
496 /* We still don't have a valid commit message. Use $EDITOR to
497 get one. Note that svn_cl__edit_string_externally will still
498 return a UTF-8'ized log message. */
500 svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
501 svn_error_t *err = SVN_NO_ERROR;
502 svn_string_t *msg_string = svn_string_create_empty(pool);
504 for (i = 0; i < commit_items->nelts; i++)
506 svn_client_commit_item3_t *item
507 = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
508 const char *path = item->path;
509 char text_mod = '_', prop_mod = ' ', unlock = ' ';
513 else if (lmb->base_dir)
514 path = svn_dirent_is_child(lmb->base_dir, path, pool);
516 /* If still no path, then just use current directory. */
517 if (! path || !*path)
520 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
521 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
523 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
525 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
527 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
530 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
533 if (! lmb->keep_locks
534 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
537 svn_stringbuf_appendbyte(tmp_message, text_mod);
538 svn_stringbuf_appendbyte(tmp_message, prop_mod);
539 svn_stringbuf_appendbyte(tmp_message, unlock);
540 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
541 /* History included via copy/move. */
542 svn_stringbuf_appendcstr(tmp_message, "+ ");
544 svn_stringbuf_appendcstr(tmp_message, " ");
545 svn_stringbuf_appendcstr(tmp_message, path);
546 svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
549 msg_string->data = tmp_message->data;
550 msg_string->len = tmp_message->len;
552 /* Use the external edit to get a log message. */
553 if (! lmb->non_interactive)
555 err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
557 lmb->base_dir ? lmb->base_dir : "",
558 msg_string, "svn-commit",
560 lmb->message_encoding,
563 else /* non_interactive flag says we can't pop up an editor, so error */
565 return svn_error_create
566 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
567 _("Cannot invoke editor to get log message "
568 "when non-interactive"));
571 /* Dup the tmpfile path into its baton's pool. */
572 *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
575 /* If the edit returned an error, handle it. */
578 if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
579 err = svn_error_quick_wrap
580 (err, _("Could not use external editor to fetch log message; "
581 "consider setting the $SVN_EDITOR environment variable "
582 "or using the --message (-m) or --file (-F) options"));
583 return svn_error_trace(err);
587 message = svn_stringbuf_create_from_string(msg_string, pool);
589 /* Strip off the EOF marker text and the junk that follows it. */
591 truncate_buffer_at_prefix(&message->len, message->data,
594 * Since we're adding freebsd-specific tokens to the log message,
595 * clean out any leftovers to avoid accidently sending them to other
596 * projects that won't be expecting them.
599 cleanmsg(&message->len, message->data);
603 /* We did get message, now check if it is anything more than just
604 white space as we will consider white space only as empty */
607 for (len = 0; len < message->len; len++)
609 /* FIXME: should really use an UTF-8 whitespace test
610 rather than svn_ctype_isspace, which is ASCII only */
611 if (! svn_ctype_isspace(message->data[len]))
614 if (len == message->len)
621 SVN_ERR(svn_cmdline_prompt_user2
623 _("\nLog message unchanged or not specified\n"
624 "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
627 int letter = apr_tolower(reply[0]);
629 /* If the user chooses to abort, we cleanup the
630 temporary file and exit the loop with a NULL
634 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
635 *tmp_file = lmb->tmpfile_left = NULL;
639 /* If the user chooses to continue, we make an empty
640 message, which will cause us to exit the loop. We
641 also cleanup the temporary file. */
644 SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
645 *tmp_file = lmb->tmpfile_left = NULL;
646 message = svn_stringbuf_create_empty(pool);
649 /* If the user chooses anything else, the loop will
650 continue on the NULL message. */
655 *log_msg = message ? message->data : NULL;
660 /* ### The way our error wrapping currently works, the error returned
661 * from here will look as though it originates in this source file,
662 * instead of in the caller's source file. This can be a bit
663 * misleading, until one starts debugging. Ideally, there'd be a way
664 * to wrap an error while preserving its FILE/LINE info.
667 svn_cl__may_need_force(svn_error_t *err)
670 && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
671 err->apr_err == SVN_ERR_CLIENT_MODIFIED))
673 /* Should this svn_error_compose a new error number? Probably not,
674 the error hasn't changed. */
675 err = svn_error_quick_wrap
676 (err, _("Use --force to override this restriction (local modifications "
680 return svn_error_trace(err);
685 svn_cl__error_checked_fputs(const char *string, FILE* stream)
687 /* On POSIX systems, errno will be set on an error in fputs, but this might
688 not be the case on other platforms. We reset errno and only
689 use it if it was set by the below fputs call. Else, we just return
693 if (fputs(string, stream) == EOF)
695 if (apr_get_os_error()) /* is errno on POSIX */
696 return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
698 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
706 svn_cl__try(svn_error_t *err,
707 apr_array_header_t *errors_seen,
713 apr_status_t apr_err;
717 while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
722 svn_boolean_t add = TRUE;
724 /* Don't report duplicate error codes. */
725 for (i = 0; i < errors_seen->nelts; i++)
727 if (APR_ARRAY_IDX(errors_seen, i,
728 apr_status_t) == err->apr_err)
735 APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
737 if (err->apr_err == apr_err)
740 svn_handle_warning2(stderr, err, "svn: ");
741 svn_error_clear(err);
748 return svn_error_trace(err);
753 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
760 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
761 tagname, SVN_VA_NULL);
762 svn_xml_escape_cdata_cstring(sb, string, pool);
763 svn_xml_make_close_tag(sb, pool, tagname);
769 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
770 svn_revnum_t revision,
776 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
778 apr_psprintf(pool, "%ld", revision), SVN_VA_NULL);
780 /* "<author>xx</author>" */
782 svn_cl__xml_tagged_cdata(sb, pool, "author", author);
784 /* "<date>xx</date>" */
786 svn_cl__xml_tagged_cdata(sb, pool, "date", date);
789 svn_xml_make_close_tag(sb, pool, "commit");
794 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
795 const svn_lock_t *lock,
799 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", SVN_VA_NULL);
801 /* "<token>xx</token>" */
802 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
804 /* "<owner>xx</owner>" */
805 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
807 /* "<comment>xx</comment>" */
808 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
810 /* "<created>xx</created>" */
811 svn_cl__xml_tagged_cdata(sb, pool, "created",
812 svn_time_to_cstring(lock->creation_date, pool));
814 /* "<expires>xx</expires>" */
815 if (lock->expiration_date != 0)
816 svn_cl__xml_tagged_cdata(sb, pool, "expires",
817 svn_time_to_cstring(lock->expiration_date, pool));
820 svn_xml_make_close_tag(sb, pool, "lock");
825 svn_cl__xml_print_header(const char *tagname,
828 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
830 /* <?xml version="1.0" encoding="UTF-8"?> */
831 svn_xml_make_header2(&sb, "UTF-8", pool);
834 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, SVN_VA_NULL);
836 return svn_cl__error_checked_fputs(sb->data, stdout);
841 svn_cl__xml_print_footer(const char *tagname,
844 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
847 svn_xml_make_close_tag(&sb, pool, tagname);
848 return svn_cl__error_checked_fputs(sb->data, stdout);
852 /* A map for svn_node_kind_t values to XML strings */
853 static const svn_token_map_t map_node_kind_xml[] =
855 { "none", svn_node_none },
856 { "file", svn_node_file },
857 { "dir", svn_node_dir },
858 { "", svn_node_unknown },
862 /* A map for svn_node_kind_t values to human-readable strings */
863 static const svn_token_map_t map_node_kind_human[] =
865 { N_("none"), svn_node_none },
866 { N_("file"), svn_node_file },
867 { N_("dir"), svn_node_dir },
868 { "", svn_node_unknown },
873 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
875 return svn_token__to_word(map_node_kind_xml, kind);
879 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
881 return _(svn_token__to_word(map_node_kind_human, kind));
885 /* A map for svn_wc_operation_t values to XML strings */
886 static const svn_token_map_t map_wc_operation_xml[] =
888 { "none", svn_wc_operation_none },
889 { "update", svn_wc_operation_update },
890 { "switch", svn_wc_operation_switch },
891 { "merge", svn_wc_operation_merge },
895 /* A map for svn_wc_operation_t values to human-readable strings */
896 static const svn_token_map_t map_wc_operation_human[] =
898 { N_("none"), svn_wc_operation_none },
899 { N_("update"), svn_wc_operation_update },
900 { N_("switch"), svn_wc_operation_switch },
901 { N_("merge"), svn_wc_operation_merge },
906 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
908 return svn_token__to_word(map_wc_operation_xml, operation);
912 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
915 return _(svn_token__to_word(map_wc_operation_human, operation));
920 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
922 const apr_array_header_t *known_targets,
923 svn_client_ctx_t *ctx,
924 svn_boolean_t keep_last_origpath_on_truepath_collision,
927 svn_error_t *err = svn_client_args_to_target_array2(targets,
931 keep_last_origpath_on_truepath_collision,
935 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
937 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
938 svn_error_clear(err);
941 return svn_error_trace(err);
947 /* Helper for svn_cl__get_changelist(); implements
948 svn_changelist_receiver_t. */
950 changelist_receiver(void *baton,
952 const char *changelist,
955 /* No need to check CHANGELIST; our caller only asked about one of them. */
956 apr_array_header_t *paths = baton;
957 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
963 svn_cl__changelist_paths(apr_array_header_t **paths,
964 const apr_array_header_t *changelists,
965 const apr_array_header_t *targets,
967 svn_client_ctx_t *ctx,
968 apr_pool_t *result_pool,
969 apr_pool_t *scratch_pool)
971 apr_array_header_t *found;
972 apr_hash_t *paths_hash;
973 apr_pool_t *iterpool;
976 if (! (changelists && changelists->nelts))
978 *paths = (apr_array_header_t *)targets;
982 found = apr_array_make(scratch_pool, 8, sizeof(const char *));
983 iterpool = svn_pool_create(scratch_pool);
984 for (i = 0; i < targets->nelts; i++)
986 const char *target = APR_ARRAY_IDX(targets, i, const char *);
987 svn_pool_clear(iterpool);
988 SVN_ERR(svn_client_get_changelists(target, changelists, depth,
989 changelist_receiver, found,
992 svn_pool_destroy(iterpool);
994 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
995 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
999 svn_cl__show_revs_from_word(const char *word)
1001 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
1002 return svn_cl__show_revs_merged;
1003 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
1004 return svn_cl__show_revs_eligible;
1005 /* word is an invalid flavor. */
1006 return svn_cl__show_revs_invalid;
1011 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
1018 err = svn_time_from_cstring(&when, data, pool);
1019 if (err && err->apr_err == SVN_ERR_BAD_DATE)
1021 svn_error_clear(err);
1023 *human_cstring = _("(invalid date)");
1024 return SVN_NO_ERROR;
1027 return svn_error_trace(err);
1029 *human_cstring = svn_time_to_human_cstring(when, pool);
1031 return SVN_NO_ERROR;
1035 svn_cl__node_description(const svn_wc_conflict_version_t *node,
1036 const char *wc_repos_root_URL,
1039 const char *root_str = "^";
1040 const char *path_str = "...";
1043 /* Printing "(none)" the harder way to ensure conformity (mostly with
1045 return apr_psprintf(pool, "(%s)",
1046 svn_cl__node_kind_str_human_readable(svn_node_none));
1048 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1049 * Otherwise show the complete URL, and if we can't, show dots. */
1051 if (node->repos_url &&
1052 (wc_repos_root_URL == NULL ||
1053 strcmp(node->repos_url, wc_repos_root_URL) != 0))
1054 root_str = node->repos_url;
1056 if (node->path_in_repos)
1057 path_str = node->path_in_repos;
1059 return apr_psprintf(pool, "(%s) %s@%ld",
1060 svn_cl__node_kind_str_human_readable(node->node_kind),
1061 svn_path_url_add_component2(root_str, path_str, pool),
1066 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1067 const apr_array_header_t *targets,
1071 apr_array_header_t *true_targets;
1073 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1075 for (i = 0; i < targets->nelts; i++)
1077 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1078 const char *true_target, *peg;
1080 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1082 if (peg[0] && peg[1])
1083 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1084 _("'%s': a peg revision is not allowed here"),
1086 APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1089 SVN_ERR_ASSERT(true_targets_p);
1090 *true_targets_p = true_targets;
1092 return SVN_NO_ERROR;
1096 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1100 err = svn_client__assert_homogeneous_target_type(targets);
1101 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1102 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1107 svn_cl__check_target_is_local_path(const char *target)
1109 if (svn_path_is_url(target))
1110 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1111 _("'%s' is not a local path"), target);
1112 return SVN_NO_ERROR;
1116 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1120 for (i = 0; i < targets->nelts; i++)
1122 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1124 SVN_ERR(svn_cl__check_target_is_local_path(target));
1126 return SVN_NO_ERROR;
1130 svn_cl__local_style_skip_ancestor(const char *parent_path,
1134 const char *relpath = NULL;
1137 relpath = svn_dirent_skip_ancestor(parent_path, path);
1139 return svn_dirent_local_style(relpath ? relpath : path, pool);
1143 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1144 const char *propname,
1145 const svn_string_t *propval,
1146 apr_pool_t *scratch_pool)
1148 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1150 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1153 for (i = 0; i < targets->nelts; i++)
1155 const char *detected_mimetype;
1156 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1157 const char *local_abspath;
1158 const svn_string_t *canon_propval;
1159 svn_node_kind_t node_kind;
1161 svn_pool_clear(iterpool);
1163 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1164 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1165 if (node_kind != svn_node_file)
1168 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1175 if (svn_mime_type_is_binary(canon_propval->data))
1177 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1178 local_abspath, NULL,
1180 if (detected_mimetype == NULL ||
1181 !svn_mime_type_is_binary(detected_mimetype))
1182 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1183 _("svn: warning: '%s' is a binary mime-type but file '%s' "
1184 "looks like text; diff, merge, blame, and other "
1185 "operations will stop working on this file\n"),
1186 canon_propval->data,
1187 svn_dirent_local_style(local_abspath, iterpool)));
1191 svn_pool_destroy(iterpool);
1194 return SVN_NO_ERROR;