summaryrefslogtreecommitdiffstats
path: root/subversion/svn/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/util.c')
-rw-r--r--subversion/svn/util.c1109
1 files changed, 1109 insertions, 0 deletions
diff --git a/subversion/svn/util.c b/subversion/svn/util.c
new file mode 100644
index 0000000..5d386f8
--- /dev/null
+++ b/subversion/svn/util.c
@@ -0,0 +1,1109 @@
+/*
+ * util.c: Subversion command line client utility functions. Any
+ * functions that need to be shared across subcommands should be put
+ * in here.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <apr_env.h>
+#include <apr_errno.h>
+#include <apr_file_info.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_ctype.h"
+#include "svn_client.h"
+#include "svn_cmdline.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_config.h"
+#include "svn_wc.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "svn_props.h"
+#include "svn_private_config.h"
+#include "cl.h"
+
+#include "private/svn_token.h"
+#include "private/svn_opt_private.h"
+#include "private/svn_client_private.h"
+#include "private/svn_cmdline_private.h"
+#include "private/svn_string_private.h"
+
+
+
+
+svn_error_t *
+svn_cl__print_commit_info(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ if (SVN_IS_VALID_REVNUM(commit_info->revision))
+ SVN_ERR(svn_cmdline_printf(pool, _("\nCommitted revision %ld%s.\n"),
+ commit_info->revision,
+ commit_info->revision == 42 &&
+ getenv("SVN_I_LOVE_PANGALACTIC_GARGLE_BLASTERS")
+ ? _(" (the answer to life, the universe, "
+ "and everything)")
+ : ""));
+
+ /* Writing to stdout, as there maybe systems that consider the
+ * presence of stderr as an indication of commit failure.
+ * OTOH, this is only of informational nature to the user as
+ * the commit has succeeded. */
+ if (commit_info->post_commit_err)
+ SVN_ERR(svn_cmdline_printf(pool, _("\nWarning: %s\n"),
+ commit_info->post_commit_err));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__merge_file_externally(const char *base_path,
+ const char *their_path,
+ const char *my_path,
+ const char *merged_path,
+ const char *wc_path,
+ apr_hash_t *config,
+ svn_boolean_t *remains_in_conflict,
+ apr_pool_t *pool)
+{
+ char *merge_tool;
+ /* Error if there is no editor specified */
+ if (apr_env_get(&merge_tool, "SVN_MERGE", pool) != APR_SUCCESS)
+ {
+ struct svn_config_t *cfg;
+ merge_tool = NULL;
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
+ /* apr_env_get wants char **, this wants const char ** */
+ svn_config_get(cfg, (const char **)&merge_tool,
+ SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_MERGE_TOOL_CMD, NULL);
+ }
+
+ if (merge_tool)
+ {
+ const char *c;
+
+ for (c = merge_tool; *c; c++)
+ if (!svn_ctype_isspace(*c))
+ break;
+
+ if (! *c)
+ return svn_error_create
+ (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
+ _("The SVN_MERGE environment variable is empty or "
+ "consists solely of whitespace. Expected a shell command.\n"));
+ }
+ else
+ return svn_error_create
+ (SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL, NULL,
+ _("The environment variable SVN_MERGE and the merge-tool-cmd run-time "
+ "configuration option were not set.\n"));
+
+ {
+ const char *arguments[7] = { 0 };
+ char *cwd;
+ int exitcode;
+
+ apr_status_t status = apr_filepath_get(&cwd, APR_FILEPATH_NATIVE, pool);
+ if (status != 0)
+ return svn_error_wrap_apr(status, NULL);
+
+ arguments[0] = merge_tool;
+ arguments[1] = base_path;
+ arguments[2] = their_path;
+ arguments[3] = my_path;
+ arguments[4] = merged_path;
+ arguments[5] = wc_path;
+ arguments[6] = NULL;
+
+ SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,
+ arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,
+ pool));
+ /* Exit code 0 means the merge was successful.
+ * Exit code 1 means the file was left in conflict but it
+ * is OK to continue with the merge.
+ * Any other exit code means there was a real problem. */
+ if (exitcode != 0 && exitcode != 1)
+ return svn_error_createf
+ (SVN_ERR_EXTERNAL_PROGRAM, NULL,
+ _("The external merge tool exited with exit code %d"), exitcode);
+ else if (remains_in_conflict)
+ *remains_in_conflict = exitcode == 1;
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* A svn_client_ctx_t's log_msg_baton3, for use with
+ svn_cl__make_log_msg_baton(). */
+struct log_msg_baton
+{
+ const char *editor_cmd; /* editor specified via --editor-cmd, else NULL */
+ const char *message; /* the message. */
+ const char *message_encoding; /* the locale/encoding of the message. */
+ const char *base_dir; /* the base directory for an external edit. UTF-8! */
+ const char *tmpfile_left; /* the tmpfile left by an external edit. UTF-8! */
+ svn_boolean_t non_interactive; /* if true, don't pop up an editor */
+ apr_hash_t *config; /* client configuration hash */
+ svn_boolean_t keep_locks; /* Keep repository locks? */
+ apr_pool_t *pool; /* a pool. */
+};
+
+
+svn_error_t *
+svn_cl__make_log_msg_baton(void **baton,
+ svn_cl__opt_state_t *opt_state,
+ const char *base_dir /* UTF-8! */,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ struct log_msg_baton *lmb = apr_palloc(pool, sizeof(*lmb));
+
+ if (opt_state->filedata)
+ {
+ if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
+ {
+ /* The data contains a zero byte, and therefore can't be
+ represented as a C string. Punt now; it's probably not
+ a deliberate encoding, and even if it is, we still
+ can't handle it. */
+ return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
+ _("Log message contains a zero byte"));
+ }
+ lmb->message = opt_state->filedata->data;
+ }
+ else
+ {
+ lmb->message = opt_state->message;
+ }
+
+ lmb->editor_cmd = opt_state->editor_cmd;
+ if (opt_state->encoding)
+ {
+ lmb->message_encoding = opt_state->encoding;
+ }
+ else if (config)
+ {
+ svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
+ svn_config_get(cfg, &(lmb->message_encoding),
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_LOG_ENCODING,
+ NULL);
+ }
+
+ lmb->base_dir = base_dir ? base_dir : "";
+ lmb->tmpfile_left = NULL;
+ lmb->config = config;
+ lmb->keep_locks = opt_state->no_unlock;
+ lmb->non_interactive = opt_state->non_interactive;
+ lmb->pool = pool;
+ *baton = lmb;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__cleanup_log_msg(void *log_msg_baton,
+ svn_error_t *commit_err,
+ apr_pool_t *pool)
+{
+ struct log_msg_baton *lmb = log_msg_baton;
+ svn_error_t *err;
+
+ /* If there was no tmpfile left, or there is no log message baton,
+ return COMMIT_ERR. */
+ if ((! lmb) || (! lmb->tmpfile_left))
+ return commit_err;
+
+ /* If there was no commit error, cleanup the tmpfile and return. */
+ if (! commit_err)
+ return svn_io_remove_file2(lmb->tmpfile_left, FALSE, lmb->pool);
+
+ /* There was a commit error; there is a tmpfile. Leave the tmpfile
+ around, and add message about its presence to the commit error
+ chain. Then return COMMIT_ERR. If the conversion from UTF-8 to
+ native encoding fails, we have to compose that error with the
+ commit error chain, too. */
+
+ err = svn_error_createf(commit_err->apr_err, NULL,
+ _(" '%s'"),
+ svn_dirent_local_style(lmb->tmpfile_left, pool));
+ svn_error_compose(commit_err,
+ svn_error_create(commit_err->apr_err, err,
+ _("Your commit message was left in "
+ "a temporary file:")));
+ return commit_err;
+}
+
+
+/* Remove line-starting PREFIX and everything after it from BUFFER.
+ If NEW_LEN is non-NULL, return the new length of BUFFER in
+ *NEW_LEN. */
+static void
+truncate_buffer_at_prefix(apr_size_t *new_len,
+ char *buffer,
+ const char *prefix)
+{
+ char *substring = buffer;
+
+ assert(buffer && prefix);
+
+ /* Initialize *NEW_LEN. */
+ if (new_len)
+ *new_len = strlen(buffer);
+
+ while (1)
+ {
+ /* Find PREFIX in BUFFER. */
+ substring = strstr(substring, prefix);
+ if (! substring)
+ return;
+
+ /* We found PREFIX. Is it really a PREFIX? Well, if it's the first
+ thing in the file, or if the character before it is a
+ line-terminator character, it sure is. */
+ if ((substring == buffer)
+ || (*(substring - 1) == '\r')
+ || (*(substring - 1) == '\n'))
+ {
+ *substring = '\0';
+ if (new_len)
+ *new_len = substring - buffer;
+ }
+ else if (substring)
+ {
+ /* Well, it wasn't really a prefix, so just advance by 1
+ character and continue. */
+ substring++;
+ }
+ }
+
+ /* NOTREACHED */
+}
+
+
+#define EDITOR_EOF_PREFIX _("--This line, and those below, will be ignored--")
+
+svn_error_t *
+svn_cl__get_log_message(const char **log_msg,
+ const char **tmp_file,
+ const apr_array_header_t *commit_items,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *default_msg = NULL;
+ struct log_msg_baton *lmb = baton;
+ svn_stringbuf_t *message = NULL;
+
+ /* Set default message. */
+ default_msg = svn_stringbuf_create(APR_EOL_STR, pool);
+ svn_stringbuf_appendcstr(default_msg, EDITOR_EOF_PREFIX);
+ svn_stringbuf_appendcstr(default_msg, APR_EOL_STR APR_EOL_STR);
+
+ *tmp_file = NULL;
+ if (lmb->message)
+ {
+ svn_stringbuf_t *log_msg_buf = svn_stringbuf_create(lmb->message, pool);
+ svn_string_t *log_msg_str = apr_pcalloc(pool, sizeof(*log_msg_str));
+
+ /* Trim incoming messages of the EOF marker text and the junk
+ that follows it. */
+ truncate_buffer_at_prefix(&(log_msg_buf->len), log_msg_buf->data,
+ EDITOR_EOF_PREFIX);
+
+ /* Make a string from a stringbuf, sharing the data allocation. */
+ log_msg_str->data = log_msg_buf->data;
+ log_msg_str->len = log_msg_buf->len;
+ SVN_ERR_W(svn_subst_translate_string2(&log_msg_str, FALSE, FALSE,
+ log_msg_str, lmb->message_encoding,
+ FALSE, pool, pool),
+ _("Error normalizing log message to internal format"));
+
+ *log_msg = log_msg_str->data;
+ return SVN_NO_ERROR;
+ }
+
+ if (! commit_items->nelts)
+ {
+ *log_msg = "";
+ return SVN_NO_ERROR;
+ }
+
+ while (! message)
+ {
+ /* We still don't have a valid commit message. Use $EDITOR to
+ get one. Note that svn_cl__edit_string_externally will still
+ return a UTF-8'ized log message. */
+ int i;
+ svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_string_t *msg_string = svn_string_create_empty(pool);
+
+ for (i = 0; i < commit_items->nelts; i++)
+ {
+ svn_client_commit_item3_t *item
+ = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
+ const char *path = item->path;
+ char text_mod = '_', prop_mod = ' ', unlock = ' ';
+
+ if (! path)
+ path = item->url;
+ else if (! *path)
+ path = ".";
+
+ if (! svn_path_is_url(path) && lmb->base_dir)
+ path = svn_dirent_is_child(lmb->base_dir, path, pool);
+
+ /* If still no path, then just use current directory. */
+ if (! path)
+ path = ".";
+
+ if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
+ text_mod = 'R';
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
+ text_mod = 'A';
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+ text_mod = 'D';
+ else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
+ text_mod = 'M';
+
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
+ prop_mod = 'M';
+
+ if (! lmb->keep_locks
+ && item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
+ unlock = 'U';
+
+ svn_stringbuf_appendbyte(tmp_message, text_mod);
+ svn_stringbuf_appendbyte(tmp_message, prop_mod);
+ svn_stringbuf_appendbyte(tmp_message, unlock);
+ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
+ /* History included via copy/move. */
+ svn_stringbuf_appendcstr(tmp_message, "+ ");
+ else
+ svn_stringbuf_appendcstr(tmp_message, " ");
+ svn_stringbuf_appendcstr(tmp_message, path);
+ svn_stringbuf_appendcstr(tmp_message, APR_EOL_STR);
+ }
+
+ msg_string->data = tmp_message->data;
+ msg_string->len = tmp_message->len;
+
+ /* Use the external edit to get a log message. */
+ if (! lmb->non_interactive)
+ {
+ err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
+ lmb->editor_cmd, lmb->base_dir,
+ msg_string, "svn-commit",
+ lmb->config, TRUE,
+ lmb->message_encoding,
+ pool);
+ }
+ else /* non_interactive flag says we can't pop up an editor, so error */
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Cannot invoke editor to get log message "
+ "when non-interactive"));
+ }
+
+ /* Dup the tmpfile path into its baton's pool. */
+ *tmp_file = lmb->tmpfile_left = apr_pstrdup(lmb->pool,
+ lmb->tmpfile_left);
+
+ /* If the edit returned an error, handle it. */
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)
+ err = svn_error_quick_wrap
+ (err, _("Could not use external editor to fetch log message; "
+ "consider setting the $SVN_EDITOR environment variable "
+ "or using the --message (-m) or --file (-F) options"));
+ return svn_error_trace(err);
+ }
+
+ if (msg_string)
+ message = svn_stringbuf_create_from_string(msg_string, pool);
+
+ /* Strip the prefix from the buffer. */
+ if (message)
+ truncate_buffer_at_prefix(&message->len, message->data,
+ EDITOR_EOF_PREFIX);
+
+ if (message)
+ {
+ /* We did get message, now check if it is anything more than just
+ white space as we will consider white space only as empty */
+ apr_size_t len;
+
+ for (len = 0; len < message->len; len++)
+ {
+ /* FIXME: should really use an UTF-8 whitespace test
+ rather than svn_ctype_isspace, which is ASCII only */
+ if (! svn_ctype_isspace(message->data[len]))
+ break;
+ }
+ if (len == message->len)
+ message = NULL;
+ }
+
+ if (! message)
+ {
+ const char *reply;
+ SVN_ERR(svn_cmdline_prompt_user2
+ (&reply,
+ _("\nLog message unchanged or not specified\n"
+ "(a)bort, (c)ontinue, (e)dit:\n"), NULL, pool));
+ if (reply)
+ {
+ int letter = apr_tolower(reply[0]);
+
+ /* If the user chooses to abort, we cleanup the
+ temporary file and exit the loop with a NULL
+ message. */
+ if ('a' == letter)
+ {
+ SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
+ *tmp_file = lmb->tmpfile_left = NULL;
+ break;
+ }
+
+ /* If the user chooses to continue, we make an empty
+ message, which will cause us to exit the loop. We
+ also cleanup the temporary file. */
+ if ('c' == letter)
+ {
+ SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
+ *tmp_file = lmb->tmpfile_left = NULL;
+ message = svn_stringbuf_create_empty(pool);
+ }
+
+ /* If the user chooses anything else, the loop will
+ continue on the NULL message. */
+ }
+ }
+ }
+
+ *log_msg = message ? message->data : NULL;
+ return SVN_NO_ERROR;
+}
+
+
+/* ### The way our error wrapping currently works, the error returned
+ * from here will look as though it originates in this source file,
+ * instead of in the caller's source file. This can be a bit
+ * misleading, until one starts debugging. Ideally, there'd be a way
+ * to wrap an error while preserving its FILE/LINE info.
+ */
+svn_error_t *
+svn_cl__may_need_force(svn_error_t *err)
+{
+ if (err
+ && (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE ||
+ err->apr_err == SVN_ERR_CLIENT_MODIFIED))
+ {
+ /* Should this svn_error_compose a new error number? Probably not,
+ the error hasn't changed. */
+ err = svn_error_quick_wrap
+ (err, _("Use --force to override this restriction (local modifications "
+ "may be lost)"));
+ }
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_cl__error_checked_fputs(const char *string, FILE* stream)
+{
+ /* On POSIX systems, errno will be set on an error in fputs, but this might
+ not be the case on other platforms. We reset errno and only
+ use it if it was set by the below fputs call. Else, we just return
+ a generic error. */
+ errno = 0;
+
+ if (fputs(string, stream) == EOF)
+ {
+ if (errno)
+ return svn_error_wrap_apr(errno, _("Write error"));
+ else
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__try(svn_error_t *err,
+ apr_array_header_t *errors_seen,
+ svn_boolean_t quiet,
+ ...)
+{
+ if (err)
+ {
+ apr_status_t apr_err;
+ va_list ap;
+
+ va_start(ap, quiet);
+ while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
+ {
+ if (errors_seen)
+ {
+ int i;
+ svn_boolean_t add = TRUE;
+
+ /* Don't report duplicate error codes. */
+ for (i = 0; i < errors_seen->nelts; i++)
+ {
+ if (APR_ARRAY_IDX(errors_seen, i,
+ apr_status_t) == err->apr_err)
+ {
+ add = FALSE;
+ break;
+ }
+ }
+ if (add)
+ APR_ARRAY_PUSH(errors_seen, apr_status_t) = err->apr_err;
+ }
+ if (err->apr_err == apr_err)
+ {
+ if (! quiet)
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ }
+ va_end(ap);
+ }
+
+ return svn_error_trace(err);
+}
+
+
+void
+svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
+ apr_pool_t *pool,
+ const char *tagname,
+ const char *string)
+{
+ if (string)
+ {
+ svn_xml_make_open_tag(sb, pool, svn_xml_protect_pcdata,
+ tagname, NULL);
+ svn_xml_escape_cdata_cstring(sb, string, pool);
+ svn_xml_make_close_tag(sb, pool, tagname);
+ }
+}
+
+
+void
+svn_cl__print_xml_commit(svn_stringbuf_t **sb,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ apr_pool_t *pool)
+{
+ /* "<commit ...>" */
+ svn_xml_make_open_tag(sb, pool, svn_xml_normal, "commit",
+ "revision",
+ apr_psprintf(pool, "%ld", revision), NULL);
+
+ /* "<author>xx</author>" */
+ if (author)
+ svn_cl__xml_tagged_cdata(sb, pool, "author", author);
+
+ /* "<date>xx</date>" */
+ if (date)
+ svn_cl__xml_tagged_cdata(sb, pool, "date", date);
+
+ /* "</commit>" */
+ svn_xml_make_close_tag(sb, pool, "commit");
+}
+
+
+void
+svn_cl__print_xml_lock(svn_stringbuf_t **sb,
+ const svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ /* "<lock>" */
+ svn_xml_make_open_tag(sb, pool, svn_xml_normal, "lock", NULL);
+
+ /* "<token>xx</token>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "token", lock->token);
+
+ /* "<owner>xx</owner>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "owner", lock->owner);
+
+ /* "<comment>xx</comment>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "comment", lock->comment);
+
+ /* "<created>xx</created>" */
+ svn_cl__xml_tagged_cdata(sb, pool, "created",
+ svn_time_to_cstring(lock->creation_date, pool));
+
+ /* "<expires>xx</expires>" */
+ if (lock->expiration_date != 0)
+ svn_cl__xml_tagged_cdata(sb, pool, "expires",
+ svn_time_to_cstring(lock->expiration_date, pool));
+
+ /* "</lock>" */
+ svn_xml_make_close_tag(sb, pool, "lock");
+}
+
+
+svn_error_t *
+svn_cl__xml_print_header(const char *tagname,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ /* <?xml version="1.0" encoding="UTF-8"?> */
+ svn_xml_make_header2(&sb, "UTF-8", pool);
+
+ /* "<TAGNAME>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, tagname, NULL);
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+svn_error_t *
+svn_cl__xml_print_footer(const char *tagname,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ /* "</TAGNAME>" */
+ svn_xml_make_close_tag(&sb, pool, tagname);
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* A map for svn_node_kind_t values to XML strings */
+static const svn_token_map_t map_node_kind_xml[] =
+{
+ { "none", svn_node_none },
+ { "file", svn_node_file },
+ { "dir", svn_node_dir },
+ { "", svn_node_unknown },
+ { NULL, 0 }
+};
+
+/* A map for svn_node_kind_t values to human-readable strings */
+static const svn_token_map_t map_node_kind_human[] =
+{
+ { N_("none"), svn_node_none },
+ { N_("file"), svn_node_file },
+ { N_("dir"), svn_node_dir },
+ { "", svn_node_unknown },
+ { NULL, 0 }
+};
+
+const char *
+svn_cl__node_kind_str_xml(svn_node_kind_t kind)
+{
+ return svn_token__to_word(map_node_kind_xml, kind);
+}
+
+const char *
+svn_cl__node_kind_str_human_readable(svn_node_kind_t kind)
+{
+ return _(svn_token__to_word(map_node_kind_human, kind));
+}
+
+
+/* A map for svn_wc_operation_t values to XML strings */
+static const svn_token_map_t map_wc_operation_xml[] =
+{
+ { "none", svn_wc_operation_none },
+ { "update", svn_wc_operation_update },
+ { "switch", svn_wc_operation_switch },
+ { "merge", svn_wc_operation_merge },
+ { NULL, 0 }
+};
+
+/* A map for svn_wc_operation_t values to human-readable strings */
+static const svn_token_map_t map_wc_operation_human[] =
+{
+ { N_("none"), svn_wc_operation_none },
+ { N_("update"), svn_wc_operation_update },
+ { N_("switch"), svn_wc_operation_switch },
+ { N_("merge"), svn_wc_operation_merge },
+ { NULL, 0 }
+};
+
+const char *
+svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool)
+{
+ return svn_token__to_word(map_wc_operation_xml, operation);
+}
+
+const char *
+svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
+ apr_pool_t *pool)
+{
+ return _(svn_token__to_word(map_wc_operation_human, operation));
+}
+
+
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_last_origpath_on_truepath_collision,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = svn_client_args_to_target_array2(targets,
+ os,
+ known_targets,
+ ctx,
+ keep_last_origpath_on_truepath_collision,
+ pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svn: Skipping argument: ");
+ svn_error_clear(err);
+ }
+ else
+ return svn_error_trace(err);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for svn_cl__get_changelist(); implements
+ svn_changelist_receiver_t. */
+static svn_error_t *
+changelist_receiver(void *baton,
+ const char *path,
+ const char *changelist,
+ apr_pool_t *pool)
+{
+ /* No need to check CHANGELIST; our caller only asked about one of them. */
+ apr_array_header_t *paths = baton;
+ APR_ARRAY_PUSH(paths, const char *) = apr_pstrdup(paths->pool, path);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_cl__changelist_paths(apr_array_header_t **paths,
+ const apr_array_header_t *changelists,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *found;
+ apr_hash_t *paths_hash;
+ apr_pool_t *iterpool;
+ int i;
+
+ if (! (changelists && changelists->nelts))
+ {
+ *paths = (apr_array_header_t *)targets;
+ return SVN_NO_ERROR;
+ }
+
+ found = apr_array_make(scratch_pool, 8, sizeof(const char *));
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_client_get_changelists(target, changelists, depth,
+ changelist_receiver, found,
+ ctx, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, result_pool));
+ return svn_error_trace(svn_hash_keys(paths, paths_hash, result_pool));
+}
+
+svn_cl__show_revs_t
+svn_cl__show_revs_from_word(const char *word)
+{
+ if (strcmp(word, SVN_CL__SHOW_REVS_MERGED) == 0)
+ return svn_cl__show_revs_merged;
+ if (strcmp(word, SVN_CL__SHOW_REVS_ELIGIBLE) == 0)
+ return svn_cl__show_revs_eligible;
+ /* word is an invalid flavor. */
+ return svn_cl__show_revs_invalid;
+}
+
+
+svn_error_t *
+svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
+ const char *data,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_time_t when;
+
+ err = svn_time_from_cstring(&when, data, pool);
+ if (err && err->apr_err == SVN_ERR_BAD_DATE)
+ {
+ svn_error_clear(err);
+
+ *human_cstring = _("(invalid date)");
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *human_cstring = svn_time_to_human_cstring(when, pool);
+
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_cl__node_description(const svn_wc_conflict_version_t *node,
+ const char *wc_repos_root_URL,
+ apr_pool_t *pool)
+{
+ const char *root_str = "^";
+ const char *path_str = "...";
+
+ if (!node)
+ /* Printing "(none)" the harder way to ensure conformity (mostly with
+ * translations). */
+ return apr_psprintf(pool, "(%s)",
+ svn_cl__node_kind_str_human_readable(svn_node_none));
+
+ /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL.
+ * Otherwise show the complete URL, and if we can't, show dots. */
+
+ if (node->repos_url &&
+ (wc_repos_root_URL == NULL ||
+ strcmp(node->repos_url, wc_repos_root_URL) != 0))
+ root_str = node->repos_url;
+
+ if (node->path_in_repos)
+ path_str = node->path_in_repos;
+
+ return apr_psprintf(pool, "(%s) %s@%ld",
+ svn_cl__node_kind_str_human_readable(node->node_kind),
+ svn_path_url_add_component2(root_str, path_str, pool),
+ node->peg_rev);
+}
+
+svn_error_t *
+svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
+ const apr_array_header_t *targets,
+ apr_pool_t *pool)
+{
+ int i;
+ apr_array_header_t *true_targets;
+
+ true_targets = apr_array_make(pool, targets->nelts, sizeof(const char *));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *true_target, *peg;
+
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
+ target, pool));
+ if (peg[0] && peg[1])
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s': a peg revision is not allowed here"),
+ target);
+ APR_ARRAY_PUSH(true_targets, const char *) = true_target;
+ }
+
+ SVN_ERR_ASSERT(true_targets_p);
+ *true_targets_p = true_targets;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
+{
+ svn_error_t *err;
+
+ err = svn_client__assert_homogeneous_target_type(targets);
+ if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
+ return err;
+}
+
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target)
+{
+ if (svn_path_is_url(target))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is not a local path"), target);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets)
+{
+ int i;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+
+ SVN_ERR(svn_cl__check_target_is_local_path(target));
+ }
+ return SVN_NO_ERROR;
+}
+
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *relpath = NULL;
+
+ if (parent_path)
+ relpath = svn_dirent_skip_ancestor(parent_path, path);
+
+ return svn_dirent_local_style(relpath ? relpath : path, pool);
+}
+
+/* Return a string of the form "PATH_OR_URL@REVISION". */
+static const char *
+path_for_display(const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ apr_pool_t *pool)
+{
+ const char *rev_str = svn_opt__revision_to_string(revision, pool);
+
+ if (! svn_path_is_url(path_or_url))
+ path_or_url = svn_dirent_local_style(path_or_url, pool);
+ return apr_psprintf(pool, "%s@%s", path_or_url, rev_str);
+}
+
+svn_error_t *
+svn_cl__check_related_source_and_target(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *ancestor_url;
+ svn_revnum_t ancestor_rev;
+
+ SVN_ERR(svn_client__youngest_common_ancestor(
+ &ancestor_url, &ancestor_rev,
+ path_or_url1, revision1, path_or_url2, revision2,
+ ctx, pool, pool));
+
+ if (ancestor_url == NULL)
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Source and target have no common ancestor: "
+ "'%s' and '%s'"),
+ path_for_display(path_or_url1, revision1, pool),
+ path_for_display(path_or_url2, revision2, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
+ const char *propname,
+ const svn_string_t *propval,
+ apr_pool_t *scratch_pool)
+{
+ if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *detected_mimetype;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *local_abspath;
+ const svn_string_t *canon_propval;
+ svn_node_kind_t node_kind;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
+ SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
+ if (node_kind != svn_node_file)
+ continue;
+
+ SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
+ propname, propval,
+ local_abspath,
+ svn_node_file,
+ FALSE, NULL, NULL,
+ iterpool));
+
+ if (svn_mime_type_is_binary(canon_propval->data))
+ {
+ SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
+ local_abspath, NULL,
+ iterpool));
+ if (detected_mimetype == NULL ||
+ !svn_mime_type_is_binary(detected_mimetype))
+ svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
+ _("svn: warning: '%s' is a binary mime-type but file '%s' "
+ "looks like text; diff, merge, blame, and other "
+ "operations will stop working on this file\n"),
+ canon_propval->data,
+ svn_dirent_local_style(local_abspath, iterpool)));
+
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
OpenPOWER on IntegriCloud