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);
749 return svn_error_trace(err);
754 svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
761 svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
762 tagname, SVN_VA_NULL);
763 svn_xml_escape_cdata_cstring(sb, string, pool);
764 svn_xml_make_close_tag(sb, pool, tagname);
770 svn_cl__print_xml_commit(svn_stringbuf_t **sb,
771 svn_revnum_t revision,
777 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
779 apr_psprintf(pool, "%ld", revision), SVN_VA_NULL);
781 /* "<author>xx</author>" */
783 svn_cl__xml_tagged_cdata(sb, pool, "author", author);
785 /* "<date>xx</date>" */
787 svn_cl__xml_tagged_cdata(sb, pool, "date", date);
790 svn_xml_make_close_tag(sb, pool, "commit");
795 svn_cl__print_xml_lock(svn_stringbuf_t **sb,
796 const svn_lock_t *lock,
800 svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", SVN_VA_NULL);
802 /* "<token>xx</token>" */
803 svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
805 /* "<owner>xx</owner>" */
806 svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
808 /* "<comment>xx</comment>" */
809 svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
811 /* "<created>xx</created>" */
812 svn_cl__xml_tagged_cdata(sb, pool, "created",
813 svn_time_to_cstring(lock->creation_date, pool));
815 /* "<expires>xx</expires>" */
816 if (lock->expiration_date != 0)
817 svn_cl__xml_tagged_cdata(sb, pool, "expires",
818 svn_time_to_cstring(lock->expiration_date, pool));
821 svn_xml_make_close_tag(sb, pool, "lock");
826 svn_cl__xml_print_header(const char *tagname,
829 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
831 /* <?xml version="1.0" encoding="UTF-8"?> */
832 svn_xml_make_header2(&sb, "UTF-8", pool);
835 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, SVN_VA_NULL);
837 return svn_cl__error_checked_fputs(sb->data, stdout);
842 svn_cl__xml_print_footer(const char *tagname,
845 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
848 svn_xml_make_close_tag(&sb, pool, tagname);
849 return svn_cl__error_checked_fputs(sb->data, stdout);
853 /* A map for svn_node_kind_t values to XML strings */
854 static const svn_token_map_t map_node_kind_xml[] =
856 { "none", svn_node_none },
857 { "file", svn_node_file },
858 { "dir", svn_node_dir },
859 { "", svn_node_unknown },
863 /* A map for svn_node_kind_t values to human-readable strings */
864 static const svn_token_map_t map_node_kind_human[] =
866 { N_("none"), svn_node_none },
867 { N_("file"), svn_node_file },
868 { N_("dir"), svn_node_dir },
869 { "", svn_node_unknown },
874 svn_cl__node_kind_str_xml(svn_node_kind_t kind)
876 return svn_token__to_word(map_node_kind_xml, kind);
880 svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
882 return _(svn_token__to_word(map_node_kind_human, kind));
886 /* A map for svn_wc_operation_t values to XML strings */
887 static const svn_token_map_t map_wc_operation_xml[] =
889 { "none", svn_wc_operation_none },
890 { "update", svn_wc_operation_update },
891 { "switch", svn_wc_operation_switch },
892 { "merge", svn_wc_operation_merge },
896 /* A map for svn_wc_operation_t values to human-readable strings */
897 static const svn_token_map_t map_wc_operation_human[] =
899 { N_("none"), svn_wc_operation_none },
900 { N_("update"), svn_wc_operation_update },
901 { N_("switch"), svn_wc_operation_switch },
902 { N_("merge"), svn_wc_operation_merge },
907 svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
909 return svn_token__to_word(map_wc_operation_xml, operation);
913 svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
916 return _(svn_token__to_word(map_wc_operation_human, operation));
921 svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
923 const apr_array_header_t *known_targets,
924 svn_client_ctx_t *ctx,
925 svn_boolean_t keep_last_origpath_on_truepath_collision,
928 svn_error_t *err = svn_client_args_to_target_array2(targets,
932 keep_last_origpath_on_truepath_collision,
936 if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
938 svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
939 svn_error_clear(err);
942 return svn_error_trace(err);
948 /* Helper for svn_cl__get_changelist(); implements
949 svn_changelist_receiver_t. */
951 changelist_receiver(void *baton,
953 const char *changelist,
956 /* No need to check CHANGELIST; our caller only asked about one of them. */
957 apr_array_header_t *paths = baton;
958 APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
964 svn_cl__changelist_paths(apr_array_header_t **paths,
965 const apr_array_header_t *changelists,
966 const apr_array_header_t *targets,
968 svn_client_ctx_t *ctx,
969 apr_pool_t *result_pool,
970 apr_pool_t *scratch_pool)
972 apr_array_header_t *found;
973 apr_hash_t *paths_hash;
974 apr_pool_t *iterpool;
977 if (! (changelists && changelists->nelts))
979 *paths = (apr_array_header_t *)targets;
983 found = apr_array_make(scratch_pool, 8, sizeof(const char *));
984 iterpool = svn_pool_create(scratch_pool);
985 for (i = 0; i < targets->nelts; i++)
987 const char *target = APR_ARRAY_IDX(targets, i, const char *);
988 svn_pool_clear(iterpool);
989 SVN_ERR(svn_client_get_changelists(target, changelists, depth,
990 changelist_receiver, found,
993 svn_pool_destroy(iterpool);
995 SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
996 return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
1000 svn_cl__show_revs_from_word(const char *word)
1002 if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
1003 return svn_cl__show_revs_merged;
1004 if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
1005 return svn_cl__show_revs_eligible;
1006 /* word is an invalid flavor. */
1007 return svn_cl__show_revs_invalid;
1012 svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
1019 err = svn_time_from_cstring(&when, data, pool);
1020 if (err && err->apr_err == SVN_ERR_BAD_DATE)
1022 svn_error_clear(err);
1024 *human_cstring = _("(invalid date)");
1025 return SVN_NO_ERROR;
1028 return svn_error_trace(err);
1030 *human_cstring = svn_time_to_human_cstring(when, pool);
1032 return SVN_NO_ERROR;
1036 svn_cl__node_description(const char *repos_root_url,
1037 const char *repos_relpath,
1038 svn_revnum_t peg_rev,
1039 svn_node_kind_t node_kind,
1040 const char *wc_repos_root_URL,
1043 const char *root_str = "^";
1044 const char *path_str = "...";
1046 if (!repos_root_url || !repos_relpath || !SVN_IS_VALID_REVNUM(peg_rev))
1047 /* Printing "(none)" the harder way to ensure conformity (mostly with
1049 return apr_psprintf(pool, "(%s)",
1050 svn_cl__node_kind_str_human_readable(svn_node_none));
1052 /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
1053 * Otherwise show the complete URL, and if we can't, show dots. */
1055 if (repos_root_url &&
1056 (wc_repos_root_URL == NULL ||
1057 strcmp(repos_root_url, wc_repos_root_URL) != 0))
1058 root_str = repos_root_url;
1061 path_str = repos_relpath;
1063 return apr_psprintf(pool, "(%s) %s@%ld",
1064 svn_cl__node_kind_str_human_readable(node_kind),
1065 svn_path_url_add_component2(root_str, path_str, pool),
1070 svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
1071 const apr_array_header_t *targets,
1075 apr_array_header_t *true_targets;
1077 true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
1079 for (i = 0; i < targets->nelts; i++)
1081 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1082 const char *true_target, *peg;
1084 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
1086 if (peg[0] && peg[1])
1087 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1088 _("'%s': a peg revision is not allowed here"),
1090 APR_ARRAY_PUSH(true_targets, const char *) = true_target;
1093 SVN_ERR_ASSERT(true_targets_p);
1094 *true_targets_p = true_targets;
1096 return SVN_NO_ERROR;
1100 svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
1104 err = svn_client__assert_homogeneous_target_type(targets);
1105 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
1106 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
1111 svn_cl__check_target_is_local_path(const char *target)
1113 if (svn_path_is_url(target))
1114 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1115 _("'%s' is not a local path"), target);
1116 return SVN_NO_ERROR;
1120 svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
1124 for (i = 0; i < targets->nelts; i++)
1126 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1128 SVN_ERR(svn_cl__check_target_is_local_path(target));
1130 return SVN_NO_ERROR;
1134 svn_cl__local_style_skip_ancestor(const char *parent_path,
1138 const char *relpath = NULL;
1141 relpath = svn_dirent_skip_ancestor(parent_path, path);
1143 return svn_dirent_local_style(relpath ? relpath : path, pool);
1147 svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
1148 const char *propname,
1149 const svn_string_t *propval,
1150 apr_pool_t *scratch_pool)
1152 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
1154 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1157 for (i = 0; i < targets->nelts; i++)
1159 const char *detected_mimetype;
1160 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1161 const char *local_abspath;
1162 const svn_string_t *canon_propval;
1163 svn_node_kind_t node_kind;
1165 svn_pool_clear(iterpool);
1167 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1168 SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
1169 if (node_kind != svn_node_file)
1172 SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
1179 if (svn_mime_type_is_binary(canon_propval->data))
1181 SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
1182 local_abspath, NULL,
1184 if (detected_mimetype == NULL ||
1185 !svn_mime_type_is_binary(detected_mimetype))
1186 svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
1187 _("svn: warning: '%s' is a binary mime-type but file '%s' "
1188 "looks like text; diff, merge, blame, and other "
1189 "operations will stop working on this file\n"),
1190 canon_propval->data,
1191 svn_dirent_local_style(local_abspath, iterpool)));
1195 svn_pool_destroy(iterpool);
1198 return SVN_NO_ERROR;