aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/svn
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commit32547653cc5376642e1231fb644db99933ac8db4 (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/svn
downloadsrc-32547653cc5376642e1231fb644db99933ac8db4.tar.gz
src-32547653cc5376642e1231fb644db99933ac8db4.zip
Import trimmed svn-1.8.0-rc3vendor/subversion/subversion-1.8.0-rc3
Notes
Notes: svn path=/vendor/subversion/dist/; revision=251881 svn path=/vendor/subversion/subversion-1.8.0-rc3/; revision=251882; tag=vendor/subversion/subversion-1.8.0-rc3
Diffstat (limited to 'subversion/svn')
-rw-r--r--subversion/svn/add-cmd.c113
-rw-r--r--subversion/svn/blame-cmd.c419
-rw-r--r--subversion/svn/cat-cmd.c118
-rw-r--r--subversion/svn/changelist-cmd.c149
-rw-r--r--subversion/svn/checkout-cmd.c173
-rw-r--r--subversion/svn/cl-conflicts.c454
-rw-r--r--subversion/svn/cl-conflicts.h80
-rw-r--r--subversion/svn/cl.h852
-rw-r--r--subversion/svn/cleanup-cmd.c104
-rw-r--r--subversion/svn/client_errors.h97
-rw-r--r--subversion/svn/commit-cmd.c186
-rw-r--r--subversion/svn/conflict-callbacks.c1369
-rw-r--r--subversion/svn/copy-cmd.c184
-rw-r--r--subversion/svn/delete-cmd.c95
-rw-r--r--subversion/svn/deprecated.c41
-rw-r--r--subversion/svn/diff-cmd.c476
-rw-r--r--subversion/svn/export-cmd.c128
-rw-r--r--subversion/svn/file-merge.c959
-rw-r--r--subversion/svn/help-cmd.c153
-rw-r--r--subversion/svn/import-cmd.c132
-rw-r--r--subversion/svn/info-cmd.c683
-rw-r--r--subversion/svn/list-cmd.c424
-rw-r--r--subversion/svn/lock-cmd.c110
-rw-r--r--subversion/svn/log-cmd.c875
-rw-r--r--subversion/svn/merge-cmd.c467
-rw-r--r--subversion/svn/mergeinfo-cmd.c349
-rw-r--r--subversion/svn/mkdir-cmd.c104
-rw-r--r--subversion/svn/move-cmd.c105
-rw-r--r--subversion/svn/notify.c1222
-rw-r--r--subversion/svn/patch-cmd.c98
-rw-r--r--subversion/svn/propdel-cmd.c103
-rw-r--r--subversion/svn/propedit-cmd.c356
-rw-r--r--subversion/svn/propget-cmd.c493
-rw-r--r--subversion/svn/proplist-cmd.c336
-rw-r--r--subversion/svn/props.c356
-rw-r--r--subversion/svn/propset-cmd.c191
-rw-r--r--subversion/svn/relocate-cmd.c120
-rw-r--r--subversion/svn/resolve-cmd.c131
-rw-r--r--subversion/svn/resolved-cmd.c88
-rw-r--r--subversion/svn/revert-cmd.c81
-rw-r--r--subversion/svn/schema/blame.rnc42
-rw-r--r--subversion/svn/schema/common.rnc77
-rw-r--r--subversion/svn/schema/diff.rnc39
-rw-r--r--subversion/svn/schema/info.rnc134
-rw-r--r--subversion/svn/schema/list.rnc45
-rw-r--r--subversion/svn/schema/log.rnc55
-rw-r--r--subversion/svn/schema/props.rnc36
-rw-r--r--subversion/svn/schema/status.rnc92
-rw-r--r--subversion/svn/status-cmd.c416
-rw-r--r--subversion/svn/status.c607
-rw-r--r--subversion/svn/svn.147
-rw-r--r--subversion/svn/svn.c2961
-rw-r--r--subversion/svn/switch-cmd.c199
-rw-r--r--subversion/svn/unlock-cmd.c68
-rw-r--r--subversion/svn/update-cmd.c196
-rw-r--r--subversion/svn/upgrade-cmd.c78
-rw-r--r--subversion/svn/util.c1109
57 files changed, 19175 insertions, 0 deletions
diff --git a/subversion/svn/add-cmd.c b/subversion/svn/add-cmd.c
new file mode 100644
index 000000000000..44f73c7bc35a
--- /dev/null
+++ b/subversion/svn/add-cmd.c
@@ -0,0 +1,113 @@
+/*
+ * add-cmd.c -- Subversion add/unadd commands
+ *
+ * ====================================================================
+ * 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. ***/
+#define APR_WANT_STDIO
+#include <apr_want.h>
+
+#include "svn_path.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__add(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *iterpool;
+ apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t));
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ iterpool = svn_pool_create(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_cl__check_cancel(ctx->cancel_baton));
+ SVN_ERR(svn_cl__try
+ (svn_client_add5(target,
+ opt_state->depth,
+ opt_state->force, opt_state->no_ignore,
+ opt_state->no_autoprops, opt_state->parents,
+ ctx, iterpool),
+ errors, opt_state->quiet,
+ SVN_ERR_ENTRY_EXISTS,
+ SVN_ERR_WC_PATH_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ if (errors->nelts > 0)
+ {
+ svn_error_t *err;
+
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+ if (status == SVN_ERR_WC_PATH_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not add all targets because "
+ "some targets don't exist"));
+ else if (status == SVN_ERR_ENTRY_EXISTS)
+ err = svn_error_quick_wrap(err,
+ _("Could not add all targets because "
+ "some targets are already versioned"));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/blame-cmd.c b/subversion/svn/blame-cmd.c
new file mode 100644
index 000000000000..174a199fe898
--- /dev/null
+++ b/subversion/svn/blame-cmd.c
@@ -0,0 +1,419 @@
+/*
+ * blame-cmd.c -- Display blame information
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_cmdline.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+typedef struct blame_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_stream_t *out;
+ svn_stringbuf_t *sbuf;
+} blame_baton_t;
+
+
+/*** Code. ***/
+
+/* This implements the svn_client_blame_receiver3_t interface, printing
+ XML to stdout. */
+static svn_error_t *
+blame_receiver_xml(void *baton,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ apr_hash_t *rev_props,
+ svn_revnum_t merged_revision,
+ apr_hash_t *merged_rev_props,
+ const char *merged_path,
+ const char *line,
+ svn_boolean_t local_change,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state =
+ ((blame_baton_t *) baton)->opt_state;
+ svn_stringbuf_t *sb = ((blame_baton_t *) baton)->sbuf;
+
+ /* "<entry ...>" */
+ /* line_no is 0-based, but the rest of the world is probably Pascal
+ programmers, so we make them happy and output 1-based line numbers. */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
+ "line-number",
+ apr_psprintf(pool, "%" APR_INT64_T_FMT,
+ line_no + 1),
+ NULL);
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ svn_cl__print_xml_commit(&sb, revision,
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_DATE),
+ pool);
+
+ if (opt_state->use_merge_history && SVN_IS_VALID_REVNUM(merged_revision))
+ {
+ /* "<merged>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "merged",
+ "path", merged_path, NULL);
+
+ svn_cl__print_xml_commit(&sb, merged_revision,
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_DATE),
+ pool);
+
+ /* "</merged>" */
+ svn_xml_make_close_tag(&sb, pool, "merged");
+
+ }
+
+ /* "</entry>" */
+ svn_xml_make_close_tag(&sb, pool, "entry");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ svn_stringbuf_setempty(sb);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+print_line_info(svn_stream_t *out,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ const char *path,
+ svn_boolean_t verbose,
+ svn_revnum_t end_revnum,
+ apr_pool_t *pool)
+{
+ const char *time_utf8;
+ const char *time_stdout;
+ const char *rev_str;
+ int rev_maxlength;
+
+ /* The standard column width for the revision number is 6 characters.
+ If the revision number can potentially be larger (i.e. if the end_revnum
+ is larger than 1000000), we increase the column width as needed. */
+ rev_maxlength = 6;
+ while (end_revnum >= 1000000)
+ {
+ rev_maxlength++;
+ end_revnum = end_revnum / 10;
+ }
+ rev_str = SVN_IS_VALID_REVNUM(revision)
+ ? apr_psprintf(pool, "%*ld", rev_maxlength, revision)
+ : apr_psprintf(pool, "%*s", rev_maxlength, "-");
+
+ if (verbose)
+ {
+ if (date)
+ {
+ SVN_ERR(svn_cl__time_cstring_to_human_cstring(&time_utf8,
+ date, pool));
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&time_stdout, time_utf8,
+ pool));
+ }
+ else
+ {
+ /* ### This is a 44 characters long string. It assumes the current
+ format of svn_time_to_human_cstring and also 3 letter
+ abbreviations for the month and weekday names. Else, the
+ line contents will be misaligned. */
+ time_stdout = " -";
+ }
+
+ SVN_ERR(svn_stream_printf(out, pool, "%s %10s %s ", rev_str,
+ author ? author : " -",
+ time_stdout));
+
+ if (path)
+ SVN_ERR(svn_stream_printf(out, pool, "%-14s ", path));
+ }
+ else
+ {
+ return svn_stream_printf(out, pool, "%s %10.10s ", rev_str,
+ author ? author : " -");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the svn_client_blame_receiver3_t interface. */
+static svn_error_t *
+blame_receiver(void *baton,
+ svn_revnum_t start_revnum,
+ svn_revnum_t end_revnum,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ apr_hash_t *rev_props,
+ svn_revnum_t merged_revision,
+ apr_hash_t *merged_rev_props,
+ const char *merged_path,
+ const char *line,
+ svn_boolean_t local_change,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state =
+ ((blame_baton_t *) baton)->opt_state;
+ svn_stream_t *out = ((blame_baton_t *)baton)->out;
+ svn_boolean_t use_merged = FALSE;
+
+ if (opt_state->use_merge_history)
+ {
+ /* Choose which revision to use. If they aren't equal, prefer the
+ earliest revision. Since we do a forward blame, we want to the first
+ revision which put the line in its current state, so we use the
+ earliest revision. If we ever switch to a backward blame algorithm,
+ we may need to adjust this. */
+ if (merged_revision < revision)
+ {
+ SVN_ERR(svn_stream_puts(out, "G "));
+ use_merged = TRUE;
+ }
+ else
+ SVN_ERR(svn_stream_puts(out, " "));
+ }
+
+ if (use_merged)
+ SVN_ERR(print_line_info(out, merged_revision,
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(merged_rev_props,
+ SVN_PROP_REVISION_DATE),
+ merged_path, opt_state->verbose, end_revnum,
+ pool));
+ else
+ SVN_ERR(print_line_info(out, revision,
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_AUTHOR),
+ svn_prop_get_value(rev_props,
+ SVN_PROP_REVISION_DATE),
+ NULL, opt_state->verbose, end_revnum,
+ pool));
+
+ return svn_stream_printf(out, pool, "%s%s", line, APR_EOL_STR);
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__blame(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_pool_t *subpool;
+ apr_array_header_t *targets;
+ blame_baton_t bl;
+ int i;
+ svn_boolean_t end_revision_unspecified = FALSE;
+ svn_diff_file_options_t *diff_options = svn_diff_file_options_create(pool);
+ svn_boolean_t seen_nonexistent_target = FALSE;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Blame needs a file on which to operate. */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ {
+ if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ /* In the case that -rX was specified, we actually want to set the
+ range to be -r1:X. */
+
+ opt_state->end_revision = opt_state->start_revision;
+ opt_state->start_revision.kind = svn_opt_revision_number;
+ opt_state->start_revision.value.number = 1;
+ }
+ else
+ end_revision_unspecified = TRUE;
+ }
+
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
+ {
+ opt_state->start_revision.kind = svn_opt_revision_number;
+ opt_state->start_revision.value.number = 1;
+ }
+
+ /* The final conclusion from issue #2431 is that blame info
+ is client output (unlike 'svn cat' which plainly cats the file),
+ so the EOL style should be the platform local one.
+ */
+ if (! opt_state->xml)
+ SVN_ERR(svn_stream_for_stdout(&bl.out, pool));
+ else
+ bl.sbuf = svn_stringbuf_create_empty(pool);
+
+ bl.opt_state = opt_state;
+
+ subpool = svn_pool_create(pool);
+
+ if (opt_state->extensions)
+ {
+ apr_array_header_t *opts;
+ opts = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
+ SVN_ERR(svn_diff_file_options_parse(diff_options, opts, pool));
+ }
+
+ if (opt_state->xml)
+ {
+ if (opt_state->verbose)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'verbose' option invalid in XML mode"));
+
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("blame", pool));
+ }
+ else
+ {
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ svn_error_t *err;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+ svn_client_blame_receiver3_t receiver;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ if (end_revision_unspecified)
+ {
+ if (peg_revision.kind != svn_opt_revision_unspecified)
+ opt_state->end_revision = peg_revision;
+ else if (svn_path_is_url(target))
+ opt_state->end_revision.kind = svn_opt_revision_head;
+ else
+ opt_state->end_revision.kind = svn_opt_revision_working;
+ }
+
+ if (opt_state->xml)
+ {
+ /* "<target ...>" */
+ /* We don't output this tag immediately, which avoids creating
+ a target element if this path is skipped. */
+ const char *outpath = truepath;
+ if (! svn_path_is_url(target))
+ outpath = svn_dirent_local_style(truepath, subpool);
+ svn_xml_make_open_tag(&bl.sbuf, pool, svn_xml_normal, "target",
+ "path", outpath, NULL);
+
+ receiver = blame_receiver_xml;
+ }
+ else
+ receiver = blame_receiver;
+
+ err = svn_client_blame5(truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ diff_options,
+ opt_state->force,
+ opt_state->use_merge_history,
+ receiver,
+ &bl,
+ ctx,
+ subpool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CLIENT_IS_BINARY_FILE)
+ {
+ svn_error_clear(err);
+ SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
+ _("Skipping binary file "
+ "(use --force to treat as text): "
+ "'%s'\n"),
+ target));
+ }
+ else if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_ENTRY_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_FILE ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+ else if (opt_state->xml)
+ {
+ /* "</target>" */
+ svn_xml_make_close_tag(&(bl.sbuf), pool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(bl.sbuf->data, stdout));
+ }
+
+ if (opt_state->xml)
+ svn_stringbuf_setempty(bl.sbuf);
+ }
+ svn_pool_destroy(subpool);
+ if (opt_state->xml && ! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_footer("blame", pool));
+
+ if (seen_nonexistent_target)
+ return svn_error_create(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not perform blame on all targets because some "
+ "targets don't exist"));
+ else
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/cat-cmd.c b/subversion/svn/cat-cmd.c
new file mode 100644
index 000000000000..551420e7fa5b
--- /dev/null
+++ b/subversion/svn/cat-cmd.c
@@ -0,0 +1,118 @@
+/*
+ * cat-cmd.c -- Print the content of a file or URL.
+ *
+ * ====================================================================
+ * 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 "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_opt.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__cat(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ svn_stream_t *out;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t));
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Cat cannot operate on an implicit '.' so a filename is required */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_stream_for_stdout(&out, pool));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ SVN_ERR(svn_cl__try(svn_client_cat2(out, truepath, &peg_revision,
+ &(opt_state->start_revision),
+ ctx, subpool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_ENTRY_NOT_FOUND,
+ SVN_ERR_CLIENT_IS_DIRECTORY,
+ SVN_ERR_FS_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+ svn_pool_destroy(subpool);
+
+ if (errors->nelts > 0)
+ {
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+
+ if (status == SVN_ERR_ENTRY_NOT_FOUND ||
+ status == SVN_ERR_FS_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not cat all targets because "
+ "some targets don't exist"));
+ else if (status == SVN_ERR_UNVERSIONED_RESOURCE)
+ err = svn_error_quick_wrap(err,
+ _("Could not cat all targets because "
+ "some targets are not versioned"));
+ else if (status == SVN_ERR_CLIENT_IS_DIRECTORY)
+ err = svn_error_quick_wrap(err,
+ _("Could not cat all targets because "
+ "some targets are directories"));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/changelist-cmd.c b/subversion/svn/changelist-cmd.c
new file mode 100644
index 000000000000..46347b65b0f3
--- /dev/null
+++ b/subversion/svn/changelist-cmd.c
@@ -0,0 +1,149 @@
+/*
+ * changelist-cmd.c -- Associate (or deassociate) a wc path with a changelist.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__changelist(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *changelist_name = NULL;
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ svn_depth_t depth = opt_state->depth;
+ apr_array_header_t *errors = apr_array_make(pool, 0, sizeof(apr_status_t));
+
+ /* If we're not removing changelists, then our first argument should
+ be the name of a changelist. */
+
+ if (! opt_state->remove)
+ {
+ apr_array_header_t *args;
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ changelist_name = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&changelist_name,
+ changelist_name, pool));
+ }
+
+ /* Parse the remaining arguments as paths. */
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Changelist has no implicit dot-target `.', so don't you put that
+ code here! */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ if (opt_state->quiet)
+ /* FIXME: This is required because svn_client_create_context()
+ always initializes ctx->notify_func2 to a wrapper function
+ which calls ctx->notify_func() if it isn't NULL. In other
+ words, typically, ctx->notify_func2 is never NULL. This isn't
+ usually a problem, but the changelist logic generates
+ svn_error_t's as part of its notification.
+
+ So, svn_wc_set_changelist() checks its notify_func (our
+ ctx->notify_func2) for NULL-ness, and seeing non-NULL-ness,
+ generates a notificaton object and svn_error_t to describe some
+ problem. It passes that off to its notify_func (our
+ ctx->notify_func2) which drops the notification on the floor
+ (because it wraps a NULL ctx->notify_func). But svn_error_t's
+ dropped on the floor cause SEGFAULTs at pool cleanup time --
+ they need instead to be cleared.
+
+ SOOOooo... we set our ctx->notify_func2 to NULL so the WC code
+ doesn't even generate the errors. */
+ ctx->notify_func2 = NULL;
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_empty;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ if (changelist_name)
+ {
+ SVN_ERR(svn_cl__try(
+ svn_client_add_to_changelist(targets, changelist_name,
+ depth, opt_state->changelists,
+ ctx, pool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_WC_PATH_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+ else
+ {
+ SVN_ERR(svn_cl__try(
+ svn_client_remove_from_changelists(targets, depth,
+ opt_state->changelists,
+ ctx, pool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_WC_PATH_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+
+ if (errors->nelts > 0)
+ {
+ int i;
+ svn_error_t *err;
+
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+
+ if (status == SVN_ERR_WC_PATH_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not set changelists on "
+ "all targets because some targets "
+ "don't exist"));
+ else if (status == SVN_ERR_UNVERSIONED_RESOURCE)
+ err = svn_error_quick_wrap(err,
+ _("Could not set changelists on "
+ "all targets because some targets "
+ "are not versioned"));
+ }
+
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/checkout-cmd.c b/subversion/svn/checkout-cmd.c
new file mode 100644
index 000000000000..6c192a033a07
--- /dev/null
+++ b/subversion/svn/checkout-cmd.c
@@ -0,0 +1,173 @@
+/*
+ * checkout-cmd.c -- Subversion checkout command
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/*
+ This is what it does
+
+ - case 1: one URL
+ $ svn co http://host/repos/module
+ checkout into ./module/
+
+ - case 2: one URL and explicit path
+ $ svn co http://host/repos/module path
+ checkout into ./path/
+
+ - case 3: multiple URLs
+ $ svn co http://host1/repos1/module1 http://host2/repos2/module2
+ checkout into ./module1/ and ./module2/
+
+ - case 4: multiple URLs and explicit path
+ $ svn co http://host1/repos1/module1 http://host2/repos2/module2 path
+ checkout into ./path/module1/ and ./path/module2/
+
+ Is this the same as CVS? Does it matter if it is not?
+*/
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__checkout(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_pool_t *subpool;
+ apr_array_header_t *targets;
+ const char *last_target, *local_dir;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
+
+ /* Determine LOCAL_DIR (case 1: URL basename; 2,4: specified; 3: "")
+ * and leave TARGETS holding just the source URLs. */
+ last_target = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *);
+ if (svn_path_is_url(last_target))
+ {
+ if (targets->nelts == 1)
+ {
+ svn_opt_revision_t pegrev;
+
+ /* Use the URL basename, discarding any peg revision. */
+ SVN_ERR(svn_opt_parse_path(&pegrev, &local_dir, last_target, pool));
+ local_dir = svn_uri_basename(local_dir, pool);
+ }
+ else
+ {
+ local_dir = "";
+ }
+ }
+ else
+ {
+ if (targets->nelts == 1)
+ /* What? They gave us one target, and it wasn't a URL. */
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, NULL);
+
+ apr_array_pop(targets);
+ local_dir = last_target;
+ }
+
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_checkout(ctx->notify_baton2));
+
+ subpool = svn_pool_create(pool);
+ for (i = 0; i < targets->nelts; ++i)
+ {
+ const char *repos_url = APR_ARRAY_IDX(targets, i, const char *);
+ const char *target_dir;
+ const char *true_url;
+ svn_opt_revision_t revision = opt_state->start_revision;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Validate the REPOS_URL */
+ if (! svn_path_is_url(repos_url))
+ return svn_error_createf
+ (SVN_ERR_BAD_URL, NULL,
+ _("'%s' does not appear to be a URL"), repos_url);
+
+ /* Get a possible peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &true_url, repos_url,
+ subpool));
+
+ /* Use sub-directory of destination if checking-out multiple URLs */
+ if (targets->nelts == 1)
+ {
+ target_dir = local_dir;
+ }
+ else
+ {
+ target_dir = svn_dirent_join(local_dir,
+ svn_uri_basename(true_url, subpool),
+ subpool);
+ }
+
+ /* Checkout doesn't accept an unspecified revision, so default to
+ the peg revision, or to HEAD if there wasn't a peg. */
+ if (revision.kind == svn_opt_revision_unspecified)
+ {
+ if (peg_revision.kind != svn_opt_revision_unspecified)
+ revision = peg_revision;
+ else
+ revision.kind = svn_opt_revision_head;
+ }
+
+ SVN_ERR(svn_client_checkout3
+ (NULL, true_url, target_dir,
+ &peg_revision,
+ &revision,
+ opt_state->depth,
+ opt_state->ignore_externals,
+ opt_state->force,
+ ctx, subpool));
+ }
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/cl-conflicts.c b/subversion/svn/cl-conflicts.c
new file mode 100644
index 000000000000..440c9d76fd09
--- /dev/null
+++ b/subversion/svn/cl-conflicts.c
@@ -0,0 +1,454 @@
+/*
+ * conflicts.c: Tree conflicts.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include "cl-conflicts.h"
+#include "svn_hash.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "private/svn_token.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/* A map for svn_wc_conflict_action_t values to XML strings */
+static const svn_token_map_t map_conflict_action_xml[] =
+{
+ { "edit", svn_wc_conflict_action_edit },
+ { "delete", svn_wc_conflict_action_delete },
+ { "add", svn_wc_conflict_action_add },
+ { "replace", svn_wc_conflict_action_replace },
+ { NULL, 0 }
+};
+
+/* A map for svn_wc_conflict_reason_t values to XML strings */
+static const svn_token_map_t map_conflict_reason_xml[] =
+{
+ { "edit", svn_wc_conflict_reason_edited },
+ { "delete", svn_wc_conflict_reason_deleted },
+ { "missing", svn_wc_conflict_reason_missing },
+ { "obstruction", svn_wc_conflict_reason_obstructed },
+ { "add", svn_wc_conflict_reason_added },
+ { "replace", svn_wc_conflict_reason_replaced },
+ { "unversioned", svn_wc_conflict_reason_unversioned },
+ { "moved-away", svn_wc_conflict_reason_moved_away },
+ { "moved-here", svn_wc_conflict_reason_moved_here },
+ { NULL, 0 }
+};
+
+static const svn_token_map_t map_conflict_kind_xml[] =
+{
+ { "text", svn_wc_conflict_kind_text },
+ { "property", svn_wc_conflict_kind_property },
+ { "tree", svn_wc_conflict_kind_tree },
+ { NULL, 0 }
+};
+
+/* Return a localised string representation of the local part of a conflict;
+ NULL for non-localised odd cases. */
+static const char *
+local_reason_str(svn_node_kind_t kind, svn_wc_conflict_reason_t reason)
+{
+ switch (kind)
+ {
+ case svn_node_file:
+ switch (reason)
+ {
+ case svn_wc_conflict_reason_edited:
+ return _("local file edit");
+ case svn_wc_conflict_reason_obstructed:
+ return _("local file obstruction");
+ case svn_wc_conflict_reason_deleted:
+ return _("local file delete");
+ case svn_wc_conflict_reason_missing:
+ return _("local file missing");
+ case svn_wc_conflict_reason_unversioned:
+ return _("local file unversioned");
+ case svn_wc_conflict_reason_added:
+ return _("local file add");
+ case svn_wc_conflict_reason_replaced:
+ return _("local file replace");
+ case svn_wc_conflict_reason_moved_away:
+ return _("local file moved away");
+ case svn_wc_conflict_reason_moved_here:
+ return _("local file moved here");
+ }
+ break;
+ case svn_node_dir:
+ switch (reason)
+ {
+ case svn_wc_conflict_reason_edited:
+ return _("local dir edit");
+ case svn_wc_conflict_reason_obstructed:
+ return _("local dir obstruction");
+ case svn_wc_conflict_reason_deleted:
+ return _("local dir delete");
+ case svn_wc_conflict_reason_missing:
+ return _("local dir missing");
+ case svn_wc_conflict_reason_unversioned:
+ return _("local dir unversioned");
+ case svn_wc_conflict_reason_added:
+ return _("local dir add");
+ case svn_wc_conflict_reason_replaced:
+ return _("local dir replace");
+ case svn_wc_conflict_reason_moved_away:
+ return _("local dir moved away");
+ case svn_wc_conflict_reason_moved_here:
+ return _("local dir moved here");
+ }
+ break;
+ case svn_node_symlink:
+ case svn_node_none:
+ case svn_node_unknown:
+ break;
+ }
+ return NULL;
+}
+
+/* Return a localised string representation of the incoming part of a
+ conflict; NULL for non-localised odd cases. */
+static const char *
+incoming_action_str(svn_node_kind_t kind, svn_wc_conflict_action_t action)
+{
+ switch (kind)
+ {
+ case svn_node_file:
+ switch (action)
+ {
+ case svn_wc_conflict_action_edit:
+ return _("incoming file edit");
+ case svn_wc_conflict_action_add:
+ return _("incoming file add");
+ case svn_wc_conflict_action_delete:
+ return _("incoming file delete");
+ case svn_wc_conflict_action_replace:
+ return _("incoming file replace");
+ }
+ break;
+ case svn_node_dir:
+ switch (action)
+ {
+ case svn_wc_conflict_action_edit:
+ return _("incoming dir edit");
+ case svn_wc_conflict_action_add:
+ return _("incoming dir add");
+ case svn_wc_conflict_action_delete:
+ return _("incoming dir delete");
+ case svn_wc_conflict_action_replace:
+ return _("incoming dir replace");
+ }
+ break;
+ case svn_node_symlink:
+ case svn_node_none:
+ case svn_node_unknown:
+ break;
+ }
+ return NULL;
+}
+
+/* Return a localised string representation of the operation part of a
+ conflict. */
+static const char *
+operation_str(svn_wc_operation_t operation)
+{
+ switch (operation)
+ {
+ case svn_wc_operation_update: return _("upon update");
+ case svn_wc_operation_switch: return _("upon switch");
+ case svn_wc_operation_merge: return _("upon merge");
+ case svn_wc_operation_none: return _("upon none");
+ }
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ return NULL;
+}
+
+svn_error_t *
+svn_cl__get_human_readable_prop_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ const char *reason_str, *action_str;
+
+ /* We provide separately translatable strings for the values that we
+ * know about, and a fall-back in case any other values occur. */
+ switch (conflict->reason)
+ {
+ case svn_wc_conflict_reason_edited:
+ reason_str = _("local edit");
+ break;
+ case svn_wc_conflict_reason_added:
+ reason_str = _("local add");
+ break;
+ case svn_wc_conflict_reason_deleted:
+ reason_str = _("local delete");
+ break;
+ case svn_wc_conflict_reason_obstructed:
+ reason_str = _("local obstruction");
+ break;
+ default:
+ reason_str = apr_psprintf(pool, _("local %s"),
+ svn_token__to_word(map_conflict_reason_xml,
+ conflict->reason));
+ break;
+ }
+ switch (conflict->action)
+ {
+ case svn_wc_conflict_action_edit:
+ action_str = _("incoming edit");
+ break;
+ case svn_wc_conflict_action_add:
+ action_str = _("incoming add");
+ break;
+ case svn_wc_conflict_action_delete:
+ action_str = _("incoming delete");
+ break;
+ default:
+ action_str = apr_psprintf(pool, _("incoming %s"),
+ svn_token__to_word(map_conflict_action_xml,
+ conflict->action));
+ break;
+ }
+ SVN_ERR_ASSERT(reason_str && action_str);
+ *desc = apr_psprintf(pool, _("%s, %s %s"),
+ reason_str, action_str,
+ operation_str(conflict->operation));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__get_human_readable_tree_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ const char *action, *reason, *operation;
+ svn_node_kind_t incoming_kind;
+
+ /* Determine the node kind of the incoming change. */
+ incoming_kind = svn_node_unknown;
+ if (conflict->action == svn_wc_conflict_action_edit ||
+ conflict->action == svn_wc_conflict_action_delete)
+ {
+ /* Change is acting on 'src_left' version of the node. */
+ if (conflict->src_left_version)
+ incoming_kind = conflict->src_left_version->node_kind;
+ }
+ else if (conflict->action == svn_wc_conflict_action_add ||
+ conflict->action == svn_wc_conflict_action_replace)
+ {
+ /* Change is acting on 'src_right' version of the node.
+ *
+ * ### For 'replace', the node kind is ambiguous. However, src_left
+ * ### is NULL for replace, so we must use src_right. */
+ if (conflict->src_right_version)
+ incoming_kind = conflict->src_right_version->node_kind;
+ }
+
+ reason = local_reason_str(conflict->node_kind, conflict->reason);
+ action = incoming_action_str(incoming_kind, conflict->action);
+ operation = operation_str(conflict->operation);
+ SVN_ERR_ASSERT(operation);
+
+ if (action && reason)
+ {
+ *desc = apr_psprintf(pool, _("%s, %s %s"),
+ reason, action, operation);
+ }
+ else
+ {
+ /* A catch-all message for very rare or nominally impossible cases.
+ It will not be pretty, but is closer to an internal error than
+ an ordinary user-facing string. */
+ *desc = apr_psprintf(pool, _("local: %s %s incoming: %s %s %s"),
+ svn_node_kind_to_word(conflict->node_kind),
+ svn_token__to_word(map_conflict_reason_xml,
+ conflict->reason),
+ svn_node_kind_to_word(incoming_kind),
+ svn_token__to_word(map_conflict_action_xml,
+ conflict->action),
+ operation);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for svn_cl__append_tree_conflict_info_xml().
+ * Appends the attributes of the given VERSION to ATT_HASH.
+ * SIDE is the content of the version tag's side="..." attribute,
+ * currently one of "source-left" or "source-right".*/
+static svn_error_t *
+add_conflict_version_xml(svn_stringbuf_t **pstr,
+ const char *side,
+ const svn_wc_conflict_version_t *version,
+ apr_pool_t *pool)
+{
+ apr_hash_t *att_hash = apr_hash_make(pool);
+
+
+ svn_hash_sets(att_hash, "side", side);
+
+ if (version->repos_url)
+ svn_hash_sets(att_hash, "repos-url", version->repos_url);
+
+ if (version->path_in_repos)
+ svn_hash_sets(att_hash, "path-in-repos", version->path_in_repos);
+
+ if (SVN_IS_VALID_REVNUM(version->peg_rev))
+ svn_hash_sets(att_hash, "revision", apr_ltoa(pool, version->peg_rev));
+
+ if (version->node_kind != svn_node_unknown)
+ svn_hash_sets(att_hash, "kind",
+ svn_cl__node_kind_str_xml(version->node_kind));
+
+ svn_xml_make_open_tag_hash(pstr, pool, svn_xml_self_closing,
+ "version", att_hash);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+append_tree_conflict_info_xml(svn_stringbuf_t *str,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool)
+{
+ apr_hash_t *att_hash = apr_hash_make(pool);
+ const char *tmp;
+
+ svn_hash_sets(att_hash, "victim",
+ svn_dirent_basename(conflict->local_abspath, pool));
+
+ svn_hash_sets(att_hash, "kind",
+ svn_cl__node_kind_str_xml(conflict->node_kind));
+
+ svn_hash_sets(att_hash, "operation",
+ svn_cl__operation_str_xml(conflict->operation, pool));
+
+ tmp = svn_token__to_word(map_conflict_action_xml, conflict->action);
+ svn_hash_sets(att_hash, "action", tmp);
+
+ tmp = svn_token__to_word(map_conflict_reason_xml, conflict->reason);
+ svn_hash_sets(att_hash, "reason", tmp);
+
+ /* Open the tree-conflict tag. */
+ svn_xml_make_open_tag_hash(&str, pool, svn_xml_normal,
+ "tree-conflict", att_hash);
+
+ /* Add child tags for OLDER_VERSION and THEIR_VERSION. */
+
+ if (conflict->src_left_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-left",
+ conflict->src_left_version,
+ pool));
+
+ if (conflict->src_right_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-right",
+ conflict->src_right_version,
+ pool));
+
+ svn_xml_make_close_tag(&str, pool, "tree-conflict");
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__append_conflict_info_xml(svn_stringbuf_t *str,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *att_hash;
+ const char *kind;
+ if (conflict->kind == svn_wc_conflict_kind_tree)
+ {
+ /* Uses other element type */
+ return svn_error_trace(
+ append_tree_conflict_info_xml(str, conflict, scratch_pool));
+ }
+
+ att_hash = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(att_hash, "operation",
+ svn_cl__operation_str_xml(conflict->operation, scratch_pool));
+
+
+ kind = svn_token__to_word(map_conflict_kind_xml, conflict->kind);
+ svn_hash_sets(att_hash, "type", kind);
+
+ svn_hash_sets(att_hash, "operation",
+ svn_cl__operation_str_xml(conflict->operation, scratch_pool));
+
+
+ /* "<conflict>" */
+ svn_xml_make_open_tag_hash(&str, scratch_pool,
+ svn_xml_normal, "conflict", att_hash);
+
+ if (conflict->src_left_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-left",
+ conflict->src_left_version,
+ scratch_pool));
+
+ if (conflict->src_right_version)
+ SVN_ERR(add_conflict_version_xml(&str,
+ "source-right",
+ conflict->src_right_version,
+ scratch_pool));
+
+ switch (conflict->kind)
+ {
+ case svn_wc_conflict_kind_text:
+ /* "<prev-base-file> xx </prev-base-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-base-file",
+ conflict->base_abspath);
+
+ /* "<prev-wc-file> xx </prev-wc-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "prev-wc-file",
+ conflict->my_abspath);
+
+ /* "<cur-base-file> xx </cur-base-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "cur-base-file",
+ conflict->their_abspath);
+
+ break;
+
+ case svn_wc_conflict_kind_property:
+ /* "<prop-file> xx </prop-file>" */
+ svn_cl__xml_tagged_cdata(&str, scratch_pool, "prop-file",
+ conflict->their_abspath);
+ break;
+
+ default:
+ case svn_wc_conflict_kind_tree:
+ SVN_ERR_MALFUNCTION(); /* Handled separately */
+ break;
+ }
+
+ /* "</conflict>" */
+ svn_xml_make_close_tag(&str, scratch_pool, "conflict");
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/cl-conflicts.h b/subversion/svn/cl-conflicts.h
new file mode 100644
index 000000000000..07591a0ef02c
--- /dev/null
+++ b/subversion/svn/cl-conflicts.h
@@ -0,0 +1,80 @@
+/*
+ * conflicts.h: Conflicts handling
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+
+
+#ifndef SVN_CONFLICTS_H
+#define SVN_CONFLICTS_H
+
+/*** Includes. ***/
+#include <apr_pools.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_wc.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * Return in @a desc a possibly localized human readable
+ * description of a property conflict described by @a conflict.
+ *
+ * Allocate the result in @a pool.
+ */
+svn_error_t *
+svn_cl__get_human_readable_prop_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool);
+
+/**
+ * Return in @a desc a possibly localized human readable
+ * description of a tree conflict described by @a conflict.
+ *
+ * Allocate the result in @a pool.
+ */
+svn_error_t *
+svn_cl__get_human_readable_tree_conflict_description(
+ const char **desc,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool);
+
+/**
+ * Append to @a str an XML representation of the conflict data
+ * for @a conflict, in a format suitable for 'svn info --xml'.
+ */
+svn_error_t *
+svn_cl__append_conflict_info_xml(
+ svn_stringbuf_t *str,
+ const svn_wc_conflict_description2_t *conflict,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CONFLICTS_H */
diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h
new file mode 100644
index 000000000000..f7ebee668290
--- /dev/null
+++ b/subversion/svn/cl.h
@@ -0,0 +1,852 @@
+/*
+ * cl.h: shared stuff in the command line program
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CL_H
+#define SVN_CL_H
+
+/*** Includes. ***/
+#include <apr_tables.h>
+#include <apr_getopt.h>
+
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_opt.h"
+#include "svn_auth.h"
+#include "svn_cmdline.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Option processing ***/
+
+/* --accept actions */
+typedef enum svn_cl__accept_t
+{
+ /* invalid accept action */
+ svn_cl__accept_invalid = -2,
+
+ /* unspecified accept action */
+ svn_cl__accept_unspecified = -1,
+
+ /* Leave conflicts alone, for later resolution. */
+ svn_cl__accept_postpone,
+
+ /* Resolve the conflict with the pre-conflict base file. */
+ svn_cl__accept_base,
+
+ /* Resolve the conflict with the current working file. */
+ svn_cl__accept_working,
+
+ /* Resolve the conflicted hunks by choosing the corresponding text
+ from the pre-conflict working copy file. */
+ svn_cl__accept_mine_conflict,
+
+ /* Resolve the conflicted hunks by choosing the corresponding text
+ from the post-conflict base copy file. */
+ svn_cl__accept_theirs_conflict,
+
+ /* Resolve the conflict by taking the entire pre-conflict working
+ copy file. */
+ svn_cl__accept_mine_full,
+
+ /* Resolve the conflict by taking the entire post-conflict base file. */
+ svn_cl__accept_theirs_full,
+
+ /* Launch user's editor and resolve conflict with edited file. */
+ svn_cl__accept_edit,
+
+ /* Launch user's resolver and resolve conflict with edited file. */
+ svn_cl__accept_launch
+
+} svn_cl__accept_t;
+
+/* --accept action user input words */
+#define SVN_CL__ACCEPT_POSTPONE "postpone"
+#define SVN_CL__ACCEPT_BASE "base"
+#define SVN_CL__ACCEPT_WORKING "working"
+#define SVN_CL__ACCEPT_MINE_CONFLICT "mine-conflict"
+#define SVN_CL__ACCEPT_THEIRS_CONFLICT "theirs-conflict"
+#define SVN_CL__ACCEPT_MINE_FULL "mine-full"
+#define SVN_CL__ACCEPT_THEIRS_FULL "theirs-full"
+#define SVN_CL__ACCEPT_EDIT "edit"
+#define SVN_CL__ACCEPT_LAUNCH "launch"
+
+/* Return the svn_cl__accept_t value corresponding to WORD, using exact
+ * case-sensitive string comparison. Return svn_cl__accept_invalid if WORD
+ * is empty or is not one of the known values. */
+svn_cl__accept_t
+svn_cl__accept_from_word(const char *word);
+
+
+/*** Mergeinfo flavors. ***/
+
+/* --show-revs values */
+typedef enum svn_cl__show_revs_t {
+ svn_cl__show_revs_invalid = -1,
+ svn_cl__show_revs_merged,
+ svn_cl__show_revs_eligible
+} svn_cl__show_revs_t;
+
+/* --show-revs user input words */
+#define SVN_CL__SHOW_REVS_MERGED "merged"
+#define SVN_CL__SHOW_REVS_ELIGIBLE "eligible"
+
+/* Return svn_cl__show_revs_t value corresponding to word. */
+svn_cl__show_revs_t
+svn_cl__show_revs_from_word(const char *word);
+
+
+/*** Command dispatch. ***/
+
+/* Hold results of option processing that are shared by multiple
+ commands. */
+typedef struct svn_cl__opt_state_t
+{
+ /* An array of svn_opt_revision_range_t *'s representing revisions
+ ranges indicated on the command-line via the -r and -c options.
+ For each range in the list, if only one revision was provided
+ (-rN), its 'end' member remains 'svn_opt_revision_unspecified'.
+ This array always has at least one element, even if that is a
+ null range in which both ends are 'svn_opt_revision_unspecified'. */
+ apr_array_header_t *revision_ranges;
+
+ /* These are simply a copy of the range start and end values present
+ in the first item of the revision_ranges list. */
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+
+ /* Flag which is only set if the '-c' option was used. */
+ svn_boolean_t used_change_arg;
+
+ /* Flag which is only set if the '-r' option was used. */
+ svn_boolean_t used_revision_arg;
+
+ /* Max number of log messages to get back from svn_client_log2. */
+ int limit;
+
+ /* After option processing is done, reflects the switch actually
+ given on the command line, or svn_depth_unknown if none. */
+ svn_depth_t depth;
+
+ /* Was --no-unlock specified? */
+ svn_boolean_t no_unlock;
+
+ const char *message; /* log message */
+ svn_boolean_t force; /* be more forceful, as in "svn rm -f ..." */
+ svn_boolean_t force_log; /* force validity of a suspect log msg file */
+ svn_boolean_t incremental; /* yield output suitable for concatenation */
+ svn_boolean_t quiet; /* sssh...avoid unnecessary output */
+ svn_boolean_t non_interactive; /* do no interactive prompting */
+ svn_boolean_t version; /* print version information */
+ svn_boolean_t verbose; /* be verbose */
+ svn_boolean_t update; /* contact the server for the full story */
+ svn_boolean_t strict; /* do strictly what was requested */
+ svn_stringbuf_t *filedata; /* contents of file used as option data */
+ const char *encoding; /* the locale/encoding of the data*/
+ svn_boolean_t help; /* print usage message */
+ const char *auth_username; /* auth username */ /* UTF-8! */
+ const char *auth_password; /* auth password */ /* UTF-8! */
+ const char *extensions; /* subprocess extension args */ /* UTF-8! */
+ apr_array_header_t *targets; /* target list from file */ /* UTF-8! */
+ svn_boolean_t xml; /* output in xml, e.g., "svn log --xml" */
+ svn_boolean_t no_ignore; /* disregard default ignores & svn:ignore's */
+ svn_boolean_t no_auth_cache; /* do not cache authentication information */
+ struct
+ {
+ const char *diff_cmd; /* the external diff command to use */
+ svn_boolean_t internal_diff; /* override diff_cmd in config file */
+ svn_boolean_t no_diff_added; /* do not show diffs for deleted files */
+ svn_boolean_t no_diff_deleted; /* do not show diffs for deleted files */
+ svn_boolean_t show_copies_as_adds; /* do not diff copies with their source */
+ svn_boolean_t notice_ancestry; /* notice ancestry for diff-y operations */
+ svn_boolean_t summarize; /* create a summary of a diff */
+ svn_boolean_t use_git_diff_format; /* Use git's extended diff format */
+ svn_boolean_t ignore_properties; /* ignore properties */
+ svn_boolean_t properties_only; /* Show properties only */
+ svn_boolean_t patch_compatible; /* Output compatible with GNU patch */
+ } diff;
+ svn_boolean_t ignore_ancestry; /* ignore ancestry for merge-y operations */
+ svn_boolean_t ignore_externals;/* ignore externals definitions */
+ svn_boolean_t stop_on_copy; /* don't cross copies during processing */
+ svn_boolean_t dry_run; /* try operation but make no changes */
+ svn_boolean_t revprop; /* operate on a revision property */
+ const char *merge_cmd; /* the external merge command to use */
+ const char *editor_cmd; /* the external editor command to use */
+ svn_boolean_t record_only; /* whether to record mergeinfo */
+ const char *old_target; /* diff target */
+ const char *new_target; /* diff target */
+ svn_boolean_t relocate; /* rewrite urls (svn switch) */
+ const char *config_dir; /* over-riding configuration directory */
+ apr_array_header_t *config_options; /* over-riding configuration options */
+ svn_boolean_t autoprops; /* enable automatic properties */
+ svn_boolean_t no_autoprops; /* disable automatic properties */
+ const char *native_eol; /* override system standard eol marker */
+ svn_boolean_t remove; /* deassociate a changelist */
+ apr_array_header_t *changelists; /* changelist filters */
+ const char *changelist; /* operate on this changelist
+ THIS IS TEMPORARY (LAST OF CHANGELISTS) */
+ svn_boolean_t keep_changelists;/* don't remove changelists after commit */
+ svn_boolean_t keep_local; /* delete path only from repository */
+ svn_boolean_t all_revprops; /* retrieve all revprops */
+ svn_boolean_t no_revprops; /* retrieve no revprops */
+ apr_hash_t *revprop_table; /* table of revision properties to get/set */
+ svn_boolean_t parents; /* create intermediate directories */
+ svn_boolean_t use_merge_history; /* use/display extra merge information */
+ svn_cl__accept_t accept_which; /* how to handle conflicts */
+ svn_cl__show_revs_t show_revs; /* mergeinfo flavor */
+ svn_depth_t set_depth; /* new sticky ambient depth value */
+ svn_boolean_t reintegrate; /* use "reintegrate" merge-source heuristic */
+ svn_boolean_t trust_server_cert; /* trust server SSL certs that would
+ otherwise be rejected as "untrusted" */
+ int strip; /* number of leading path components to strip */
+ svn_boolean_t ignore_keywords; /* do not expand keywords */
+ svn_boolean_t reverse_diff; /* reverse a diff (e.g. when patching) */
+ svn_boolean_t ignore_whitespace; /* don't account for whitespace when
+ patching */
+ svn_boolean_t show_diff; /* produce diff output (maps to --diff) */
+ svn_boolean_t allow_mixed_rev; /* Allow operation on mixed-revision WC */
+ svn_boolean_t include_externals; /* Recurses (in)to file & dir externals */
+ svn_boolean_t show_inherited_props; /* get inherited properties */
+ apr_array_header_t* search_patterns; /* pattern arguments for --search */
+} svn_cl__opt_state_t;
+
+
+typedef struct svn_cl__cmd_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_client_ctx_t *ctx;
+} svn_cl__cmd_baton_t;
+
+
+/* Declare all the command procedures */
+svn_opt_subcommand_t
+ svn_cl__add,
+ svn_cl__blame,
+ svn_cl__cat,
+ svn_cl__changelist,
+ svn_cl__checkout,
+ svn_cl__cleanup,
+ svn_cl__commit,
+ svn_cl__copy,
+ svn_cl__delete,
+ svn_cl__diff,
+ svn_cl__export,
+ svn_cl__help,
+ svn_cl__import,
+ svn_cl__info,
+ svn_cl__lock,
+ svn_cl__log,
+ svn_cl__list,
+ svn_cl__merge,
+ svn_cl__mergeinfo,
+ svn_cl__mkdir,
+ svn_cl__move,
+ svn_cl__patch,
+ svn_cl__propdel,
+ svn_cl__propedit,
+ svn_cl__propget,
+ svn_cl__proplist,
+ svn_cl__propset,
+ svn_cl__relocate,
+ svn_cl__revert,
+ svn_cl__resolve,
+ svn_cl__resolved,
+ svn_cl__status,
+ svn_cl__switch,
+ svn_cl__unlock,
+ svn_cl__update,
+ svn_cl__upgrade;
+
+
+/* See definition in svn.c for documentation. */
+extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[];
+
+/* See definition in svn.c for documentation. */
+extern const int svn_cl__global_options[];
+
+/* See definition in svn.c for documentation. */
+extern const apr_getopt_option_t svn_cl__options[];
+
+
+/* A helper for the many subcommands that wish to merely warn when
+ * invoked on an unversioned, nonexistent, or otherwise innocuously
+ * errorful resource. Meant to be wrapped with SVN_ERR().
+ *
+ * If ERR is null, return SVN_NO_ERROR.
+ *
+ * Else if ERR->apr_err is one of the error codes supplied in varargs,
+ * then handle ERR as a warning (unless QUIET is true), clear ERR, and
+ * return SVN_NO_ERROR, and push the value of ERR->apr_err into the
+ * ERRORS_SEEN array, if ERRORS_SEEN is not NULL.
+ *
+ * Else return ERR.
+ *
+ * Typically, error codes like SVN_ERR_UNVERSIONED_RESOURCE,
+ * SVN_ERR_ENTRY_NOT_FOUND, etc, are supplied in varargs. Don't
+ * forget to terminate the argument list with SVN_NO_ERROR.
+ */
+svn_error_t *
+svn_cl__try(svn_error_t *err,
+ apr_array_header_t *errors_seen,
+ svn_boolean_t quiet,
+ ...);
+
+
+/* Our cancellation callback. */
+svn_error_t *
+svn_cl__check_cancel(void *baton);
+
+
+
+/* Various conflict-resolution callbacks. */
+
+/* Opaque baton type for svn_cl__conflict_func_interactive(). */
+typedef struct svn_cl__interactive_conflict_baton_t
+ svn_cl__interactive_conflict_baton_t;
+
+/* Conflict stats for operations such as update and merge. */
+typedef struct svn_cl__conflict_stats_t svn_cl__conflict_stats_t;
+
+/* Return a new, initialized, conflict stats structure, allocated in
+ * POOL. */
+svn_cl__conflict_stats_t *
+svn_cl__conflict_stats_create(apr_pool_t *pool);
+
+/* Update CONFLICT_STATS to reflect that a conflict on PATH_LOCAL of kind
+ * CONFLICT_KIND is resolved. (There is no support for updating the
+ * 'skipped paths' stats, since skips cannot be 'resolved'.) */
+void
+svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats,
+ const char *path_local,
+ svn_wc_conflict_kind_t conflict_kind);
+
+
+/* Create and return an baton for use with svn_cl__conflict_func_interactive
+ * in *B, allocated from RESULT_POOL, and initialised with the values
+ * ACCEPT_WHICH, CONFIG, EDITOR_CMD, CANCEL_FUNC and CANCEL_BATON. */
+svn_error_t *
+svn_cl__get_conflict_func_interactive_baton(
+ svn_cl__interactive_conflict_baton_t **b,
+ svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ svn_cl__conflict_stats_t *conflict_stats,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool);
+
+/* A callback capable of doing interactive conflict resolution.
+
+ The BATON must come from svn_cl__get_conflict_func_interactive_baton().
+ Resolves based on the --accept option if one was given to that function,
+ otherwise prompts the user to choose one of the three fulltexts, edit
+ the merged file on the spot, or just skip the conflict (to be resolved
+ later), among other options.
+
+ Implements svn_wc_conflict_resolver_func2_t.
+ */
+svn_error_t *
+svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/*** Command-line output functions -- printing to the user. ***/
+
+/* Print out commit information found in COMMIT_INFO to the console.
+ * POOL is used for temporay allocations.
+ * COMMIT_INFO should not be NULL.
+ *
+ * This function implements svn_commit_callback2_t.
+ */
+svn_error_t *
+svn_cl__print_commit_info(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/* Convert the date in DATA to a human-readable UTF-8-encoded string
+ * *HUMAN_CSTRING, or set the latter to "(invalid date)" if DATA is not
+ * a valid date. DATA should be as expected by svn_time_from_cstring().
+ *
+ * Do all allocations in POOL.
+ */
+svn_error_t *
+svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
+ const char *data,
+ apr_pool_t *pool);
+
+
+/* Print STATUS for PATH to stdout for human consumption. Prints in
+ abbreviated format by default, or DETAILED format if flag is set.
+
+ When SUPPRESS_EXTERNALS_PLACEHOLDERS is set, avoid printing
+ externals placeholder lines ("X lines").
+
+ When DETAILED is set, use SHOW_LAST_COMMITTED to toggle display of
+ the last-committed-revision and last-committed-author.
+
+ If SKIP_UNRECOGNIZED is TRUE, this function will not print out
+ unversioned items found in the working copy.
+
+ When DETAILED is set, and REPOS_LOCKS is set, treat missing repository locks
+ as broken WC locks.
+
+ Increment *TEXT_CONFLICTS, *PROP_CONFLICTS, or *TREE_CONFLICTS if
+ a conflict was encountered.
+
+ Use CWD_ABSPATH -- the absolute path of the current working
+ directory -- to shorten PATH into something relative to that
+ directory as necessary.
+*/
+svn_error_t *
+svn_cl__print_status(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_boolean_t suppress_externals_placeholders,
+ svn_boolean_t detailed,
+ svn_boolean_t show_last_committed,
+ svn_boolean_t skip_unrecognized,
+ svn_boolean_t repos_locks,
+ unsigned int *text_conflicts,
+ unsigned int *prop_conflicts,
+ unsigned int *tree_conflicts,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Print STATUS for PATH in XML to stdout. Use POOL for temporary
+ allocations.
+
+ Use CWD_ABSPATH -- the absolute path of the current working
+ directory -- to shorten PATH into something relative to that
+ directory as necessary.
+ */
+svn_error_t *
+svn_cl__print_status_xml(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Output a commit xml element to *OUTSTR. If *OUTSTR is NULL, allocate it
+ first from POOL, otherwise append to it. If AUTHOR or DATE is
+ NULL, it will be omitted. */
+void
+svn_cl__print_xml_commit(svn_stringbuf_t **outstr,
+ svn_revnum_t revision,
+ const char *author,
+ const char *date,
+ apr_pool_t *pool);
+
+/* Output an XML "<lock>" element describing LOCK to *OUTSTR. If *OUTSTR is
+ NULL, allocate it first from POOL, otherwise append to it. */
+void
+svn_cl__print_xml_lock(svn_stringbuf_t **outstr,
+ const svn_lock_t *lock,
+ apr_pool_t *pool);
+
+/* Do the following things that are commonly required before accessing revision
+ properties. Ensure that REVISION is specified explicitly and is not
+ relative to a working-copy item. Ensure that exactly one target is
+ specified in TARGETS. Set *URL to the URL of the target. Return an
+ appropriate error if any of those checks or operations fail. Use CTX for
+ accessing the working copy
+ */
+svn_error_t *
+svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
+ const apr_array_header_t *targets,
+ const char **URL,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+/* Search for a merge tool command in environment variables,
+ and use it to perform the merge of the four given files.
+ WC_PATH is the path of the file that is in conflict, relative
+ to the merge target.
+ Use POOL for all allocations.
+
+ CONFIG is a hash of svn_config_t * items keyed on a configuration
+ category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL.
+
+ Upon success, set *REMAINS_IN_CONFLICT to indicate whether the
+ merge result contains conflict markers.
+ */
+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);
+
+/* Like svn_cl__merge_file_externally, but using a built-in merge tool
+ * with help from an external editor specified by EDITOR_CMD. */
+svn_error_t *
+svn_cl__merge_file(const char *base_path,
+ const char *their_path,
+ const char *my_path,
+ const char *merged_path,
+ const char *wc_path,
+ const char *path_prefix,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ svn_boolean_t *remains_in_conflict,
+ apr_pool_t *scratch_pool);
+
+
+/*** Notification functions to display results on the terminal. */
+
+/* Set *NOTIFY_FUNC_P and *NOTIFY_BATON_P to a notifier/baton for all
+ * operations, allocated in POOL.
+ */
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ svn_cl__conflict_stats_t *conflict_stats,
+ apr_pool_t *pool);
+
+/* Make the notifier for use with BATON print the appropriate summary
+ * line at the end of the output.
+ */
+svn_error_t *
+svn_cl__notifier_mark_checkout(void *baton);
+
+/* Make the notifier for use with BATON print the appropriate summary
+ * line at the end of the output.
+ */
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton);
+
+/* Make the notifier for use with BATON print the appropriate notifications
+ * for a wc to repository copy
+ */
+svn_error_t *
+svn_cl__notifier_mark_wc_to_repos_copy(void *baton);
+
+/* Baton for use with svn_cl__check_externals_failed_notify_wrapper(). */
+struct svn_cl__check_externals_failed_notify_baton
+{
+ svn_wc_notify_func2_t wrapped_func; /* The "real" notify_func2. */
+ void *wrapped_baton; /* The "real" notify_func2 baton. */
+ svn_boolean_t had_externals_error; /* Did something fail in an external? */
+};
+
+/* Notification function wrapper (implements `svn_wc_notify_func2_t').
+ Use with an svn_cl__check_externals_failed_notify_baton BATON. */
+void
+svn_cl__check_externals_failed_notify_wrapper(void *baton,
+ const svn_wc_notify_t *n,
+ apr_pool_t *pool);
+
+/* Print the conflict stats accumulated in BATON, which is the
+ * notifier baton from svn_cl__get_notifier().
+ * Return any error encountered during printing.
+ */
+svn_error_t *
+svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool);
+
+
+ /*** Log message callback stuffs. ***/
+
+/* Allocate in POOL a baton for use with svn_cl__get_log_message().
+
+ OPT_STATE is the set of command-line options given.
+
+ BASE_DIR is a directory in which to create temporary files if an
+ external editor is used to edit the log message. If BASE_DIR is
+ NULL, the current working directory (`.') will be used, and
+ therefore the user must have the proper permissions on that
+ directory. ### todo: What *should* happen in the NULL case is that
+ we ask APR to tell us where a suitable tmp directory is (like, /tmp
+ on Unix and C:\Windows\Temp on Win32 or something), and use it.
+ But APR doesn't yet have that capability.
+
+ CONFIG is a client configuration hash of svn_config_t * items keyed
+ on config categories, and may be NULL.
+
+ NOTE: While the baton itself will be allocated from POOL, the items
+ add to it are added by reference, not duped into POOL!*/
+svn_error_t *
+svn_cl__make_log_msg_baton(void **baton,
+ svn_cl__opt_state_t *opt_state,
+ const char *base_dir,
+ apr_hash_t *config,
+ apr_pool_t *pool);
+
+/* A function of type svn_client_get_commit_log3_t. */
+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);
+
+/* Handle the cleanup of a log message, using the data in the
+ LOG_MSG_BATON, in the face of COMMIT_ERR. This may mean removing a
+ temporary file left by an external editor, or it may be a complete
+ no-op. COMMIT_ERR may be NULL to indicate to indicate that the
+ function should act as though no commit error occurred. Use POOL
+ for temporary allocations.
+
+ All error returns from this function are guaranteed to at least
+ include COMMIT_ERR, and perhaps additional errors attached to the
+ end of COMMIT_ERR's chain. */
+svn_error_t *
+svn_cl__cleanup_log_msg(void *log_msg_baton,
+ svn_error_t *commit_err,
+ apr_pool_t *pool);
+
+/* Add a message about --force if appropriate */
+svn_error_t *
+svn_cl__may_need_force(svn_error_t *err);
+
+/* Write the STRING to the stdio STREAM, returning an error if it fails.
+
+ This function is equal to svn_cmdline_fputs() minus the utf8->local
+ encoding translation. */
+svn_error_t *
+svn_cl__error_checked_fputs(const char *string, FILE* stream);
+
+/* If STRING is non-null, append it, wrapped in a simple XML CDATA element
+ named TAGNAME, to the string SB. Use POOL for temporary allocations. */
+void
+svn_cl__xml_tagged_cdata(svn_stringbuf_t **sb,
+ apr_pool_t *pool,
+ const char *tagname,
+ const char *string);
+
+/* Print the XML prolog and document root element start-tag to stdout, using
+ TAGNAME as the root element name. Use POOL for temporary allocations. */
+svn_error_t *
+svn_cl__xml_print_header(const char *tagname, apr_pool_t *pool);
+
+/* Print the XML document root element end-tag to stdout, using TAGNAME as the
+ root element name. Use POOL for temporary allocations. */
+svn_error_t *
+svn_cl__xml_print_footer(const char *tagname, apr_pool_t *pool);
+
+
+/* For use in XML output, return a non-localised string representation
+ * of KIND, being "none" or "dir" or "file" or, in any other case,
+ * the empty string. */
+const char *
+svn_cl__node_kind_str_xml(svn_node_kind_t kind);
+
+/* Return a (possibly localised) string representation of KIND, being "none" or
+ "dir" or "file" or, in any other case, the empty string. */
+const char *
+svn_cl__node_kind_str_human_readable(svn_node_kind_t kind);
+
+
+/** Provides an XML name for a given OPERATION.
+ * Note: POOL is currently not used.
+ */
+const char *
+svn_cl__operation_str_xml(svn_wc_operation_t operation, apr_pool_t *pool);
+
+/** Return a possibly localized human readable string for
+ * a given OPERATION.
+ * Note: POOL is currently not used.
+ */
+const char *
+svn_cl__operation_str_human_readable(svn_wc_operation_t operation,
+ apr_pool_t *pool);
+
+
+/* What use is a property name intended for.
+ Used by svn_cl__check_svn_prop_name to customize error messages. */
+typedef enum svn_cl__prop_use_e
+ {
+ svn_cl__prop_use_set, /* setting the property */
+ svn_cl__prop_use_edit, /* editing the property */
+ svn_cl__prop_use_use /* using the property name */
+ }
+svn_cl__prop_use_t;
+
+/* If PROPNAME looks like but is not identical to one of the svn:
+ * poperties, raise an error and suggest a better spelling. Names that
+ * raise errors look like this:
+ *
+ * - start with svn: but do not exactly match a known property; or,
+ * - start with a 3-letter prefix that differs in only one letter
+ * from "svn:", and the rest exactly matches a known propery.
+ *
+ * If REVPROP is TRUE, only check revision property names; otherwise
+ * only check node property names.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+svn_error_t *
+svn_cl__check_svn_prop_name(const char *propname,
+ svn_boolean_t revprop,
+ svn_cl__prop_use_t prop_use,
+ apr_pool_t *scratch_pool);
+
+/* If PROPNAME is one of the svn: properties with a boolean value, and
+ * PROPVAL looks like an attempt to turn the property off (i.e., it's
+ * "off", "no", "false", or ""), then print a warning to the user that
+ * setting the property to this value might not do what they expect.
+ * Perform temporary allocations in POOL.
+ */
+void
+svn_cl__check_boolean_prop_val(const char *propname,
+ const char *propval,
+ apr_pool_t *pool);
+
+/* De-streamifying wrapper around svn_client_get_changelists(), which
+ is called for each target in TARGETS to populate *PATHS (a list of
+ paths assigned to one of the CHANGELISTS.
+ If all targets are to be included, may set *PATHS to TARGETS without
+ reallocating. */
+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);
+
+/* Like svn_client_args_to_target_array() but, if the only error is that some
+ * arguments are reserved file names, then print warning messages for those
+ * targets, store the rest of the targets in TARGETS_P and return success. */
+svn_error_t *
+svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets_p,
+ apr_getopt_t *os,
+ const apr_array_header_t *known_targets,
+ svn_client_ctx_t *ctx,
+ svn_boolean_t keep_dest_origpath_on_truepath_collision,
+ apr_pool_t *pool);
+
+/* Return a string showing NODE's kind, URL and revision, to the extent that
+ * that information is available in NODE. If NODE itself is NULL, this prints
+ * just a 'none' node kind.
+ * WC_REPOS_ROOT_URL should reflect the target working copy's repository
+ * root URL. If NODE is from that same URL, the printed URL is abbreviated
+ * to caret notation (^/). WC_REPOS_ROOT_URL may be NULL, in which case
+ * this function tries to print the conflicted node's complete URL. */
+const char *
+svn_cl__node_description(const svn_wc_conflict_version_t *node,
+ const char *wc_repos_root_URL,
+ apr_pool_t *pool);
+
+/* Return, in @a *true_targets_p, a shallow copy of @a targets with any
+ * empty peg revision specifier snipped off the end of each element. If any
+ * target has a non-empty peg revision specifier, throw an error. The user
+ * may have specified a peg revision where it doesn't make sense to do so,
+ * or may have forgotten to escape an '@' character in a filename.
+ *
+ * This function is useful for subcommands for which peg revisions
+ * do not make any sense. Such subcommands still need to allow an empty
+ * peg revision to be specified on the command line so that users of
+ * the command line client can consistently escape '@' characters
+ * in filenames by appending an '@' character, regardless of the
+ * subcommand being used.
+ *
+ * It is safe to pass the address of @a targets as @a true_targets_p.
+ *
+ * Do all allocations in @a pool. */
+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);
+
+/* Return an error if TARGETS contains a mixture of URLs and paths; otherwise
+ * return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets);
+
+/* Return an error if TARGETS contains a URL; otherwise return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__check_targets_are_local_paths(const apr_array_header_t *targets);
+
+/* Return an error if TARGET is a URL; otherwise return SVN_NO_ERROR. */
+svn_error_t *
+svn_cl__check_target_is_local_path(const char *target);
+
+/* Return a copy of PATH, converted to the local path style, skipping
+ * PARENT_PATH if it is non-null and is a parent of or equal to PATH.
+ *
+ * This function assumes PARENT_PATH and PATH are both absolute "dirents"
+ * or both relative "dirents". */
+const char *
+svn_cl__local_style_skip_ancestor(const char *parent_path,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Check that PATH_OR_URL1@REVISION1 is related to PATH_OR_URL2@REVISION2.
+ * Raise an error if not.
+ *
+ * ### Ideally we would also check that they are on different lines of
+ * history. That is easy in common cases, but to give a correct answer in
+ * general we need to know the operative revision(s) as well. For example,
+ * when one location is the branch point from which the other branch was
+ * copied.
+ */
+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);
+
+/* If the user is setting a mime-type to mark one of the TARGETS as binary,
+ * as determined by property name PROPNAME and value PROPVAL, then check
+ * whether Subversion's own binary-file detection recognizes the target as
+ * a binary file. If Subversion doesn't consider the target to be a binary
+ * file, assume the user is making an error and print a warning to inform
+ * the user that some operations might fail on the file in the future. */
+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);
+
+/* A wrapper around the deprecated svn_client_merge_reintegrate. */
+svn_error_t *
+svn_cl__deprecated_merge_reintegrate(const char *source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CL_H */
diff --git a/subversion/svn/cleanup-cmd.c b/subversion/svn/cleanup-cmd.c
new file mode 100644
index 000000000000..64fa400d71be
--- /dev/null
+++ b/subversion/svn/cleanup-cmd.c
@@ -0,0 +1,104 @@
+/*
+ * cleanup-cmd.c -- Subversion cleanup command
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__cleanup(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_pool_t *subpool;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ subpool = svn_pool_create(pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_error_t *err;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+ err = svn_client_cleanup(target, ctx, subpool);
+ if (err && err->apr_err == SVN_ERR_WC_LOCKED)
+ {
+ const char *target_abspath;
+ svn_error_t *err2 = svn_dirent_get_absolute(&target_abspath,
+ target, subpool);
+ if (err2)
+ {
+ err = svn_error_compose_create(err, err2);
+ }
+ else
+ {
+ const char *wcroot_abspath;
+
+ err2 = svn_client_get_wc_root(&wcroot_abspath, target_abspath,
+ ctx, subpool, subpool);
+ if (err2)
+ err = svn_error_compose_create(err, err2);
+ else
+ err = svn_error_createf(SVN_ERR_WC_LOCKED, err,
+ _("Working copy locked; try running "
+ "'svn cleanup' on the root of the "
+ "working copy ('%s') instead."),
+ svn_dirent_local_style(wcroot_abspath,
+ subpool));
+ }
+ }
+ SVN_ERR(err);
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/client_errors.h b/subversion/svn/client_errors.h
new file mode 100644
index 000000000000..19f0bdfadc09
--- /dev/null
+++ b/subversion/svn/client_errors.h
@@ -0,0 +1,97 @@
+/*
+ * client_errors.h: error codes this command line client features
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+#ifndef SVN_CLIENT_ERRORS_H
+#define SVN_CLIENT_ERRORS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * This error defining system is copied from and explained in
+ * ../../include/svn_error_codes.h
+ */
+
+/* Process this file if we're building an error array, or if we have
+ not defined the enumerated constants yet. */
+#if defined(SVN_ERROR_BUILD_ARRAY) || !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#if defined(SVN_ERROR_BUILD_ARRAY)
+
+#error "Need to update err_defn for r1464679 and un-typo 'CDMLINE'"
+
+#define SVN_ERROR_START \
+ static const err_defn error_table[] = { \
+ { SVN_ERR_CDMLINE__WARNING, "Warning" },
+#define SVN_ERRDEF(n, s) { n, s },
+#define SVN_ERROR_END { 0, NULL } };
+
+#elif !defined(SVN_CMDLINE_ERROR_ENUM_DEFINED)
+
+#define SVN_ERROR_START \
+ typedef enum svn_client_errno_t { \
+ SVN_ERR_CDMLINE__WARNING = SVN_ERR_LAST + 1,
+#define SVN_ERRDEF(n, s) n,
+#define SVN_ERROR_END SVN_ERR_CMDLINE__ERR_LAST } svn_client_errno_t;
+
+#define SVN_CMDLINE_ERROR_ENUM_DEFINED
+
+#endif
+
+/* Define custom command line client error numbers */
+
+SVN_ERROR_START
+
+ /* BEGIN Client errors */
+
+SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_WRITE,
+ "Failed writing to temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_STAT,
+ "Failed getting info about temporary file.")
+
+ SVN_ERRDEF(SVN_ERR_CMDLINE__TMPFILE_OPEN,
+ "Failed opening temporary file.")
+
+ /* END Client errors */
+
+
+SVN_ERROR_END
+
+#undef SVN_ERROR_START
+#undef SVN_ERRDEF
+#undef SVN_ERROR_END
+
+#endif /* SVN_ERROR_BUILD_ARRAY || !SVN_CMDLINE_ERROR_ENUM_DEFINED */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CLIENT_ERRORS_H */
diff --git a/subversion/svn/commit-cmd.c b/subversion/svn/commit-cmd.c
new file mode 100644
index 000000000000..2d04c6971ca4
--- /dev/null
+++ b/subversion/svn/commit-cmd.c
@@ -0,0 +1,186 @@
+/*
+ * commit-cmd.c -- Check changes into the repository.
+ *
+ * ====================================================================
+ * 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 <apr_general.h>
+
+#include "svn_hash.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_path.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_config.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Wrapper notify_func2 function and baton for warning about
+ reduced-depth commits of copied directories. */
+struct copy_warning_notify_baton
+{
+ svn_wc_notify_func2_t wrapped_func;
+ void *wrapped_baton;
+ svn_depth_t depth;
+ svn_boolean_t warned;
+};
+
+static void
+copy_warning_notify_func(void *baton,
+ const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ struct copy_warning_notify_baton *b = baton;
+
+ /* Call the wrapped notification system (if any). */
+ if (b->wrapped_func)
+ b->wrapped_func(b->wrapped_baton, notify, pool);
+
+ /* If we're being notified about a copy of a directory when our
+ commit depth is less-than-infinite, and we've not already warned
+ about this situation, then warn about it (and remember that we
+ now have.) */
+ if ((! b->warned)
+ && (b->depth < svn_depth_infinity)
+ && (notify->kind == svn_node_dir)
+ && ((notify->action == svn_wc_notify_commit_copied) ||
+ (notify->action == svn_wc_notify_commit_copied_replaced)))
+ {
+ svn_error_t *err;
+ err = svn_cmdline_printf(pool,
+ _("svn: The depth of this commit is '%s', "
+ "but copies are always performed "
+ "recursively in the repository.\n"),
+ svn_depth_to_word(b->depth));
+ /* ### FIXME: Try to return this error showhow? */
+ svn_error_clear(err);
+
+ /* We'll only warn once. */
+ b->warned = TRUE;
+ }
+}
+
+
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__commit(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_array_header_t *condensed_targets;
+ const char *base_dir;
+ svn_config_t *cfg;
+ svn_boolean_t no_unlock = FALSE;
+ struct copy_warning_notify_baton cwnb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ SVN_ERR_W(svn_cl__check_targets_are_local_paths(targets),
+ _("Commit targets must be local paths"));
+
+ /* Add "." if user passed 0 arguments. */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ /* Condense the targets (like commit does)... */
+ SVN_ERR(svn_dirent_condense_targets(&base_dir, &condensed_targets, targets,
+ TRUE, pool, pool));
+
+ if ((! condensed_targets) || (! condensed_targets->nelts))
+ {
+ const char *parent_dir, *base_name;
+
+ SVN_ERR(svn_wc_get_actual_target2(&parent_dir, &base_name, ctx->wc_ctx,
+ base_dir, pool, pool));
+ if (*base_name)
+ base_dir = apr_pstrdup(pool, parent_dir);
+ }
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ cfg = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
+ if (cfg)
+ SVN_ERR(svn_config_get_bool(cfg, &no_unlock,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_NO_UNLOCK, FALSE));
+
+ /* We're creating a new log message baton because we can use our base_dir
+ to store the temp file, instead of the current working directory. The
+ client might not have write access to their working directory, but they
+ better have write access to the directory they're committing. */
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3),
+ opt_state, base_dir,
+ ctx->config, pool));
+
+ /* Copies are done server-side, and cheaply, which means they're
+ effectively always done with infinite depth. This is a potential
+ cause of confusion for users trying to commit copied subtrees in
+ part by restricting the commit's depth. See issues #3699 and #3752. */
+ if (opt_state->depth < svn_depth_infinity)
+ {
+ cwnb.wrapped_func = ctx->notify_func2;
+ cwnb.wrapped_baton = ctx->notify_baton2;
+ cwnb.depth = opt_state->depth;
+ cwnb.warned = FALSE;
+ ctx->notify_func2 = copy_warning_notify_func;
+ ctx->notify_baton2 = &cwnb;
+ }
+
+ /* Commit. */
+ err = svn_client_commit6(targets,
+ opt_state->depth,
+ no_unlock,
+ opt_state->keep_changelists,
+ TRUE /* commit_as_operations */,
+ opt_state->include_externals, /* file externals */
+ opt_state->include_externals, /* dir externals */
+ opt_state->changelists,
+ opt_state->revprop_table,
+ (opt_state->quiet
+ ? NULL : svn_cl__print_commit_info),
+ NULL,
+ ctx,
+ pool);
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c
new file mode 100644
index 000000000000..096a1892cdec
--- /dev/null
+++ b/subversion/svn/conflict-callbacks.c
@@ -0,0 +1,1369 @@
+/*
+ * conflict-callbacks.c: conflict resolution callbacks specific to the
+ * commandline client.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_sorts.h"
+#include "svn_utf.h"
+
+#include "cl.h"
+#include "cl-conflicts.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+#define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0])))
+
+
+
+struct svn_cl__interactive_conflict_baton_t {
+ svn_cl__accept_t accept_which;
+ apr_hash_t *config;
+ const char *editor_cmd;
+ svn_boolean_t external_failed;
+ svn_cmdline_prompt_baton_t *pb;
+ const char *path_prefix;
+ svn_boolean_t quit;
+ svn_cl__conflict_stats_t *conflict_stats;
+};
+
+svn_error_t *
+svn_cl__get_conflict_func_interactive_baton(
+ svn_cl__interactive_conflict_baton_t **b,
+ svn_cl__accept_t accept_which,
+ apr_hash_t *config,
+ const char *editor_cmd,
+ svn_cl__conflict_stats_t *conflict_stats,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *result_pool)
+{
+ svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb));
+ pb->cancel_func = cancel_func;
+ pb->cancel_baton = cancel_baton;
+
+ *b = apr_palloc(result_pool, sizeof(**b));
+ (*b)->accept_which = accept_which;
+ (*b)->config = config;
+ (*b)->editor_cmd = editor_cmd;
+ (*b)->external_failed = FALSE;
+ (*b)->pb = pb;
+ SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool));
+ (*b)->quit = FALSE;
+ (*b)->conflict_stats = conflict_stats;
+
+ return SVN_NO_ERROR;
+}
+
+svn_cl__accept_t
+svn_cl__accept_from_word(const char *word)
+{
+ /* Shorthand options are consistent with svn_cl__conflict_handler(). */
+ if (strcmp(word, SVN_CL__ACCEPT_POSTPONE) == 0
+ || strcmp(word, "p") == 0 || strcmp(word, ":-P") == 0)
+ return svn_cl__accept_postpone;
+ if (strcmp(word, SVN_CL__ACCEPT_BASE) == 0)
+ /* ### shorthand? */
+ return svn_cl__accept_base;
+ if (strcmp(word, SVN_CL__ACCEPT_WORKING) == 0)
+ /* ### shorthand? */
+ return svn_cl__accept_working;
+ if (strcmp(word, SVN_CL__ACCEPT_MINE_CONFLICT) == 0
+ || strcmp(word, "mc") == 0 || strcmp(word, "X-)") == 0)
+ return svn_cl__accept_mine_conflict;
+ if (strcmp(word, SVN_CL__ACCEPT_THEIRS_CONFLICT) == 0
+ || strcmp(word, "tc") == 0 || strcmp(word, "X-(") == 0)
+ return svn_cl__accept_theirs_conflict;
+ if (strcmp(word, SVN_CL__ACCEPT_MINE_FULL) == 0
+ || strcmp(word, "mf") == 0 || strcmp(word, ":-)") == 0)
+ return svn_cl__accept_mine_full;
+ if (strcmp(word, SVN_CL__ACCEPT_THEIRS_FULL) == 0
+ || strcmp(word, "tf") == 0 || strcmp(word, ":-(") == 0)
+ return svn_cl__accept_theirs_full;
+ if (strcmp(word, SVN_CL__ACCEPT_EDIT) == 0
+ || strcmp(word, "e") == 0 || strcmp(word, ":-E") == 0)
+ return svn_cl__accept_edit;
+ if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0
+ || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0)
+ return svn_cl__accept_launch;
+ /* word is an invalid action. */
+ return svn_cl__accept_invalid;
+}
+
+
+/* Print on stdout a diff that shows incoming conflicting changes
+ * corresponding to the conflict described in DESC. */
+static svn_error_t *
+show_diff(const svn_wc_conflict_description2_t *desc,
+ const char *path_prefix,
+ apr_pool_t *pool)
+{
+ const char *path1, *path2;
+ const char *label1, *label2;
+ svn_diff_t *diff;
+ svn_stream_t *output;
+ svn_diff_file_options_t *options;
+
+ if (desc->merged_file)
+ {
+ /* For conflicts recorded by the 'merge' operation, show a diff between
+ * 'mine' (the working version of the file as it appeared before the
+ * 'merge' operation was run) and 'merged' (the version of the file
+ * as it appears after the merge operation).
+ *
+ * For conflicts recorded by the 'update' and 'switch' operations,
+ * show a diff beween 'theirs' (the new pristine version of the
+ * file) and 'merged' (the version of the file as it appears with
+ * local changes merged with the new pristine version).
+ *
+ * This way, the diff is always minimal and clearly identifies changes
+ * brought into the working copy by the update/switch/merge operation. */
+ if (desc->operation == svn_wc_operation_merge)
+ {
+ path1 = desc->my_abspath;
+ label1 = _("MINE");
+ }
+ else
+ {
+ path1 = desc->their_abspath;
+ label1 = _("THEIRS");
+ }
+ path2 = desc->merged_file;
+ label2 = _("MERGED");
+ }
+ else
+ {
+ /* There's no merged file, but we can show the
+ difference between mine and theirs. */
+ path1 = desc->their_abspath;
+ label1 = _("THEIRS");
+ path2 = desc->my_abspath;
+ label2 = _("MINE");
+ }
+
+ label1 = apr_psprintf(pool, "%s\t- %s",
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, path1, pool), label1);
+ label2 = apr_psprintf(pool, "%s\t- %s",
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, path2, pool), label2);
+
+ options = svn_diff_file_options_create(pool);
+ options->ignore_eol_style = TRUE;
+ SVN_ERR(svn_stream_for_stdout(&output, pool));
+ SVN_ERR(svn_diff_file_diff_2(&diff, path1, path2,
+ options, pool));
+ return svn_diff_file_output_unified3(output, diff,
+ path1, path2,
+ label1, label2,
+ APR_LOCALE_CHARSET,
+ NULL, FALSE,
+ pool);
+}
+
+
+/* Print on stdout just the conflict hunks of a diff among the 'base', 'their'
+ * and 'my' files of DESC. */
+static svn_error_t *
+show_conflicts(const svn_wc_conflict_description2_t *desc,
+ apr_pool_t *pool)
+{
+ svn_diff_t *diff;
+ svn_stream_t *output;
+ svn_diff_file_options_t *options;
+
+ options = svn_diff_file_options_create(pool);
+ options->ignore_eol_style = TRUE;
+ SVN_ERR(svn_stream_for_stdout(&output, pool));
+ SVN_ERR(svn_diff_file_diff3_2(&diff,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
+ options, pool));
+ /* ### Consider putting the markers/labels from
+ ### svn_wc__merge_internal in the conflict description. */
+ return svn_diff_file_output_merge2(output, diff,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
+ _("||||||| ORIGINAL"),
+ _("<<<<<<< MINE (select with 'mc')"),
+ _(">>>>>>> THEIRS (select with 'tc')"),
+ "=======",
+ svn_diff_conflict_display_only_conflicts,
+ pool);
+}
+
+/* Perform a 3-way merge of the conflicting values of a property,
+ * and write the result to the OUTPUT stream.
+ *
+ * If MERGED_ABSPATH is non-NULL, use it as 'my' version instead of
+ * DESC->MY_ABSPATH.
+ *
+ * Assume the values are printable UTF-8 text.
+ */
+static svn_error_t *
+merge_prop_conflict(svn_stream_t *output,
+ const svn_wc_conflict_description2_t *desc,
+ const char *merged_abspath,
+ apr_pool_t *pool)
+{
+ const char *base_abspath = desc->base_abspath;
+ const char *my_abspath = desc->my_abspath;
+ const char *their_abspath = desc->their_abspath;
+ svn_diff_file_options_t *options = svn_diff_file_options_create(pool);
+ svn_diff_t *diff;
+
+ /* If any of the property values is missing, use an empty file instead
+ * for the purpose of showing a diff. */
+ if (! base_abspath || ! my_abspath || ! their_abspath)
+ {
+ const char *empty_file;
+
+ SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file,
+ NULL, svn_io_file_del_on_pool_cleanup,
+ pool, pool));
+ if (! base_abspath)
+ base_abspath = empty_file;
+ if (! my_abspath)
+ my_abspath = empty_file;
+ if (! their_abspath)
+ their_abspath = empty_file;
+ }
+
+ options->ignore_eol_style = TRUE;
+ SVN_ERR(svn_diff_file_diff3_2(&diff,
+ base_abspath,
+ merged_abspath ? merged_abspath : my_abspath,
+ their_abspath,
+ options, pool));
+ SVN_ERR(svn_diff_file_output_merge2(output, diff,
+ base_abspath,
+ merged_abspath ? merged_abspath
+ : my_abspath,
+ their_abspath,
+ _("||||||| ORIGINAL"),
+ _("<<<<<<< MINE"),
+ _(">>>>>>> THEIRS"),
+ "=======",
+ svn_diff_conflict_display_modified_original_latest,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Display the conflicting values of a property as a 3-way diff.
+ *
+ * If MERGED_ABSPATH is non-NULL, show it as 'my' version instead of
+ * DESC->MY_ABSPATH.
+ *
+ * Assume the values are printable UTF-8 text.
+ */
+static svn_error_t *
+show_prop_conflict(const svn_wc_conflict_description2_t *desc,
+ const char *merged_abspath,
+ apr_pool_t *pool)
+{
+ svn_stream_t *output;
+
+ SVN_ERR(svn_stream_for_stdout(&output, pool));
+ SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Run an external editor, passing it the MERGED_FILE, or, if the
+ * 'merged' file is null, return an error. The tool to use is determined by
+ * B->editor_cmd, B->config and environment variables; see
+ * svn_cl__edit_file_externally() for details.
+ *
+ * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
+ * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
+ * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
+ * return that error. */
+static svn_error_t *
+open_editor(svn_boolean_t *performed_edit,
+ const char *merged_file,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ if (merged_file)
+ {
+ err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd,
+ b->config, pool);
+ if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("No editor found.")));
+ svn_error_clear(err);
+ }
+ else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("Error running editor.")));
+ svn_error_clear(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+ else
+ *performed_edit = TRUE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool,
+ _("Invalid option; there's no "
+ "merged version to edit.\n\n")));
+
+ return SVN_NO_ERROR;
+}
+
+/* Run an external editor, passing it the 'merged' property in DESC.
+ * The tool to use is determined by B->editor_cmd, B->config and
+ * environment variables; see svn_cl__edit_file_externally() for details. */
+static svn_error_t *
+edit_prop_conflict(const char **merged_file_path,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *file;
+ const char *file_path;
+ svn_boolean_t performed_edit = FALSE;
+ svn_stream_t *merged_prop;
+
+ SVN_ERR(svn_io_open_unique_file3(&file, &file_path, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool));
+ merged_prop = svn_stream_from_aprfile2(file, TRUE /* disown */,
+ scratch_pool);
+ SVN_ERR(merge_prop_conflict(merged_prop, desc, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(merged_prop));
+ SVN_ERR(svn_io_file_flush_to_disk(file, scratch_pool));
+ SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool));
+ *merged_file_path = (performed_edit ? file_path : NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Run an external merge tool, passing it the 'base', 'their', 'my' and
+ * 'merged' files in DESC. The tool to use is determined by B->config and
+ * environment variables; see svn_cl__merge_file_externally() for details.
+ *
+ * If the tool runs, set *PERFORMED_EDIT to true; if a tool is not
+ * configured or cannot run, do not touch *PERFORMED_EDIT, report the error
+ * on stderr, and return SVN_NO_ERROR; if any other error is encountered,
+ * return that error. */
+static svn_error_t *
+launch_resolver(svn_boolean_t *performed_edit,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ err = svn_cl__merge_file_externally(desc->base_abspath, desc->their_abspath,
+ desc->my_abspath, desc->merged_file,
+ desc->local_abspath, b->config, NULL,
+ pool);
+ if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ err->message ? err->message :
+ _("No merge tool found, "
+ "try '(m) merge' instead.\n")));
+ svn_error_clear(err);
+ }
+ else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ err->message ? err->message :
+ _("Error running merge tool, "
+ "try '(m) merge' instead.")));
+ svn_error_clear(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+ else if (performed_edit)
+ *performed_edit = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Maximum line length for the prompt string. */
+#define MAX_PROMPT_WIDTH 70
+
+/* Description of a resolver option */
+typedef struct resolver_option_t
+{
+ const char *code; /* one or two characters */
+ const char *short_desc; /* label in prompt (localized) */
+ const char *long_desc; /* longer description (localized) */
+ svn_wc_conflict_choice_t choice; /* or -1 if not a simple choice */
+} resolver_option_t;
+
+/* Resolver options for a text conflict */
+/* (opt->code == "" causes a blank line break in help_string()) */
+static const resolver_option_t text_conflict_options[] =
+{
+ /* Translators: keep long_desc below 70 characters (wrap with a left
+ margin of 9 spaces if needed); don't translate the words within square
+ brackets. */
+ { "e", N_("edit file"), N_("change merged file in an editor"
+ " [edit]"),
+ -1 },
+ { "df", N_("show diff"), N_("show all changes made to merged file"),
+ -1 },
+ { "r", N_("resolved"), N_("accept merged version of file"),
+ svn_wc_conflict_choose_merged },
+ { "", "", "", svn_wc_conflict_choose_unspecified },
+ { "dc", N_("display conflict"), N_("show all conflicts "
+ "(ignoring merged version)"), -1 },
+ { "mc", N_("my side of conflict"), N_("accept my version for all conflicts "
+ "(same) [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "tc", N_("their side of conflict"), N_("accept their version for all "
+ "conflicts (same)"
+ " [theirs-conflict]"),
+ svn_wc_conflict_choose_theirs_conflict },
+ { "", "", "", svn_wc_conflict_choose_unspecified },
+ { "mf", N_("my version"), N_("accept my version of entire file (even "
+ "non-conflicts) [mine-full]"),
+ svn_wc_conflict_choose_mine_full },
+ { "tf", N_("their version"), N_("accept their version of entire file "
+ "(same) [theirs-full]"),
+ svn_wc_conflict_choose_theirs_full },
+ { "", "", "", svn_wc_conflict_choose_unspecified },
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "m", N_("merge"), N_("use internal merge tool to resolve "
+ "conflict"), -1 },
+ { "l", N_("launch tool"), N_("launch external tool to resolve "
+ "conflict [launch]"), -1 },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "s", N_("show all options"), N_("show this list (also 'h', '?')"), -1 },
+ { NULL }
+};
+
+/* Resolver options for a property conflict */
+static const resolver_option_t prop_conflict_options[] =
+{
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mf", N_("my version"), N_("accept my version of entire file (even "
+ "non-conflicts) [mine-full]"),
+ svn_wc_conflict_choose_mine_full },
+ { "tf", N_("their version"), N_("accept their version of entire file "
+ "(same) [theirs-full]"),
+ svn_wc_conflict_choose_theirs_full },
+ { "dc", N_("display conflict"), N_("show conflicts in this property"), -1 },
+ { "e", N_("edit property"), N_("change merged property value in an editor"
+ " [edit]"), -1 },
+ { "r", N_("resolved"), N_("accept edited version of property"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+/* Resolver options for an obstructued addition */
+static const resolver_option_t obstructed_add_options[] =
+{
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mf", N_("my version"), N_("accept pre-existing item (ignore "
+ "upstream addition) [mine-full]"),
+ svn_wc_conflict_choose_mine_full },
+ { "tf", N_("their version"), N_("accept incoming item (overwrite "
+ "pre-existing item) [theirs-full]"),
+ svn_wc_conflict_choose_theirs_full },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+/* Resolver options for a tree conflict */
+static const resolver_option_t tree_conflict_options[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "r", N_("resolved"), N_("accept current working copy state"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_moved_away[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mc", N_("my side of conflict"), N_("apply update to the move destination"
+ " [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("resolved"), N_("mark resolved "
+ "(the move will become a copy)"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_deleted[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mc", N_("my side of conflict"), N_("keep any moves affected "
+ "by this deletion [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("resolved"), N_("mark resolved (any affected moves will "
+ "become copies)"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+static const resolver_option_t tree_conflict_options_update_replaced[] =
+{
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "mc", N_("my side of conflict"), N_("keep any moves affected by this "
+ "replacement [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("resolved"), N_("mark resolved (any affected moves will "
+ "become copies)"),
+ svn_wc_conflict_choose_merged },
+ { "q", N_("quit resolution"), N_("postpone all remaining conflicts"),
+ svn_wc_conflict_choose_postpone },
+ { "h", N_("help"), N_("show this help (also '?')"), -1 },
+ { NULL }
+};
+
+
+/* Return a pointer to the option description in OPTIONS matching the
+ * one- or two-character OPTION_CODE. Return NULL if not found. */
+static const resolver_option_t *
+find_option(const resolver_option_t *options,
+ const char *option_code)
+{
+ const resolver_option_t *opt;
+
+ for (opt = options; opt->code; opt++)
+ {
+ /* Ignore code "" (blank lines) which is not a valid answer. */
+ if (opt->code[0] && strcmp(opt->code, option_code) == 0)
+ return opt;
+ }
+ return NULL;
+}
+
+/* Return a prompt string listing the options OPTIONS. If OPTION_CODES is
+ * non-null, select only the options whose codes are mentioned in it. */
+static const char *
+prompt_string(const resolver_option_t *options,
+ const char *const *option_codes,
+ apr_pool_t *pool)
+{
+ const char *result = _("Select:");
+ int left_margin = svn_utf_cstring_utf8_width(result);
+ const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, "");
+ int this_line_len = left_margin;
+ svn_boolean_t first = TRUE;
+
+ while (1)
+ {
+ const resolver_option_t *opt;
+ const char *s;
+ int slen;
+
+ if (option_codes)
+ {
+ if (! *option_codes)
+ break;
+ opt = find_option(options, *option_codes++);
+ }
+ else
+ {
+ opt = options++;
+ if (! opt->code)
+ break;
+ }
+
+ if (! first)
+ result = apr_pstrcat(pool, result, ",", (char *)NULL);
+ s = apr_psprintf(pool, _(" (%s) %s"),
+ opt->code, _(opt->short_desc));
+ slen = svn_utf_cstring_utf8_width(s);
+ /* Break the line if adding the next option would make it too long */
+ if (this_line_len + slen > MAX_PROMPT_WIDTH)
+ {
+ result = apr_pstrcat(pool, result, line_sep, (char *)NULL);
+ this_line_len = left_margin;
+ }
+ result = apr_pstrcat(pool, result, s, (char *)NULL);
+ this_line_len += slen;
+ first = FALSE;
+ }
+ return apr_pstrcat(pool, result, ": ", (char *)NULL);
+}
+
+/* Return a help string listing the OPTIONS. */
+static const char *
+help_string(const resolver_option_t *options,
+ apr_pool_t *pool)
+{
+ const char *result = "";
+ const resolver_option_t *opt;
+
+ for (opt = options; opt->code; opt++)
+ {
+ /* Append a line describing OPT, or a blank line if its code is "". */
+ if (opt->code[0])
+ {
+ const char *s = apr_psprintf(pool, " (%s)", opt->code);
+
+ result = apr_psprintf(pool, "%s%-6s - %s\n",
+ result, s, _(opt->long_desc));
+ }
+ else
+ {
+ result = apr_pstrcat(pool, result, "\n", (char *)NULL);
+ }
+ }
+ result = apr_pstrcat(pool, result,
+ _("Words in square brackets are the corresponding "
+ "--accept option arguments.\n"),
+ (char *)NULL);
+ return result;
+}
+
+/* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed
+ * in OPTIONS_TO_SHOW if that is non-null. Set *OPT to point to the chosen
+ * one of CONFLICT_OPTIONS (not necessarily one of OPTIONS_TO_SHOW), or to
+ * NULL if the answer was not one of them.
+ *
+ * If the answer is the (globally recognized) 'help' option, then display
+ * the help (on stderr) and return with *OPT == NULL.
+ */
+static svn_error_t *
+prompt_user(const resolver_option_t **opt,
+ const resolver_option_t *conflict_options,
+ const char *const *options_to_show,
+ void *prompt_baton,
+ apr_pool_t *scratch_pool)
+{
+ const char *prompt
+ = prompt_string(conflict_options, options_to_show, scratch_pool);
+ const char *answer;
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, prompt_baton, scratch_pool));
+ if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
+ help_string(conflict_options,
+ scratch_pool)));
+ *opt = NULL;
+ }
+ else
+ {
+ *opt = find_option(conflict_options, answer);
+ if (! *opt)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Unrecognized option.\n\n")));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the text conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_text_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t diff_allowed = FALSE;
+ /* Have they done something that might have affected the merged
+ file (so that we need to save a .edited copy)? */
+ svn_boolean_t performed_edit = FALSE;
+ /* Have they done *something* (edit, look at diff, etc) to
+ give them a rational basis for choosing (r)esolved? */
+ svn_boolean_t knows_something = FALSE;
+
+ SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict discovered in file '%s'.\n"),
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ scratch_pool)));
+
+ /* Diffing can happen between base and merged, to show conflict
+ markers to the user (this is the typical 3-way merge
+ scenario), or if no base is available, we can show a diff
+ between mine and theirs. */
+ if ((desc->merged_file && desc->base_abspath)
+ || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))
+ diff_allowed = TRUE;
+
+ while (TRUE)
+ {
+ const char *options[ARRAY_LEN(text_conflict_options)];
+ const char **next_option = options;
+ const resolver_option_t *opt;
+
+ svn_pool_clear(iterpool);
+
+ *next_option++ = "p";
+ if (diff_allowed)
+ {
+ *next_option++ = "df";
+ *next_option++ = "e";
+ *next_option++ = "m";
+
+ if (knows_something)
+ *next_option++ = "r";
+
+ if (! desc->is_binary)
+ {
+ *next_option++ = "mc";
+ *next_option++ = "tc";
+ }
+ }
+ else
+ {
+ if (knows_something)
+ *next_option++ = "r";
+ *next_option++ = "mf";
+ *next_option++ = "tf";
+ }
+ *next_option++ = "s";
+ *next_option++ = NULL;
+
+ SVN_ERR(prompt_user(&opt, text_conflict_options, options, b->pb,
+ iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (strcmp(opt->code, "s") == 0)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n",
+ help_string(text_conflict_options,
+ iterpool)));
+ }
+ else if (strcmp(opt->code, "dc") == 0)
+ {
+ if (desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; cannot "
+ "display conflicts for a "
+ "binary file.\n\n")));
+ continue;
+ }
+ else if (! (desc->my_abspath && desc->base_abspath &&
+ desc->their_abspath))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; original "
+ "files not available.\n\n")));
+ continue;
+ }
+ SVN_ERR(show_conflicts(desc, iterpool));
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "df") == 0)
+ {
+ if (! diff_allowed)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; there's no "
+ "merged version to diff.\n\n")));
+ continue;
+ }
+
+ SVN_ERR(show_diff(desc, b->path_prefix, iterpool));
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "e") == 0 || strcmp(opt->code, ":-E") == 0)
+ {
+ SVN_ERR(open_editor(&performed_edit, desc->merged_file, b, iterpool));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else if (strcmp(opt->code, "m") == 0 || strcmp(opt->code, ":-g") == 0 ||
+ strcmp(opt->code, "=>-") == 0 || strcmp(opt->code, ":>.") == 0)
+ {
+ if (desc->kind != svn_wc_conflict_kind_text)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; can only "
+ "resolve text conflicts with "
+ "the internal merge tool."
+ "\n\n")));
+ continue;
+ }
+
+ if (desc->base_abspath && desc->their_abspath &&
+ desc->my_abspath && desc->merged_file)
+ {
+ svn_boolean_t remains_in_conflict;
+
+ SVN_ERR(svn_cl__merge_file(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
+ desc->merged_file,
+ desc->local_abspath,
+ b->path_prefix,
+ b->editor_cmd,
+ b->config,
+ &remains_in_conflict,
+ iterpool));
+ knows_something = !remains_in_conflict;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option.\n\n")));
+ }
+ else if (strcmp(opt->code, "l") == 0 || strcmp(opt->code, ":-l") == 0)
+ {
+ /* ### This check should be earlier as it's nasty to offer an option
+ * and then when the user chooses it say 'Invalid option'. */
+ /* ### 'merged_file' shouldn't be necessary *before* we launch the
+ * resolver: it should be the *result* of doing so. */
+ if (desc->base_abspath && desc->their_abspath &&
+ desc->my_abspath && desc->merged_file)
+ {
+ SVN_ERR(launch_resolver(&performed_edit, desc, b, iterpool));
+ if (performed_edit)
+ knows_something = TRUE;
+ }
+ else
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option.\n\n")));
+ }
+ else if (opt->choice != -1)
+ {
+ if ((opt->choice == svn_wc_conflict_choose_mine_conflict
+ || opt->choice == svn_wc_conflict_choose_theirs_conflict)
+ && desc->is_binary)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; cannot choose "
+ "based on conflicts in a "
+ "binary file.\n\n")));
+ continue;
+ }
+
+ /* We only allow the user accept the merged version of
+ the file if they've edited it, or at least looked at
+ the diff. */
+ if (result->choice == svn_wc_conflict_choose_merged
+ && ! knows_something)
+ {
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, iterpool,
+ _("Invalid option; use diff/edit/merge/launch "
+ "before choosing 'resolved'.\n\n")));
+ continue;
+ }
+
+ result->choice = opt->choice;
+ if (performed_edit)
+ result->save_merged = TRUE;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the property conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_prop_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ const char *message;
+ const char *merged_file_path = NULL;
+ svn_boolean_t resolved_allowed = FALSE;
+
+ /* ### Work around a historical bug in the provider: the path to the
+ * conflict description file was put in the 'theirs' field, and
+ * 'theirs' was put in the 'merged' field. */
+ ((svn_wc_conflict_description2_t *)desc)->their_abspath = desc->merged_file;
+ ((svn_wc_conflict_description2_t *)desc)->merged_file = NULL;
+
+ SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_property);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
+ _("Conflict for property '%s' discovered"
+ " on '%s'.\n"),
+ desc->property_name,
+ svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath,
+ scratch_pool)));
+
+ SVN_ERR(svn_cl__get_human_readable_prop_conflict_description(&message, desc,
+ scratch_pool));
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", message));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (TRUE)
+ {
+ const resolver_option_t *opt;
+ const char *options[ARRAY_LEN(prop_conflict_options)];
+ const char **next_option = options;
+
+ *next_option++ = "p";
+ *next_option++ = "mf";
+ *next_option++ = "tf";
+ *next_option++ = "dc";
+ *next_option++ = "e";
+ if (resolved_allowed)
+ *next_option++ = "r";
+ *next_option++ = "q";
+ *next_option++ = "h";
+ *next_option++ = NULL;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb,
+ iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (strcmp(opt->code, "dc") == 0)
+ {
+ SVN_ERR(show_prop_conflict(desc, merged_file_path, scratch_pool));
+ }
+ else if (strcmp(opt->code, "e") == 0)
+ {
+ SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b,
+ result_pool, scratch_pool));
+ resolved_allowed = (merged_file_path != NULL);
+ }
+ else if (strcmp(opt->code, "r") == 0)
+ {
+ if (! resolved_allowed)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, iterpool,
+ _("Invalid option; please edit the property "
+ "first.\n\n")));
+ continue;
+ }
+
+ result->merged_file = merged_file_path;
+ result->choice = svn_wc_conflict_choose_merged;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the tree conflict described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_tree_conflict(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ const char *readable_desc;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
+ &readable_desc, desc, scratch_pool));
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, scratch_pool,
+ _("Tree conflict on '%s'\n > %s\n"),
+ svn_cl__local_style_skip_ancestor(b->path_prefix,
+ desc->local_abspath,
+ scratch_pool),
+ readable_desc));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (1)
+ {
+ const resolver_option_t *opt;
+ const resolver_option_t *tc_opts;
+
+ svn_pool_clear(iterpool);
+
+ if (desc->operation == svn_wc_operation_update ||
+ desc->operation == svn_wc_operation_switch)
+ {
+ if (desc->reason == svn_wc_conflict_reason_moved_away)
+ tc_opts = tree_conflict_options_update_moved_away;
+ else if (desc->reason == svn_wc_conflict_reason_deleted)
+ tc_opts = tree_conflict_options_update_deleted;
+ else if (desc->reason == svn_wc_conflict_reason_replaced)
+ tc_opts = tree_conflict_options_update_replaced;
+ else
+ tc_opts = tree_conflict_options;
+ }
+ else
+ tc_opts = tree_conflict_options;
+
+ SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Ask the user what to do about the obstructed add described by DESC.
+ * Return the answer in RESULT. B is the conflict baton for this
+ * conflict resolution session.
+ * SCRATCH_POOL is used for temporary allocations. */
+static svn_error_t *
+handle_obstructed_add(svn_wc_conflict_result_t *result,
+ const svn_wc_conflict_description2_t *desc,
+ svn_cl__interactive_conflict_baton_t *b,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, scratch_pool,
+ _("Conflict discovered when trying to add '%s'.\n"
+ "An object of the same name already exists.\n"),
+ svn_cl__local_style_skip_ancestor(b->path_prefix,
+ desc->local_abspath,
+ scratch_pool)));
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (1)
+ {
+ const resolver_option_t *opt;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(prompt_user(&opt, obstructed_add_options, NULL, b->pb,
+ iterpool));
+ if (! opt)
+ continue;
+
+ if (strcmp(opt->code, "q") == 0)
+ {
+ result->choice = opt->choice;
+ b->accept_which = svn_cl__accept_postpone;
+ b->quit = TRUE;
+ break;
+ }
+ else if (opt->choice != -1)
+ {
+ result->choice = opt->choice;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* The body of svn_cl__conflict_func_interactive(). */
+static svn_error_t *
+conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__interactive_conflict_baton_t *b = baton;
+ svn_error_t *err;
+
+ /* Start out assuming we're going to postpone the conflict. */
+ *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
+ NULL, result_pool);
+
+ switch (b->accept_which)
+ {
+ case svn_cl__accept_invalid:
+ case svn_cl__accept_unspecified:
+ /* No (or no valid) --accept option, fall through to prompting. */
+ break;
+ case svn_cl__accept_postpone:
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_base:
+ (*result)->choice = svn_wc_conflict_choose_base;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_working:
+ /* If the caller didn't merge the property values, then I guess
+ * 'choose working' means 'choose mine'... */
+ if (! desc->merged_file)
+ (*result)->merged_file = desc->my_abspath;
+ (*result)->choice = svn_wc_conflict_choose_merged;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_mine_conflict:
+ (*result)->choice = svn_wc_conflict_choose_mine_conflict;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_theirs_conflict:
+ (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_mine_full:
+ (*result)->choice = svn_wc_conflict_choose_mine_full;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_theirs_full:
+ (*result)->choice = svn_wc_conflict_choose_theirs_full;
+ return SVN_NO_ERROR;
+ case svn_cl__accept_edit:
+ if (desc->merged_file)
+ {
+ if (b->external_failed)
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_cmdline__edit_file_externally(desc->merged_file,
+ b->editor_cmd, b->config,
+ scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("No editor found;"
+ " leaving all conflicts.")));
+ svn_error_clear(err);
+ b->external_failed = TRUE;
+ }
+ else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("Error running editor;"
+ " leaving all conflicts.")));
+ svn_error_clear(err);
+ b->external_failed = TRUE;
+ }
+ else if (err)
+ return svn_error_trace(err);
+ (*result)->choice = svn_wc_conflict_choose_merged;
+ return SVN_NO_ERROR;
+ }
+ /* else, fall through to prompting. */
+ break;
+ case svn_cl__accept_launch:
+ if (desc->base_abspath && desc->their_abspath
+ && desc->my_abspath && desc->merged_file)
+ {
+ svn_boolean_t remains_in_conflict;
+
+ if (b->external_failed)
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ return SVN_NO_ERROR;
+ }
+
+ err = svn_cl__merge_file_externally(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
+ desc->merged_file,
+ desc->local_abspath,
+ b->config,
+ &remains_in_conflict,
+ scratch_pool);
+ if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("No merge tool found;"
+ " leaving all conflicts.")));
+ b->external_failed = TRUE;
+ return svn_error_trace(err);
+ }
+ else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
+ {
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ err->message ? err->message :
+ _("Error running merge tool;"
+ " leaving all conflicts.")));
+ b->external_failed = TRUE;
+ return svn_error_trace(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ if (remains_in_conflict)
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ else
+ (*result)->choice = svn_wc_conflict_choose_merged;
+ return SVN_NO_ERROR;
+ }
+ /* else, fall through to prompting. */
+ break;
+ }
+
+ /* We're in interactive mode and either the user gave no --accept
+ option or the option did not apply; let's prompt. */
+
+ /* Handle the most common cases, which is either:
+
+ Conflicting edits on a file's text, or
+ Conflicting edits on a property.
+ */
+ if (((desc->kind == svn_wc_conflict_kind_text)
+ && (desc->action == svn_wc_conflict_action_edit)
+ && (desc->reason == svn_wc_conflict_reason_edited)))
+ SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool));
+ else if (desc->kind == svn_wc_conflict_kind_property)
+ SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool));
+
+ /*
+ Dealing with obstruction of additions can be tricky. The
+ obstructing item could be unversioned, versioned, or even
+ schedule-add. Here's a matrix of how the caller should behave,
+ based on results we return.
+
+ Unversioned Versioned Schedule-Add
+
+ choose_mine skip addition, skip addition skip addition
+ add existing item
+
+ choose_theirs destroy file, schedule-delete, revert add,
+ add new item. add new item. rm file,
+ add new item
+
+ postpone [ bail out ]
+
+ */
+ else if ((desc->action == svn_wc_conflict_action_add)
+ && (desc->reason == svn_wc_conflict_reason_obstructed))
+ SVN_ERR(handle_obstructed_add(*result, desc, b, scratch_pool));
+
+ else if (desc->kind == svn_wc_conflict_kind_tree)
+ SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool));
+
+ else /* other types of conflicts -- do nothing about them. */
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result,
+ const svn_wc_conflict_description2_t *desc,
+ void *baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__interactive_conflict_baton_t *b = baton;
+
+ SVN_ERR(conflict_func_interactive(result, desc, baton,
+ result_pool, scratch_pool));
+
+ /* If we are resolving a conflict, adjust the summary of conflicts. */
+ if ((*result)->choice != svn_wc_conflict_choose_postpone)
+ {
+ const char *local_path
+ = svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath, scratch_pool);
+
+ svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
+ desc->kind);
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/copy-cmd.c b/subversion/svn/copy-cmd.c
new file mode 100644
index 000000000000..e6fbd4b19570
--- /dev/null
+++ b/subversion/svn/copy-cmd.c
@@ -0,0 +1,184 @@
+/*
+ * copy-cmd.c -- Subversion copy command
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__copy(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets, *sources;
+ const char *src_path, *dst_path;
+ svn_boolean_t srcs_are_urls, dst_is_url;
+ svn_error_t *err;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ /* Get the src list and associated peg revs */
+ sources = apr_array_make(pool, targets->nelts - 1,
+ sizeof(svn_client_copy_source_t *));
+ for (i = 0; i < (targets->nelts - 1); i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_client_copy_source_t *source = apr_palloc(pool, sizeof(*source));
+ const char *src;
+ svn_opt_revision_t *peg_revision = apr_palloc(pool,
+ sizeof(*peg_revision));
+
+ err = svn_opt_parse_path(peg_revision, &src, target, pool);
+
+ if (err)
+ {
+ /* Issue #3606: 'svn cp .@HEAD target' gives
+ svn: '@HEAD' is just a peg revision. Maybe try '@HEAD@' instead?
+
+ This is caused by a first round of canonicalization in
+ svn_cl__args_to_target_array_print_reserved(). Undo that in an
+ attempt to fix this issue without revving many apis.
+ */
+ if (*target == '@' && err->apr_err == SVN_ERR_BAD_FILENAME)
+ {
+ svn_error_t *err2;
+
+ err2 = svn_opt_parse_path(peg_revision, &src,
+ apr_pstrcat(pool, ".", target,
+ (const char *)NULL), pool);
+
+ if (err2)
+ {
+ /* Fix attempt failed; return original error */
+ svn_error_clear(err2);
+ }
+ else
+ {
+ /* Error resolved. Use path */
+ svn_error_clear(err);
+ err = NULL;
+ }
+ }
+
+ if (err)
+ return svn_error_trace(err);
+ }
+
+ source->path = src;
+ source->revision = &(opt_state->start_revision);
+ source->peg_revision = peg_revision;
+
+ APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = source;
+ }
+
+ /* Get DST_PATH (the target path or URL) and check that no peg revision is
+ * specified for it. */
+ {
+ const char *tgt = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *);
+ svn_opt_revision_t peg;
+
+ SVN_ERR(svn_opt_parse_path(&peg, &dst_path, tgt, pool));
+ if (peg.kind != svn_opt_revision_unspecified)
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s': a peg revision is not allowed here"),
+ tgt);
+ }
+
+ /* Figure out which type of notification to use.
+ (There is no need to check that the src paths are homogeneous;
+ svn_client_copy6() through its subroutine try_copy() will return an
+ error if they are not.) */
+ src_path = APR_ARRAY_IDX(targets, 0, const char *);
+ srcs_are_urls = svn_path_is_url(src_path);
+ dst_is_url = svn_path_is_url(dst_path);
+
+ if ((! srcs_are_urls) && (! dst_is_url))
+ {
+ /* WC->WC */
+ }
+ else if ((! srcs_are_urls) && (dst_is_url))
+ {
+ /* WC->URL : Use notification. */
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_wc_to_repos_copy(ctx->notify_baton2));
+ }
+ else if ((srcs_are_urls) && (! dst_is_url))
+ {
+ /* URL->WC : Use checkout-style notification. */
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_checkout(ctx->notify_baton2));
+ }
+ else
+ {
+ /* URL -> URL, meaning that no notification is needed. */
+ ctx->notify_func2 = NULL;
+ }
+
+ if (! dst_is_url)
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+
+ err = svn_client_copy6(sources, dst_path, TRUE,
+ opt_state->parents, opt_state->ignore_externals,
+ opt_state->revprop_table,
+ (opt_state->quiet ? NULL : svn_cl__print_commit_info),
+ NULL,
+ ctx, pool);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/delete-cmd.c b/subversion/svn/delete-cmd.c
new file mode 100644
index 000000000000..e73813b925a7
--- /dev/null
+++ b/subversion/svn/delete-cmd.c
@@ -0,0 +1,95 @@
+/*
+ * delete-cmd.c -- Delete/undelete commands
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__delete(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+ svn_boolean_t is_url;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+ is_url = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
+
+ if (! is_url)
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ err = svn_client_delete4(targets, opt_state->force, opt_state->keep_local,
+ opt_state->revprop_table,
+ (opt_state->quiet
+ ? NULL : svn_cl__print_commit_info),
+ NULL, ctx, pool);
+ if (err)
+ err = svn_cl__may_need_force(err);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/deprecated.c b/subversion/svn/deprecated.c
new file mode 100644
index 000000000000..6115573da0eb
--- /dev/null
+++ b/subversion/svn/deprecated.c
@@ -0,0 +1,41 @@
+/*
+ * deprecated.c: Wrappers to call deprecated functions.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#define SVN_DEPRECATED
+#include "cl.h"
+#include "svn_client.h"
+
+svn_error_t *
+svn_cl__deprecated_merge_reintegrate(const char *source_path_or_url,
+ const svn_opt_revision_t *src_peg_revision,
+ const char *target_wcpath,
+ svn_boolean_t dry_run,
+ const apr_array_header_t *merge_options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_client_merge_reintegrate(source_path_or_url, src_peg_revision,
+ target_wcpath, dry_run, merge_options,
+ ctx, pool));
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/diff-cmd.c b/subversion/svn/diff-cmd.c
new file mode 100644
index 000000000000..2cbd202e3e25
--- /dev/null
+++ b/subversion/svn/diff-cmd.c
@@ -0,0 +1,476 @@
+/*
+ * diff-cmd.c -- Display context diff of a file
+ *
+ * ====================================================================
+ * 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 "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "svn_cmdline.h"
+#include "svn_xml.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Convert KIND into a single character for display to the user. */
+static char
+kind_to_char(svn_client_diff_summarize_kind_t kind)
+{
+ switch (kind)
+ {
+ case svn_client_diff_summarize_kind_modified:
+ return 'M';
+
+ case svn_client_diff_summarize_kind_added:
+ return 'A';
+
+ case svn_client_diff_summarize_kind_deleted:
+ return 'D';
+
+ default:
+ return ' ';
+ }
+}
+
+/* Convert KIND into a word describing the kind to the user. */
+static const char *
+kind_to_word(svn_client_diff_summarize_kind_t kind)
+{
+ switch (kind)
+ {
+ case svn_client_diff_summarize_kind_modified: return "modified";
+ case svn_client_diff_summarize_kind_added: return "added";
+ case svn_client_diff_summarize_kind_deleted: return "deleted";
+ default: return "none";
+ }
+}
+
+/* Baton for summarize_xml and summarize_regular */
+struct summarize_baton_t
+{
+ const char *anchor;
+};
+
+/* Print summary information about a given change as XML, implements the
+ * svn_client_diff_summarize_func_t interface. The @a baton is a 'char *'
+ * representing the either the path to the working copy root or the url
+ * the path the working copy root corresponds to. */
+static svn_error_t *
+summarize_xml(const svn_client_diff_summarize_t *summary,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct summarize_baton_t *b = baton;
+ /* Full path to the object being diffed. This is created by taking the
+ * baton, and appending the target's relative path. */
+ const char *path = b->anchor;
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ /* Tack on the target path, so we can differentiate between different parts
+ * of the output when we're given multiple targets. */
+ if (svn_path_is_url(path))
+ {
+ path = svn_path_url_add_component2(path, summary->path, pool);
+ }
+ else
+ {
+ path = svn_dirent_join(path, summary->path, pool);
+
+ /* Convert non-urls to local style, so that things like ""
+ show up as "." */
+ path = svn_dirent_local_style(path, pool);
+ }
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
+ "kind", svn_cl__node_kind_str_xml(summary->node_kind),
+ "item", kind_to_word(summary->summarize_kind),
+ "props", summary->prop_changed ? "modified" : "none",
+ NULL);
+
+ svn_xml_escape_cdata_cstring(&sb, path, pool);
+ svn_xml_make_close_tag(&sb, pool, "path");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+/* Print summary information about a given change, implements the
+ * svn_client_diff_summarize_func_t interface. */
+static svn_error_t *
+summarize_regular(const svn_client_diff_summarize_t *summary,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct summarize_baton_t *b = baton;
+ const char *path = b->anchor;
+
+ /* Tack on the target path, so we can differentiate between different parts
+ * of the output when we're given multiple targets. */
+ if (svn_path_is_url(path))
+ {
+ path = svn_path_url_add_component2(path, summary->path, pool);
+ }
+ else
+ {
+ path = svn_dirent_join(path, summary->path, pool);
+
+ /* Convert non-urls to local style, so that things like ""
+ show up as "." */
+ path = svn_dirent_local_style(path, pool);
+ }
+
+ /* Note: This output format tries to look like the output of 'svn status',
+ * thus the blank spaces where information that is not relevant to
+ * a diff summary would go. */
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ "%c%c %s\n",
+ kind_to_char(summary->summarize_kind),
+ summary->prop_changed ? 'M' : ' ',
+ path));
+
+ return svn_cmdline_fflush(stdout);
+}
+
+/* An svn_opt_subcommand_t to handle the 'diff' command.
+ This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__diff(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *options;
+ apr_array_header_t *targets;
+ svn_stream_t *outstream;
+ svn_stream_t *errstream;
+ const char *old_target, *new_target;
+ apr_pool_t *iterpool;
+ svn_boolean_t pegged_diff = FALSE;
+ svn_boolean_t show_copies_as_adds =
+ opt_state->diff.patch_compatible || opt_state->diff.show_copies_as_adds;
+ svn_boolean_t ignore_properties =
+ opt_state->diff.patch_compatible || opt_state->diff.ignore_properties;
+ int i;
+ struct summarize_baton_t summarize_baton;
+ const svn_client_diff_summarize_func_t summarize_func =
+ (opt_state->xml ? summarize_xml : summarize_regular);
+
+ if (opt_state->extensions)
+ options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
+ else
+ options = NULL;
+
+ /* Get streams representing stdout and stderr, which is where
+ we'll have the external 'diff' program print to. */
+ SVN_ERR(svn_stream_for_stdout(&outstream, pool));
+ SVN_ERR(svn_stream_for_stderr(&errstream, pool));
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb;
+
+ /* Check that the --summarize is passed as well. */
+ if (!opt_state->diff.summarize)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'--xml' option only valid with "
+ "'--summarize' option"));
+
+ SVN_ERR(svn_cl__xml_print_header("diff", pool));
+
+ sb = svn_stringbuf_create_empty(pool);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", NULL);
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! opt_state->old_target && ! opt_state->new_target
+ && (targets->nelts == 2)
+ && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))
+ || svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))
+ && opt_state->start_revision.kind == svn_opt_revision_unspecified
+ && opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ {
+ /* A 2-target diff where one or both targets are URLs. These are
+ * shorthands for some 'svn diff --old X --new Y' invocations. */
+
+ SVN_ERR(svn_opt_parse_path(&opt_state->start_revision, &old_target,
+ APR_ARRAY_IDX(targets, 0, const char *),
+ pool));
+ SVN_ERR(svn_opt_parse_path(&opt_state->end_revision, &new_target,
+ APR_ARRAY_IDX(targets, 1, const char *),
+ pool));
+ targets->nelts = 0;
+
+ /* Set default start/end revisions based on target types, in the same
+ * manner as done for the corresponding '--old X --new Y' cases,
+ * (note that we have an explicit --new target) */
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
+ opt_state->start_revision.kind = svn_path_is_url(old_target)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = svn_path_is_url(new_target)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+ }
+ else if (opt_state->old_target)
+ {
+ apr_array_header_t *tmp, *tmp2;
+ svn_opt_revision_t old_rev, new_rev;
+
+ /* The 'svn diff --old=OLD[@OLDREV] [--new=NEW[@NEWREV]]
+ [PATH...]' case matches. */
+
+ tmp = apr_array_make(pool, 2, sizeof(const char *));
+ APR_ARRAY_PUSH(tmp, const char *) = (opt_state->old_target);
+ APR_ARRAY_PUSH(tmp, const char *) = (opt_state->new_target
+ ? opt_state->new_target
+ : opt_state->old_target);
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
+ ctx, FALSE, pool));
+
+ /* Check if either or both targets were skipped (e.g. because they
+ * were .svn directories). */
+ if (tmp2->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
+
+ SVN_ERR(svn_opt_parse_path(&old_rev, &old_target,
+ APR_ARRAY_IDX(tmp2, 0, const char *),
+ pool));
+ if (old_rev.kind != svn_opt_revision_unspecified)
+ opt_state->start_revision = old_rev;
+ SVN_ERR(svn_opt_parse_path(&new_rev, &new_target,
+ APR_ARRAY_IDX(tmp2, 1, const char *),
+ pool));
+ if (new_rev.kind != svn_opt_revision_unspecified)
+ opt_state->end_revision = new_rev;
+
+ /* For URLs, default to HEAD. For WC paths, default to WORKING if
+ * new target is explicit; if new target is implicitly the same as
+ * old target, then default the old to BASE and new to WORKING. */
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified)
+ opt_state->start_revision.kind = svn_path_is_url(old_target)
+ ? svn_opt_revision_head
+ : (opt_state->new_target
+ ? svn_opt_revision_working : svn_opt_revision_base);
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = svn_path_is_url(new_target)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+ }
+ else if (opt_state->new_target)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'--new' option only valid with "
+ "'--old' option"));
+ }
+ else
+ {
+ svn_boolean_t working_copy_present;
+
+ /* The 'svn diff [-r N[:M]] [TARGET[@REV]...]' case matches. */
+
+ /* Here each target is a pegged object. Find out the starting
+ and ending paths for each target. */
+
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ old_target = "";
+ new_target = "";
+
+ SVN_ERR_W(svn_cl__assert_homogeneous_target_type(targets),
+ _("'svn diff [-r N[:M]] [TARGET[@REV]...]' does not support mixed "
+ "target types. Try using the --old and --new options or one of "
+ "the shorthand invocations listed in 'svn help diff'."));
+
+ working_copy_present = ! svn_path_is_url(APR_ARRAY_IDX(targets, 0,
+ const char *));
+
+ if (opt_state->start_revision.kind == svn_opt_revision_unspecified
+ && working_copy_present)
+ opt_state->start_revision.kind = svn_opt_revision_base;
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ opt_state->end_revision.kind = working_copy_present
+ ? svn_opt_revision_working : svn_opt_revision_head;
+
+ /* Determine if we need to do pegged diffs. */
+ if ((opt_state->start_revision.kind != svn_opt_revision_base
+ && opt_state->start_revision.kind != svn_opt_revision_working)
+ || (opt_state->end_revision.kind != svn_opt_revision_base
+ && opt_state->end_revision.kind != svn_opt_revision_working))
+ pegged_diff = TRUE;
+
+ }
+
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < targets->nelts; ++i)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ const char *target1, *target2;
+
+ svn_pool_clear(iterpool);
+ if (! pegged_diff)
+ {
+ /* We can't be tacking URLs onto base paths! */
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' not relative to base URLs"),
+ path);
+
+ if (svn_path_is_url(old_target))
+ target1 = svn_path_url_add_component2(
+ old_target,
+ svn_relpath_canonicalize(path, iterpool),
+ iterpool);
+ else
+ target1 = svn_dirent_join(old_target, path, iterpool);
+
+ if (svn_path_is_url(new_target))
+ target2 = svn_path_url_add_component2(
+ new_target,
+ svn_relpath_canonicalize(path, iterpool),
+ iterpool);
+ else
+ target2 = svn_dirent_join(new_target, path, iterpool);
+
+ if (opt_state->diff.summarize)
+ {
+ summarize_baton.anchor = target1;
+
+ SVN_ERR(svn_client_diff_summarize2(
+ target1,
+ &opt_state->start_revision,
+ target2,
+ &opt_state->end_revision,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->changelists,
+ summarize_func, &summarize_baton,
+ ctx, iterpool));
+ }
+ else
+ SVN_ERR(svn_client_diff6(
+ options,
+ target1,
+ &(opt_state->start_revision),
+ target2,
+ &(opt_state->end_revision),
+ NULL,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->diff.no_diff_added,
+ opt_state->diff.no_diff_deleted,
+ show_copies_as_adds,
+ opt_state->force,
+ ignore_properties,
+ opt_state->diff.properties_only,
+ opt_state->diff.use_git_diff_format,
+ svn_cmdline_output_encoding(pool),
+ outstream,
+ errstream,
+ opt_state->changelists,
+ ctx, iterpool));
+ }
+ else
+ {
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ /* First check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, path,
+ iterpool));
+
+ /* Set the default peg revision if one was not specified. */
+ if (peg_revision.kind == svn_opt_revision_unspecified)
+ peg_revision.kind = svn_path_is_url(path)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+
+ if (opt_state->diff.summarize)
+ {
+ summarize_baton.anchor = truepath;
+ SVN_ERR(svn_client_diff_summarize_peg2(
+ truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->changelists,
+ summarize_func, &summarize_baton,
+ ctx, iterpool));
+ }
+ else
+ SVN_ERR(svn_client_diff_peg6(
+ options,
+ truepath,
+ &peg_revision,
+ &opt_state->start_revision,
+ &opt_state->end_revision,
+ NULL,
+ opt_state->depth,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->diff.no_diff_added,
+ opt_state->diff.no_diff_deleted,
+ show_copies_as_adds,
+ opt_state->force,
+ ignore_properties,
+ opt_state->diff.properties_only,
+ opt_state->diff.use_git_diff_format,
+ svn_cmdline_output_encoding(pool),
+ outstream,
+ errstream,
+ opt_state->changelists,
+ ctx, iterpool));
+ }
+ }
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ svn_xml_make_close_tag(&sb, pool, "paths");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ SVN_ERR(svn_cl__xml_print_footer("diff", pool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/export-cmd.c b/subversion/svn/export-cmd.c
new file mode 100644
index 000000000000..75b6723a3a11
--- /dev/null
+++ b/subversion/svn/export-cmd.c
@@ -0,0 +1,128 @@
+/*
+ * export-cmd.c -- Subversion export command
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_opt_private.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__export(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *from, *to;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+ svn_opt_revision_t peg_revision;
+ const char *truefrom;
+ struct svn_cl__check_externals_failed_notify_baton nwb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We want exactly 1 or 2 targets for this subcommand. */
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ /* The first target is the `from' path. */
+ from = APR_ARRAY_IDX(targets, 0, const char *);
+
+ /* Get the peg revision if present. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truefrom, from, pool));
+
+ /* If only one target was given, split off the basename to use as
+ the `to' path. Else, a `to' path was supplied. */
+ if (targets->nelts == 1)
+ {
+ if (svn_path_is_url(truefrom))
+ to = svn_uri_basename(truefrom, pool);
+ else
+ to = svn_dirent_basename(truefrom, pool);
+ }
+ else
+ {
+ to = APR_ARRAY_IDX(targets, 1, const char *);
+
+ if (strcmp("", to) != 0)
+ /* svn_cl__eat_peg_revisions() but only on one target */
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&to, NULL, to, pool));
+ }
+
+ SVN_ERR(svn_cl__check_target_is_local_path(to));
+
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_mark_export(ctx->notify_baton2));
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ nwb.wrapped_func = ctx->notify_func2;
+ nwb.wrapped_baton = ctx->notify_baton2;
+ nwb.had_externals_error = FALSE;
+ ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
+ ctx->notify_baton2 = &nwb;
+
+ /* Do the export. */
+ err = svn_client_export5(NULL, truefrom, to, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->force, opt_state->ignore_externals,
+ opt_state->ignore_keywords, opt_state->depth,
+ opt_state->native_eol, ctx, pool);
+ if (err && err->apr_err == SVN_ERR_WC_OBSTRUCTED_UPDATE && !opt_state->force)
+ SVN_ERR_W(err,
+ _("Destination directory exists; please remove "
+ "the directory or use --force to overwrite"));
+
+ if (nwb.had_externals_error)
+ {
+ svn_error_t *externals_err;
+
+ externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ NULL,
+ _("Failure occurred processing one or "
+ "more externals definitions"));
+ return svn_error_compose_create(externals_err, err);
+ }
+
+ return svn_error_trace(err);
+}
diff --git a/subversion/svn/file-merge.c b/subversion/svn/file-merge.c
new file mode 100644
index 000000000000..43ebba920406
--- /dev/null
+++ b/subversion/svn/file-merge.c
@@ -0,0 +1,959 @@
+/*
+ * file-merge.c: internal file merge tool
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* This is an interactive file merge tool with an interface similar to
+ * the interactive mode of the UNIX sdiff ("side-by-side diff") utility.
+ * The merge tool is driven by Subversion's diff code and user input. */
+
+#include "svn_cmdline.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_xml.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_utf_private.h"
+#include "private/svn_cmdline_private.h"
+#include "private/svn_dep_compat.h"
+
+#if APR_HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#if APR_HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+
+/* Baton for functions in this file which implement svn_diff_output_fns_t. */
+struct file_merge_baton {
+ /* The files being merged. */
+ apr_file_t *original_file;
+ apr_file_t *modified_file;
+ apr_file_t *latest_file;
+
+ /* Counters to keep track of the current line in each file. */
+ svn_linenum_t current_line_original;
+ svn_linenum_t current_line_modified;
+ svn_linenum_t current_line_latest;
+
+ /* The merge result is written to this file. */
+ apr_file_t *merged_file;
+
+ /* Whether the merged file remains in conflict after the merge. */
+ svn_boolean_t remains_in_conflict;
+
+ /* External editor command for editing chunks. */
+ const char *editor_cmd;
+
+ /* The client configuration hash. */
+ apr_hash_t *config;
+
+ /* Wether the merge should be aborted. */
+ svn_boolean_t abort_merge;
+
+ /* Pool for temporary allocations. */
+ apr_pool_t *scratch_pool;
+} file_merge_baton;
+
+/* Copy LEN lines from SOURCE_FILE to the MERGED_FILE, starting at
+ * line START. The CURRENT_LINE is the current line in the source file.
+ * The new current line is returned in *NEW_CURRENT_LINE. */
+static svn_error_t *
+copy_to_merged_file(svn_linenum_t *new_current_line,
+ apr_file_t *merged_file,
+ apr_file_t *source_file,
+ apr_off_t start,
+ apr_off_t len,
+ svn_linenum_t current_line,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ svn_stringbuf_t *line;
+ apr_size_t lines_read;
+ apr_size_t lines_copied;
+ svn_boolean_t eof;
+ svn_linenum_t orig_current_line = current_line;
+
+ lines_read = 0;
+ iterpool = svn_pool_create(scratch_pool);
+ while (current_line < start)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_readline(source_file, &line, NULL, &eof,
+ APR_SIZE_MAX, iterpool, iterpool));
+ if (eof)
+ break;
+
+ current_line++;
+ lines_read++;
+ }
+
+ lines_copied = 0;
+ while (lines_copied < len)
+ {
+ apr_size_t bytes_written;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_readline(source_file, &line, &eol_str, &eof,
+ APR_SIZE_MAX, iterpool, iterpool));
+ if (eol_str)
+ svn_stringbuf_appendcstr(line, eol_str);
+ SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
+ &bytes_written, iterpool));
+ if (bytes_written != line->len)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Could not write data to merged file"));
+ if (eof)
+ break;
+ lines_copied++;
+ }
+ svn_pool_destroy(iterpool);
+
+ *new_current_line = orig_current_line + lines_read + lines_copied;
+
+ return SVN_NO_ERROR;
+}
+
+/* Copy common data to the merged file. */
+static svn_error_t *
+file_merge_output_common(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_original,
+ b->merged_file,
+ b->original_file,
+ original_start,
+ original_length,
+ b->current_line_original,
+ b->scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Original/latest match up, but modified differs.
+ * Copy modified data to the merged file. */
+static svn_error_t *
+file_merge_output_diff_modified(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_modified,
+ b->merged_file,
+ b->modified_file,
+ modified_start,
+ modified_length,
+ b->current_line_modified,
+ b->scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Original/modified match up, but latest differs.
+ * Copy latest data to the merged file. */
+static svn_error_t *
+file_merge_output_diff_latest(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_latest,
+ b->merged_file,
+ b->latest_file,
+ latest_start,
+ latest_length,
+ b->current_line_latest,
+ b->scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Modified/latest match up, but original differs.
+ * Copy latest data to the merged file. */
+static svn_error_t *
+file_merge_output_diff_common(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(copy_to_merged_file(&b->current_line_latest,
+ b->merged_file,
+ b->latest_file,
+ latest_start,
+ latest_length,
+ b->current_line_latest,
+ b->scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Return LEN lines within the diff chunk staring at line START
+ * in a *LINES array of svn_stringbuf_t* elements.
+ * Store the resulting current in in *NEW_CURRENT_LINE. */
+static svn_error_t *
+read_diff_chunk(apr_array_header_t **lines,
+ svn_linenum_t *new_current_line,
+ apr_file_t *file,
+ svn_linenum_t current_line,
+ apr_off_t start,
+ apr_off_t len,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *line;
+ const char *eol_str;
+ svn_boolean_t eof;
+ apr_pool_t *iterpool;
+
+ *lines = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
+
+ /* Skip lines before start of range. */
+ iterpool = svn_pool_create(scratch_pool);
+ while (current_line < start)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_io_file_readline(file, &line, NULL, &eof, APR_SIZE_MAX,
+ iterpool, iterpool));
+ if (eof)
+ return SVN_NO_ERROR;
+ current_line++;
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now read the lines. */
+ do
+ {
+ SVN_ERR(svn_io_file_readline(file, &line, &eol_str, &eof, APR_SIZE_MAX,
+ result_pool, scratch_pool));
+ if (eol_str)
+ svn_stringbuf_appendcstr(line, eol_str);
+ APR_ARRAY_PUSH(*lines, svn_stringbuf_t *) = line;
+ if (eof)
+ break;
+ current_line++;
+ }
+ while ((*lines)->nelts < len);
+
+ *new_current_line = current_line;
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the terminal width in number of columns. */
+static int
+get_term_width(void)
+{
+ char *columns_env;
+#ifdef TIOCGWINSZ
+ int fd;
+
+ fd = open("/dev/tty", O_RDONLY, 0);
+ if (fd != -1)
+ {
+ struct winsize ws;
+ int error;
+
+ error = ioctl(fd, TIOCGWINSZ, &ws);
+ close(fd);
+ if (error != -1)
+ {
+ if (ws.ws_col < 80)
+ return 80;
+ return ws.ws_col;
+ }
+ }
+#elif defined WIN32
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
+ {
+ if (csbi.dwSize.X < 80)
+ return 80;
+ return csbi.dwSize.X;
+ }
+#endif
+
+ columns_env = getenv("COLUMNS");
+ if (columns_env)
+ {
+ svn_error_t *err;
+ int cols;
+
+ err = svn_cstring_atoi(&cols, columns_env);
+ if (err)
+ {
+ svn_error_clear(err);
+ return 80;
+ }
+
+ if (cols < 80)
+ return 80;
+ return cols;
+ }
+ else
+ return 80;
+}
+
+#define LINE_DISPLAY_WIDTH ((get_term_width() / 2) - 2)
+
+/* Prepare LINE for display, pruning or extending it to an appropriate
+ * display width, and stripping the EOL marker, if any.
+ * This function assumes that the data in LINE is encoded in UTF-8. */
+static const char *
+prepare_line_for_display(const char *line, apr_pool_t *pool)
+{
+ svn_stringbuf_t *buf = svn_stringbuf_create(line, pool);
+ size_t width;
+ size_t line_width = LINE_DISPLAY_WIDTH;
+ apr_pool_t *iterpool;
+
+ /* Trim EOL. */
+ if (buf->len >= 2 &&
+ buf->data[buf->len - 2] == '\r' &&
+ buf->data[buf->len - 1] == '\n')
+ svn_stringbuf_chop(buf, 2);
+ else if (buf->len >= 1 &&
+ (buf->data[buf->len - 1] == '\n' ||
+ buf->data[buf->len - 1] == '\r'))
+ svn_stringbuf_chop(buf, 1);
+
+ /* Determine the on-screen width of the line. */
+ width = svn_utf_cstring_utf8_width(buf->data);
+ if (width == -1)
+ {
+ /* Determining the width failed. Try to get rid of unprintable
+ * characters in the line buffer. */
+ buf = svn_stringbuf_create(svn_xml_fuzzy_escape(buf->data, pool), pool);
+ width = svn_utf_cstring_utf8_width(buf->data);
+ if (width == -1)
+ width = buf->len; /* fallback: buffer length */
+ }
+
+ /* Trim further in case line is still too long, or add padding in case
+ * it is too short. */
+ iterpool = svn_pool_create(pool);
+ while (width > line_width)
+ {
+ const char *last_valid;
+
+ svn_pool_clear(iterpool);
+
+ svn_stringbuf_chop(buf, 1);
+
+ /* Be careful not to invalidate the UTF-8 string by trimming
+ * just part of a character. */
+ last_valid = svn_utf__last_valid(buf->data, buf->len);
+ if (last_valid < buf->data + buf->len)
+ svn_stringbuf_chop(buf, (buf->data + buf->len) - last_valid);
+
+ width = svn_utf_cstring_utf8_width(buf->data);
+ if (width == -1)
+ width = buf->len; /* fallback: buffer length */
+ }
+ svn_pool_destroy(iterpool);
+
+ while (width == 0 || width < line_width)
+ {
+ svn_stringbuf_appendbyte(buf, ' ');
+ width++;
+ }
+
+ SVN_ERR_ASSERT_NO_RETURN(width == line_width);
+ return buf->data;
+}
+
+/* Merge CHUNK1 and CHUNK2 into a new chunk with conflict markers. */
+static apr_array_header_t *
+merge_chunks_with_conflict_markers(apr_array_header_t *chunk1,
+ apr_array_header_t *chunk2,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *merged_chunk;
+ int i;
+
+ merged_chunk = apr_array_make(result_pool, 0, sizeof(svn_stringbuf_t *));
+ /* ### would be nice to show filenames next to conflict markers */
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ svn_stringbuf_create("<<<<<<<\n", result_pool);
+ for (i = 0; i < chunk1->nelts; i++)
+ {
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ APR_ARRAY_IDX(chunk1, i, svn_stringbuf_t*);
+ }
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ svn_stringbuf_create("=======\n", result_pool);
+ for (i = 0; i < chunk2->nelts; i++)
+ {
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ APR_ARRAY_IDX(chunk2, i, svn_stringbuf_t*);
+ }
+ APR_ARRAY_PUSH(merged_chunk, svn_stringbuf_t *) =
+ svn_stringbuf_create(">>>>>>>\n", result_pool);
+
+ return merged_chunk;
+}
+
+/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */
+static svn_error_t *
+edit_chunk(apr_array_header_t **merged_chunk,
+ apr_array_header_t *chunk,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_file_t *temp_file;
+ const char *temp_file_name;
+ int i;
+ apr_off_t pos;
+ svn_boolean_t eof;
+ svn_error_t *err;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < chunk->nelts; i++)
+ {
+ svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *);
+ apr_size_t bytes_written;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len,
+ &bytes_written, iterpool));
+ if (line->len != bytes_written)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Could not write data to temporary file"));
+ }
+ SVN_ERR(svn_io_file_flush_to_disk(temp_file, scratch_pool));
+
+ err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd,
+ config, scratch_pool);
+ if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("No editor found.")));
+ svn_error_clear(err);
+ *merged_chunk = NULL;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
+ {
+ svn_error_t *root_err = svn_error_root_cause(err);
+
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
+ root_err->message ? root_err->message :
+ _("Error running editor.")));
+ svn_error_clear(err);
+ *merged_chunk = NULL;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *));
+ pos = 0;
+ SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool));
+ do
+ {
+ svn_stringbuf_t *line;
+ const char *eol_str;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof,
+ APR_SIZE_MAX, result_pool, iterpool));
+ if (eol_str)
+ svn_stringbuf_appendcstr(line, eol_str);
+
+ APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line;
+ }
+ while (!eof);
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_io_file_close(temp_file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Create a separator string of the appropriate length. */
+static const char *
+get_sep_string(apr_pool_t *result_pool)
+{
+ int line_width = LINE_DISPLAY_WIDTH;
+ int i;
+ svn_stringbuf_t *buf;
+
+ buf = svn_stringbuf_create_empty(result_pool);
+ for (i = 0; i < line_width; i++)
+ svn_stringbuf_appendbyte(buf, '-');
+ svn_stringbuf_appendbyte(buf, '+');
+ for (i = 0; i < line_width; i++)
+ svn_stringbuf_appendbyte(buf, '-');
+ svn_stringbuf_appendbyte(buf, '\n');
+
+ return buf->data;
+}
+
+/* Merge chunks CHUNK1 and CHUNK2.
+ * Each lines array contains elements of type svn_stringbuf_t*.
+ * Return the result in *MERGED_CHUNK, or set *MERGED_CHUNK to NULL in
+ * case the user chooses to postpone resolution of this chunk.
+ * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
+static svn_error_t *
+merge_chunks(apr_array_header_t **merged_chunk,
+ svn_boolean_t *abort_merge,
+ apr_array_header_t *chunk1,
+ apr_array_header_t *chunk2,
+ svn_linenum_t current_line1,
+ svn_linenum_t current_line2,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *prompt;
+ int i;
+ int max_chunk_lines;
+ apr_pool_t *iterpool;
+
+ max_chunk_lines = chunk1->nelts > chunk2->nelts ? chunk1->nelts
+ : chunk2->nelts;
+ *abort_merge = FALSE;
+
+ /*
+ * Prepare the selection prompt.
+ */
+
+ prompt = svn_stringbuf_create(
+ apr_psprintf(scratch_pool, "%s\n%s|%s\n%s",
+ _("Conflicting section found during merge:"),
+ prepare_line_for_display(
+ apr_psprintf(scratch_pool,
+ _("(1) their version (at line %lu)"),
+ current_line1),
+ scratch_pool),
+ prepare_line_for_display(
+ apr_psprintf(scratch_pool,
+ _("(2) your version (at line %lu)"),
+ current_line2),
+ scratch_pool),
+ get_sep_string(scratch_pool)),
+ scratch_pool);
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < max_chunk_lines; i++)
+ {
+ const char *line1;
+ const char *line2;
+ const char *prompt_line;
+
+ svn_pool_clear(iterpool);
+
+ if (i < chunk1->nelts)
+ {
+ svn_stringbuf_t *line_utf8;
+
+ SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
+ APR_ARRAY_IDX(chunk1, i,
+ svn_stringbuf_t*),
+ iterpool));
+ line1 = prepare_line_for_display(line_utf8->data, iterpool);
+ }
+ else
+ line1 = prepare_line_for_display("", iterpool);
+
+ if (i < chunk2->nelts)
+ {
+ svn_stringbuf_t *line_utf8;
+
+ SVN_ERR(svn_utf_stringbuf_to_utf8(&line_utf8,
+ APR_ARRAY_IDX(chunk2, i,
+ svn_stringbuf_t*),
+ iterpool));
+ line2 = prepare_line_for_display(line_utf8->data, iterpool);
+ }
+ else
+ line2 = prepare_line_for_display("", iterpool);
+
+ prompt_line = apr_psprintf(iterpool, "%s|%s\n", line1, line2);
+
+ svn_stringbuf_appendcstr(prompt, prompt_line);
+ }
+
+ svn_stringbuf_appendcstr(prompt, get_sep_string(scratch_pool));
+ svn_stringbuf_appendcstr(
+ prompt,
+ _("Select: (1) use their version, (2) use your version,\n"
+ " (e1) edit their version and use the result,\n"
+ " (e2) edit your version and use the result,\n"
+ " (eb) edit both versions and use the result,\n"
+ " (p) postpone this conflicting section leaving conflict markers,\n"
+ " (a) abort file merge and return to main menu: "));
+
+ /* Now let's see what the user wants to do with this conflict. */
+ while (TRUE)
+ {
+ const char *answer;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt->data, NULL, iterpool));
+ if (strcmp(answer, "1") == 0)
+ {
+ *merged_chunk = chunk1;
+ break;
+ }
+ else if (strcmp(answer, "2") == 0)
+ {
+ *merged_chunk = chunk2;
+ break;
+ }
+ else if (strcmp(answer, "p") == 0)
+ {
+ *merged_chunk = NULL;
+ break;
+ }
+ else if (strcmp(answer, "e1") == 0)
+ {
+ SVN_ERR(edit_chunk(merged_chunk, chunk1, editor_cmd, config,
+ result_pool, iterpool));
+ if (*merged_chunk)
+ break;
+ }
+ else if (strcmp(answer, "e2") == 0)
+ {
+ SVN_ERR(edit_chunk(merged_chunk, chunk2, editor_cmd, config,
+ result_pool, iterpool));
+ if (*merged_chunk)
+ break;
+ }
+ else if (strcmp(answer, "eb") == 0)
+ {
+ apr_array_header_t *conflict_chunk;
+
+ conflict_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
+ scratch_pool);
+ SVN_ERR(edit_chunk(merged_chunk, conflict_chunk, editor_cmd, config,
+ result_pool, iterpool));
+ if (*merged_chunk)
+ break;
+ }
+ else if (strcmp(answer, "a") == 0)
+ {
+ *abort_merge = TRUE;
+ break;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform a merge of chunks from FILE1 and FILE2, specified by START1/LEN1
+ * and START2/LEN2, respectively. Append the result to MERGED_FILE.
+ * The current line numbers for FILE1 and FILE2 are passed in *CURRENT_LINE1
+ * and *CURRENT_LINE2, and will be updated to new values upon return.
+ * If the user wants to abort the merge, set *ABORT_MERGE to TRUE. */
+static svn_error_t *
+merge_file_chunks(svn_boolean_t *remains_in_conflict,
+ svn_boolean_t *abort_merge,
+ apr_file_t *merged_file,
+ apr_file_t *file1,
+ apr_file_t *file2,
+ apr_off_t start1,
+ apr_off_t len1,
+ apr_off_t start2,
+ apr_off_t len2,
+ svn_linenum_t *current_line1,
+ svn_linenum_t *current_line2,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *chunk1;
+ apr_array_header_t *chunk2;
+ apr_array_header_t *merged_chunk;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR(read_diff_chunk(&chunk1, current_line1, file1, *current_line1,
+ start1, len1, scratch_pool, scratch_pool));
+ SVN_ERR(read_diff_chunk(&chunk2, current_line2, file2, *current_line2,
+ start2, len2, scratch_pool, scratch_pool));
+
+ SVN_ERR(merge_chunks(&merged_chunk, abort_merge, chunk1, chunk2,
+ *current_line1, *current_line2,
+ editor_cmd, config,
+ scratch_pool, scratch_pool));
+
+ if (*abort_merge)
+ return SVN_NO_ERROR;
+
+ /* If the user chose 'postpone' put conflict markers and left/right
+ * versions into the merged file. */
+ if (merged_chunk == NULL)
+ {
+ *remains_in_conflict = TRUE;
+ merged_chunk = merge_chunks_with_conflict_markers(chunk1, chunk2,
+ scratch_pool);
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < merged_chunk->nelts; i++)
+ {
+ apr_size_t bytes_written;
+ svn_stringbuf_t *line = APR_ARRAY_IDX(merged_chunk, i,
+ svn_stringbuf_t *);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_io_file_write_full(merged_file, line->data, line->len,
+ &bytes_written, iterpool));
+ if (line->len != bytes_written)
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
+ _("Could not write data to merged file"));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Original, modified, and latest all differ from one another.
+ * This is a conflict and we'll need to ask the user to merge it. */
+static svn_error_t *
+file_merge_output_conflict(void *output_baton,
+ apr_off_t original_start,
+ apr_off_t original_length,
+ apr_off_t modified_start,
+ apr_off_t modified_length,
+ apr_off_t latest_start,
+ apr_off_t latest_length,
+ svn_diff_t *resolved_diff)
+{
+ struct file_merge_baton *b = output_baton;
+
+ if (b->abort_merge)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(merge_file_chunks(&b->remains_in_conflict,
+ &b->abort_merge,
+ b->merged_file,
+ b->modified_file,
+ b->latest_file,
+ modified_start,
+ modified_length,
+ latest_start,
+ latest_length,
+ &b->current_line_modified,
+ &b->current_line_latest,
+ b->editor_cmd,
+ b->config,
+ b->scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Our collection of diff output functions that get driven during the merge. */
+static svn_diff_output_fns_t file_merge_diff_output_fns = {
+ file_merge_output_common,
+ file_merge_output_diff_modified,
+ file_merge_output_diff_latest,
+ file_merge_output_diff_common,
+ file_merge_output_conflict
+};
+
+svn_error_t *
+svn_cl__merge_file(const char *base_path,
+ const char *their_path,
+ const char *my_path,
+ const char *merged_path,
+ const char *wc_path,
+ const char *path_prefix,
+ const char *editor_cmd,
+ apr_hash_t *config,
+ svn_boolean_t *remains_in_conflict,
+ apr_pool_t *scratch_pool)
+{
+ svn_diff_t *diff;
+ svn_diff_file_options_t *diff_options;
+ apr_file_t *original_file;
+ apr_file_t *modified_file;
+ apr_file_t *latest_file;
+ apr_file_t *merged_file;
+ const char *merged_file_name;
+ struct file_merge_baton fmb;
+ svn_boolean_t executable;
+ const char *merged_path_local_style;
+ const char *merged_rel_path;
+ const char *wc_path_local_style;
+ const char *wc_rel_path = svn_dirent_skip_ancestor(path_prefix, wc_path);
+
+ /* PATH_PREFIX may not be an ancestor of WC_PATH, just use the
+ full WC_PATH in that case. */
+ if (wc_rel_path)
+ wc_path_local_style = svn_dirent_local_style(wc_rel_path, scratch_pool);
+ else
+ wc_path_local_style = svn_dirent_local_style(wc_path, scratch_pool);
+
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merging '%s'.\n"),
+ wc_path_local_style));
+
+ SVN_ERR(svn_io_file_open(&original_file, base_path,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_file_open(&modified_file, their_path,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_file_open(&latest_file, my_path,
+ APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+ SVN_ERR(svn_io_open_unique_file3(&merged_file, &merged_file_name,
+ NULL, svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+
+ diff_options = svn_diff_file_options_create(scratch_pool);
+ SVN_ERR(svn_diff_file_diff3_2(&diff, base_path, their_path, my_path,
+ diff_options, scratch_pool));
+
+ fmb.original_file = original_file;
+ fmb.modified_file = modified_file;
+ fmb.latest_file = latest_file;
+ fmb.current_line_original = 0;
+ fmb.current_line_modified = 0;
+ fmb.current_line_latest = 0;
+ fmb.merged_file = merged_file;
+ fmb.remains_in_conflict = FALSE;
+ fmb.editor_cmd = editor_cmd;
+ fmb.config = config;
+ fmb.abort_merge = FALSE;
+ fmb.scratch_pool = scratch_pool;
+
+ SVN_ERR(svn_diff_output(diff, &fmb, &file_merge_diff_output_fns));
+
+ SVN_ERR(svn_io_file_close(original_file, scratch_pool));
+ SVN_ERR(svn_io_file_close(modified_file, scratch_pool));
+ SVN_ERR(svn_io_file_close(latest_file, scratch_pool));
+ SVN_ERR(svn_io_file_close(merged_file, scratch_pool));
+
+ /* Start out assuming that conflicts remain. */
+ if (remains_in_conflict)
+ *remains_in_conflict = TRUE;
+
+ if (fmb.abort_merge)
+ {
+ SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("Merge of '%s' aborted.\n"),
+ wc_path_local_style));
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_io_is_file_executable(&executable, merged_path, scratch_pool));
+
+ merged_rel_path = svn_dirent_skip_ancestor(path_prefix, merged_path);
+ if (merged_rel_path)
+ merged_path_local_style = svn_dirent_local_style(merged_rel_path,
+ scratch_pool);
+ else
+ merged_path_local_style = svn_dirent_local_style(merged_path,
+ scratch_pool);
+
+ SVN_ERR_W(svn_io_copy_file(merged_file_name, merged_path, FALSE,
+ scratch_pool),
+ apr_psprintf(scratch_pool,
+ _("Could not write merged result to '%s', saved "
+ "instead at '%s'.\n'%s' remains in conflict.\n"),
+ merged_path_local_style,
+ svn_dirent_local_style(merged_file_name,
+ scratch_pool),
+ wc_path_local_style));
+ SVN_ERR(svn_io_set_file_executable(merged_path, executable, FALSE,
+ scratch_pool));
+ SVN_ERR(svn_io_remove_file2(merged_file_name, TRUE, scratch_pool));
+
+ /* The merge was not aborted and we could install the merged result. The
+ * file remains in conflict unless all conflicting sections were resolved. */
+ if (remains_in_conflict)
+ *remains_in_conflict = fmb.remains_in_conflict;
+
+ if (fmb.remains_in_conflict)
+ SVN_ERR(svn_cmdline_printf(
+ scratch_pool,
+ _("Merge of '%s' completed (remains in conflict).\n"),
+ wc_path_local_style));
+ else
+ SVN_ERR(svn_cmdline_printf(
+ scratch_pool, _("Merge of '%s' completed.\n"),
+ wc_path_local_style));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/help-cmd.c b/subversion/svn/help-cmd.c
new file mode 100644
index 000000000000..93fecc32249b
--- /dev/null
+++ b/subversion/svn/help-cmd.c
@@ -0,0 +1,153 @@
+/*
+ * help-cmd.c -- Provide help
+ *
+ * ====================================================================
+ * 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 "svn_hash.h"
+#include "svn_string.h"
+#include "svn_config.h"
+#include "svn_error.h"
+#include "svn_version.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__help(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = NULL;
+ svn_stringbuf_t *version_footer = NULL;
+
+ /* xgettext: the %s is for SVN_VER_NUMBER. */
+ char help_header_template[] =
+ N_("usage: svn <subcommand> [options] [args]\n"
+ "Subversion command-line client, version %s.\n"
+ "Type 'svn help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svn --version' to see the program version and RA modules\n"
+ " or 'svn --version --quiet' to see just the version number.\n"
+ "\n"
+ "Most subcommands take file and/or directory arguments, recursing\n"
+ "on the directories. If no arguments are supplied to such a\n"
+ "command, it recurses on the current directory (inclusive) by default.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ char help_footer[] =
+ N_("Subversion is a tool for version control.\n"
+ "For additional information, see http://subversion.apache.org/\n");
+
+ char *help_header =
+ apr_psprintf(pool, _(help_header_template), SVN_VER_NUMBER);
+
+ const char *ra_desc_start
+ = _("The following repository access (RA) modules are available:\n\n");
+
+ if (baton)
+ {
+ svn_cl__cmd_baton_t *const cmd_baton = baton;
+#ifndef SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE
+ /* Windows never actually stores plaintext passwords, it
+ encrypts the contents using CryptoAPI. ...
+
+ ... If CryptoAPI is available ... but it should be on all
+ versions of Windows that are even remotely interesting two
+ days before the scheduled end of the world, when this comment
+ is being written. */
+# ifndef WIN32
+ svn_boolean_t store_auth_creds =
+ SVN_CONFIG_DEFAULT_OPTION_STORE_AUTH_CREDS;
+ svn_boolean_t store_passwords =
+ SVN_CONFIG_DEFAULT_OPTION_STORE_PASSWORDS;
+ svn_boolean_t store_plaintext_passwords = FALSE;
+ svn_config_t *cfg;
+
+ if (cmd_baton->ctx->config)
+ {
+ cfg = svn_hash_gets(cmd_baton->ctx->config,
+ SVN_CONFIG_CATEGORY_CONFIG);
+ if (cfg)
+ {
+ SVN_ERR(svn_config_get_bool(cfg, &store_auth_creds,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
+ store_auth_creds));
+ SVN_ERR(svn_config_get_bool(cfg, &store_passwords,
+ SVN_CONFIG_SECTION_AUTH,
+ SVN_CONFIG_OPTION_STORE_PASSWORDS,
+ store_passwords));
+ }
+ cfg = svn_hash_gets(cmd_baton->ctx->config,
+ SVN_CONFIG_CATEGORY_SERVERS);
+ if (cfg)
+ {
+ const char *value;
+ SVN_ERR(svn_config_get_yes_no_ask
+ (cfg, &value,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_STORE_PLAINTEXT_PASSWORDS,
+ SVN_CONFIG_DEFAULT_OPTION_STORE_PLAINTEXT_PASSWORDS));
+ if (0 == svn_cstring_casecmp(value, SVN_CONFIG_TRUE))
+ store_plaintext_passwords = TRUE;
+ }
+ }
+
+ if (store_plaintext_passwords && store_auth_creds && store_passwords)
+ {
+ version_footer = svn_stringbuf_create(
+ _("WARNING: Plaintext password storage is enabled!\n\n"),
+ pool);
+ svn_stringbuf_appendcstr(version_footer, ra_desc_start);
+ }
+# endif /* !WIN32 */
+#endif /* !SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE */
+
+ opt_state = cmd_baton->opt_state;
+ }
+
+ if (!version_footer)
+ version_footer = svn_stringbuf_create(ra_desc_start, pool);
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+
+ return svn_opt_print_help4(os,
+ "svn", /* ### erm, derive somehow? */
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ opt_state ? opt_state->verbose : FALSE,
+ version_footer->data,
+ help_header, /* already gettext()'d */
+ svn_cl__cmd_table,
+ svn_cl__options,
+ svn_cl__global_options,
+ _(help_footer),
+ pool);
+}
diff --git a/subversion/svn/import-cmd.c b/subversion/svn/import-cmd.c
new file mode 100644
index 000000000000..6fe5af6639ad
--- /dev/null
+++ b/subversion/svn/import-cmd.c
@@ -0,0 +1,132 @@
+/*
+ * import-cmd.c -- Import a file or tree into the repository.
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__import(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *path;
+ const char *url;
+
+ /* Import takes two arguments, for example
+ *
+ * $ svn import projects/test file:///home/jrandom/repos/trunk
+ * ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * (source) (repository)
+ *
+ * or
+ *
+ * $ svn import file:///home/jrandom/repos/some/subdir
+ *
+ * What is the nicest behavior for import, from the user's point of
+ * view? This is a subtle question. Seemingly intuitive answers
+ * can lead to weird situations, such never being able to create
+ * non-directories in the top-level of the repository.
+ *
+ * If 'source' is a file then the basename of 'url' is used as the
+ * filename in the repository. If 'source' is a directory then the
+ * import happens directly in the repository target dir, creating
+ * however many new entries are necessary. If some part of 'url'
+ * does not exist in the repository then parent directories are created
+ * as necessary.
+ *
+ * In the case where no 'source' is given '.' (the current directory)
+ * is implied.
+ *
+ * ### kff todo: review above behaviors.
+ */
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (targets->nelts < 1)
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Repository URL required when importing"));
+ else if (targets->nelts > 2)
+ return svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments to import command"));
+ else if (targets->nelts == 1)
+ {
+ url = APR_ARRAY_IDX(targets, 0, const char *);
+ path = "";
+ }
+ else
+ {
+ path = APR_ARRAY_IDX(targets, 0, const char *);
+ url = APR_ARRAY_IDX(targets, 1, const char *);
+ }
+
+ SVN_ERR(svn_cl__check_target_is_local_path(path));
+
+ if (! svn_path_is_url(url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid URL '%s'"), url);
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_infinity;
+
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+
+ SVN_ERR(svn_cl__cleanup_log_msg
+ (ctx->log_msg_baton3,
+ svn_client_import5(path,
+ url,
+ opt_state->depth,
+ opt_state->no_ignore,
+ opt_state->no_autoprops,
+ opt_state->force,
+ opt_state->revprop_table,
+ NULL, NULL, /* filter callback / baton */
+ (opt_state->quiet
+ ? NULL : svn_cl__print_commit_info),
+ NULL,
+ ctx,
+ pool), pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/info-cmd.c b/subversion/svn/info-cmd.c
new file mode 100644
index 000000000000..56833f6edc67
--- /dev/null
+++ b/subversion/svn/info-cmd.c
@@ -0,0 +1,683 @@
+/*
+ * info-cmd.c -- Display information about a resource
+ *
+ * ====================================================================
+ * 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 "svn_string.h"
+#include "svn_cmdline.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "cl-conflicts.h"
+
+
+/*** Code. ***/
+
+static svn_error_t *
+svn_cl__info_print_time(apr_time_t atime,
+ const char *desc,
+ apr_pool_t *pool)
+{
+ const char *time_utf8;
+
+ time_utf8 = svn_time_to_human_cstring(atime, pool);
+ return svn_cmdline_printf(pool, "%s: %s\n", desc, time_utf8);
+}
+
+
+/* Return string representation of SCHEDULE */
+static const char *
+schedule_str(svn_wc_schedule_t schedule)
+{
+ switch (schedule)
+ {
+ case svn_wc_schedule_normal:
+ return "normal";
+ case svn_wc_schedule_add:
+ return "add";
+ case svn_wc_schedule_delete:
+ return "delete";
+ case svn_wc_schedule_replace:
+ return "replace";
+ default:
+ return "none";
+ }
+}
+
+
+/* A callback of type svn_client_info_receiver2_t.
+ Prints svn info in xml mode to standard out */
+static svn_error_t *
+print_info_xml(void *baton,
+ const char *target,
+ const svn_client_info2_t *info,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ const char *rev_str;
+ const char *path_prefix = baton;
+
+ if (SVN_IS_VALID_REVNUM(info->rev))
+ rev_str = apr_psprintf(pool, "%ld", info->rev);
+ else
+ rev_str = apr_pstrdup(pool, _("Resource is not under version control."));
+
+ /* "<entry ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
+ "path", svn_cl__local_style_skip_ancestor(
+ path_prefix, target, pool),
+ "kind", svn_cl__node_kind_str_xml(info->kind),
+ "revision", rev_str,
+ NULL);
+
+ /* "<url> xx </url>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL);
+
+ if (info->repos_root_URL && info->URL)
+ {
+ /* "<relative-url> xx </relative-url>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "relative-url",
+ apr_pstrcat(pool, "^/",
+ svn_path_uri_encode(
+ svn_uri_skip_ancestor(
+ info->repos_root_URL,
+ info->URL, pool),
+ pool),
+ NULL));
+ }
+
+ if (info->repos_root_URL || info->repos_UUID)
+ {
+ /* "<repository>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repository", NULL);
+
+ /* "<root> xx </root>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "root", info->repos_root_URL);
+
+ /* "<uuid> xx </uuid>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "uuid", info->repos_UUID);
+
+ /* "</repository>" */
+ svn_xml_make_close_tag(&sb, pool, "repository");
+ }
+
+ if (info->wc_info)
+ {
+ /* "<wc-info>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "wc-info", NULL);
+
+ /* "<wcroot-abspath> xx </wcroot-abspath>" */
+ if (info->wc_info->wcroot_abspath)
+ svn_cl__xml_tagged_cdata(&sb, pool, "wcroot-abspath",
+ info->wc_info->wcroot_abspath);
+
+ /* "<schedule> xx </schedule>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "schedule",
+ schedule_str(info->wc_info->schedule));
+
+ /* "<depth> xx </depth>" */
+ {
+ svn_depth_t depth = info->wc_info->depth;
+
+ /* In the entries world info just passed depth infinity for files */
+ if (depth == svn_depth_unknown && info->kind == svn_node_file)
+ depth = svn_depth_infinity;
+
+ svn_cl__xml_tagged_cdata(&sb, pool, "depth", svn_depth_to_word(depth));
+ }
+
+ /* "<copy-from-url> xx </copy-from-url>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-url",
+ info->wc_info->copyfrom_url);
+
+ /* "<copy-from-rev> xx </copy-from-rev>" */
+ if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
+ svn_cl__xml_tagged_cdata(&sb, pool, "copy-from-rev",
+ apr_psprintf(pool, "%ld",
+ info->wc_info->copyfrom_rev));
+
+ /* "<text-updated> xx </text-updated>" */
+ if (info->wc_info->recorded_time)
+ svn_cl__xml_tagged_cdata(&sb, pool, "text-updated",
+ svn_time_to_cstring(
+ info->wc_info->recorded_time,
+ pool));
+
+ /* "<checksum> xx </checksum>" */
+ /* ### Print the checksum kind. */
+ svn_cl__xml_tagged_cdata(&sb, pool, "checksum",
+ svn_checksum_to_cstring(info->wc_info->checksum,
+ pool));
+
+ if (info->wc_info->changelist)
+ /* "<changelist> xx </changelist>" */
+ svn_cl__xml_tagged_cdata(&sb, pool, "changelist",
+ info->wc_info->changelist);
+
+ if (info->wc_info->moved_from_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_from_abspath);
+
+ /* <moved-from> xx </moved-from> */
+ if (relpath && relpath[0] != '\0')
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-from", relpath);
+ else
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-from",
+ info->wc_info->moved_from_abspath);
+ }
+
+ if (info->wc_info->moved_to_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_to_abspath);
+ /* <moved-to> xx </moved-to> */
+ if (relpath && relpath[0] != '\0')
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-to", relpath);
+ else
+ svn_cl__xml_tagged_cdata(&sb, pool, "moved-to",
+ info->wc_info->moved_to_abspath);
+ }
+
+ /* "</wc-info>" */
+ svn_xml_make_close_tag(&sb, pool, "wc-info");
+ }
+
+ if (info->last_changed_author
+ || SVN_IS_VALID_REVNUM(info->last_changed_rev)
+ || info->last_changed_date)
+ {
+ svn_cl__print_xml_commit(&sb, info->last_changed_rev,
+ info->last_changed_author,
+ svn_time_to_cstring(info->last_changed_date,
+ pool),
+ pool);
+ }
+
+ if (info->wc_info && info->wc_info->conflicts)
+ {
+ int i;
+
+ for (i = 0; i < info->wc_info->conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(info->wc_info->conflicts, i,
+ const svn_wc_conflict_description2_t *);
+
+ SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool));
+ }
+ }
+
+ if (info->lock)
+ svn_cl__print_xml_lock(&sb, info->lock, pool);
+
+ /* "</entry>" */
+ svn_xml_make_close_tag(&sb, pool, "entry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* A callback of type svn_client_info_receiver2_t. */
+static svn_error_t *
+print_info(void *baton,
+ const char *target,
+ const svn_client_info2_t *info,
+ apr_pool_t *pool)
+{
+ const char *path_prefix = baton;
+
+ SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, target, pool)));
+
+ /* ### remove this someday: it's only here for cmdline output
+ compatibility with svn 1.1 and older. */
+ if (info->kind != svn_node_dir)
+ SVN_ERR(svn_cmdline_printf(pool, _("Name: %s\n"),
+ svn_dirent_basename(target, pool)));
+
+ if (info->wc_info && info->wc_info->wcroot_abspath)
+ SVN_ERR(svn_cmdline_printf(pool, _("Working Copy Root Path: %s\n"),
+ svn_dirent_local_style(
+ info->wc_info->wcroot_abspath,
+ pool)));
+
+ if (info->URL)
+ SVN_ERR(svn_cmdline_printf(pool, _("URL: %s\n"), info->URL));
+
+ if (info->URL && info->repos_root_URL)
+ SVN_ERR(svn_cmdline_printf(pool, _("Relative URL: ^/%s\n"),
+ svn_path_uri_encode(
+ svn_uri_skip_ancestor(info->repos_root_URL,
+ info->URL, pool),
+ pool)));
+
+ if (info->repos_root_URL)
+ SVN_ERR(svn_cmdline_printf(pool, _("Repository Root: %s\n"),
+ info->repos_root_URL));
+
+ if (info->repos_UUID)
+ SVN_ERR(svn_cmdline_printf(pool, _("Repository UUID: %s\n"),
+ info->repos_UUID));
+
+ if (SVN_IS_VALID_REVNUM(info->rev))
+ SVN_ERR(svn_cmdline_printf(pool, _("Revision: %ld\n"), info->rev));
+
+ switch (info->kind)
+ {
+ case svn_node_file:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: file\n")));
+ break;
+
+ case svn_node_dir:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: directory\n")));
+ break;
+
+ case svn_node_none:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: none\n")));
+ break;
+
+ case svn_node_unknown:
+ default:
+ SVN_ERR(svn_cmdline_printf(pool, _("Node Kind: unknown\n")));
+ break;
+ }
+
+ if (info->wc_info)
+ {
+ switch (info->wc_info->schedule)
+ {
+ case svn_wc_schedule_normal:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: normal\n")));
+ break;
+
+ case svn_wc_schedule_add:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: add\n")));
+ break;
+
+ case svn_wc_schedule_delete:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: delete\n")));
+ break;
+
+ case svn_wc_schedule_replace:
+ SVN_ERR(svn_cmdline_printf(pool, _("Schedule: replace\n")));
+ break;
+
+ default:
+ break;
+ }
+
+ switch (info->wc_info->depth)
+ {
+ case svn_depth_unknown:
+ /* Unknown depth is the norm for remote directories anyway
+ (although infinity would be equally appropriate). Let's
+ not bother to print it. */
+ break;
+
+ case svn_depth_empty:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: empty\n")));
+ break;
+
+ case svn_depth_files:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: files\n")));
+ break;
+
+ case svn_depth_immediates:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: immediates\n")));
+ break;
+
+ case svn_depth_exclude:
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: exclude\n")));
+ break;
+
+ case svn_depth_infinity:
+ /* Infinity is the default depth for working copy
+ directories. Let's not print it, it's not special enough
+ to be worth mentioning. */
+ break;
+
+ default:
+ /* Other depths should never happen here. */
+ SVN_ERR(svn_cmdline_printf(pool, _("Depth: INVALID\n")));
+ }
+
+ if (info->wc_info->copyfrom_url)
+ SVN_ERR(svn_cmdline_printf(pool, _("Copied From URL: %s\n"),
+ info->wc_info->copyfrom_url));
+
+ if (SVN_IS_VALID_REVNUM(info->wc_info->copyfrom_rev))
+ SVN_ERR(svn_cmdline_printf(pool, _("Copied From Rev: %ld\n"),
+ info->wc_info->copyfrom_rev));
+ if (info->wc_info->moved_from_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_from_abspath);
+ if (relpath && relpath[0] != '\0')
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"), relpath));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved From: %s\n"),
+ info->wc_info->moved_from_abspath));
+ }
+
+ if (info->wc_info->moved_to_abspath)
+ {
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(info->wc_info->wcroot_abspath,
+ info->wc_info->moved_to_abspath);
+ if (relpath && relpath[0] != '\0')
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"), relpath));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, _("Moved To: %s\n"),
+ info->wc_info->moved_to_abspath));
+ }
+ }
+
+ if (info->last_changed_author)
+ SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Author: %s\n"),
+ info->last_changed_author));
+
+ if (SVN_IS_VALID_REVNUM(info->last_changed_rev))
+ SVN_ERR(svn_cmdline_printf(pool, _("Last Changed Rev: %ld\n"),
+ info->last_changed_rev));
+
+ if (info->last_changed_date)
+ SVN_ERR(svn_cl__info_print_time(info->last_changed_date,
+ _("Last Changed Date"), pool));
+
+ if (info->wc_info)
+ {
+ if (info->wc_info->recorded_time)
+ SVN_ERR(svn_cl__info_print_time(info->wc_info->recorded_time,
+ _("Text Last Updated"), pool));
+
+ if (info->wc_info->checksum)
+ SVN_ERR(svn_cmdline_printf(pool, _("Checksum: %s\n"),
+ svn_checksum_to_cstring(
+ info->wc_info->checksum, pool)));
+
+ if (info->wc_info->conflicts)
+ {
+ svn_boolean_t printed_prop_conflict_file = FALSE;
+ int i;
+
+ for (i = 0; i < info->wc_info->conflicts->nelts; i++)
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(info->wc_info->conflicts, i,
+ const svn_wc_conflict_description2_t *);
+ const char *desc;
+
+ switch (conflict->kind)
+ {
+ case svn_wc_conflict_kind_text:
+ if (conflict->base_abspath)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Previous Base File: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, conflict->base_abspath,
+ pool)));
+
+ if (conflict->my_abspath)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Previous Working File: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, conflict->my_abspath,
+ pool)));
+
+ if (conflict->their_abspath)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Current Base File: %s\n"),
+ svn_cl__local_style_skip_ancestor(
+ path_prefix, conflict->their_abspath,
+ pool)));
+ break;
+
+ case svn_wc_conflict_kind_property:
+ if (! printed_prop_conflict_file)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Conflict Properties File: %s\n"),
+ svn_dirent_local_style(conflict->their_abspath,
+ pool)));
+ printed_prop_conflict_file = TRUE;
+ break;
+
+ case svn_wc_conflict_kind_tree:
+ SVN_ERR(
+ svn_cl__get_human_readable_tree_conflict_description(
+ &desc, conflict, pool));
+
+ SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
+ _("Tree conflict"), desc));
+ break;
+ }
+ }
+
+ /* We only store one left and right version for all conflicts, which is
+ referenced from all conflicts.
+ Print it after the conflicts to match the 1.6/1.7 output where it is
+ only available for tree conflicts */
+ {
+ const char *src_left_version;
+ const char *src_right_version;
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(info->wc_info->conflicts, 0,
+ const svn_wc_conflict_description2_t *);
+
+ src_left_version =
+ svn_cl__node_description(conflict->src_left_version,
+ info->repos_root_URL, pool);
+
+ src_right_version =
+ svn_cl__node_description(conflict->src_right_version,
+ info->repos_root_URL, pool);
+
+ if (src_left_version)
+ SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
+ _("Source left"), /* (1) */
+ src_left_version));
+ /* (1): Sneaking in a space in "Source left" so that
+ * it is the same length as "Source right" while it still
+ * starts in the same column. That's just a tiny tweak in
+ * the English `svn'. */
+
+ if (src_right_version)
+ SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n",
+ _("Source right"),
+ src_right_version));
+ }
+ }
+ }
+
+ if (info->lock)
+ {
+ if (info->lock->token)
+ SVN_ERR(svn_cmdline_printf(pool, _("Lock Token: %s\n"),
+ info->lock->token));
+
+ if (info->lock->owner)
+ SVN_ERR(svn_cmdline_printf(pool, _("Lock Owner: %s\n"),
+ info->lock->owner));
+
+ if (info->lock->creation_date)
+ SVN_ERR(svn_cl__info_print_time(info->lock->creation_date,
+ _("Lock Created"), pool));
+
+ if (info->lock->expiration_date)
+ SVN_ERR(svn_cl__info_print_time(info->lock->expiration_date,
+ _("Lock Expires"), pool));
+
+ if (info->lock->comment)
+ {
+ int comment_lines;
+ /* NOTE: The stdio will handle newline translation. */
+ comment_lines = svn_cstring_count_newlines(info->lock->comment) + 1;
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_("Lock Comment (%i line):\n%s\n",
+ "Lock Comment (%i lines):\n%s\n",
+ comment_lines),
+ comment_lines,
+ info->lock->comment));
+ }
+ }
+
+ if (info->wc_info && info->wc_info->changelist)
+ SVN_ERR(svn_cmdline_printf(pool, _("Changelist: %s\n"),
+ info->wc_info->changelist));
+
+ /* Print extra newline separator. */
+ return svn_cmdline_printf(pool, "\n");
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__info(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets = NULL;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ int i;
+ svn_error_t *err;
+ svn_boolean_t seen_nonexistent_target = FALSE;
+ svn_opt_revision_t peg_revision;
+ svn_client_info_receiver2_t receiver;
+ const char *path_prefix;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments. */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ if (opt_state->xml)
+ {
+ receiver = print_info_xml;
+
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("info", pool));
+ }
+ else
+ {
+ receiver = print_info;
+
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *truepath;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool));
+
+ /* If no peg-rev was attached to a URL target, then assume HEAD. */
+ if (svn_path_is_url(truepath))
+ {
+ if (peg_revision.kind == svn_opt_revision_unspecified)
+ peg_revision.kind = svn_opt_revision_head;
+ }
+ else
+ {
+ SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
+ }
+
+ err = svn_client_info3(truepath,
+ &peg_revision, &(opt_state->start_revision),
+ opt_state->depth, TRUE, TRUE,
+ opt_state->changelists,
+ receiver, (void *) path_prefix,
+ ctx, subpool);
+
+ if (err)
+ {
+ /* If one of the targets is a non-existent URL or wc-entry,
+ don't bail out. Just warn and move on to the next target. */
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(svn_cmdline_fprintf(stderr, subpool, "\n"));
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+ }
+ svn_pool_destroy(subpool);
+
+ if (opt_state->xml && (! opt_state->incremental))
+ SVN_ERR(svn_cl__xml_print_footer("info", pool));
+
+ if (seen_nonexistent_target)
+ return svn_error_create(
+ SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not display info for all targets because some "
+ "targets don't exist"));
+ else
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/list-cmd.c b/subversion/svn/list-cmd.c
new file mode 100644
index 000000000000..efe427942d5f
--- /dev/null
+++ b/subversion/svn/list-cmd.c
@@ -0,0 +1,424 @@
+/*
+ * list-cmd.c -- list a URL
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_opt.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Baton used when printing directory entries. */
+struct print_baton {
+ svn_boolean_t verbose;
+ svn_client_ctx_t *ctx;
+
+ /* To keep track of last seen external information. */
+ const char *last_external_parent_url;
+ const char *last_external_target;
+ svn_boolean_t in_external;
+};
+
+/* This implements the svn_client_list_func2_t API, printing a single
+ directory entry in text format. */
+static svn_error_t *
+print_dirent(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *scratch_pool)
+{
+ struct print_baton *pb = baton;
+ const char *entryname;
+ static const char *time_format_long = NULL;
+ static const char *time_format_short = NULL;
+
+ SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
+ (external_parent_url && external_target));
+
+ if (time_format_long == NULL)
+ time_format_long = _("%b %d %H:%M");
+ if (time_format_short == NULL)
+ time_format_short = _("%b %d %Y");
+
+ if (pb->ctx->cancel_func)
+ SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
+
+ if (strcmp(path, "") == 0)
+ {
+ if (dirent->kind == svn_node_file)
+ entryname = svn_dirent_basename(abs_path, scratch_pool);
+ else if (pb->verbose)
+ entryname = ".";
+ else
+ /* Don't bother to list if no useful information will be shown. */
+ return SVN_NO_ERROR;
+ }
+ else
+ entryname = path;
+
+ if (external_parent_url && external_target)
+ {
+ if ((pb->last_external_parent_url == NULL
+ && pb->last_external_target == NULL)
+ || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
+ || strcmp(pb->last_external_target, external_target) != 0))
+ {
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("Listing external '%s'"
+ " defined on '%s':\n"),
+ external_target,
+ external_parent_url));
+
+ pb->last_external_parent_url = external_parent_url;
+ pb->last_external_target = external_target;
+ }
+ }
+
+ if (pb->verbose)
+ {
+ apr_time_t now = apr_time_now();
+ apr_time_exp_t exp_time;
+ apr_status_t apr_err;
+ apr_size_t size;
+ char timestr[20];
+ const char *sizestr, *utf8_timestr;
+
+ /* svn_time_to_human_cstring gives us something *way* too long
+ to use for this, so we have to roll our own. We include
+ the year if the entry's time is not within half a year. */
+ apr_time_exp_lt(&exp_time, dirent->time);
+ if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2)
+ && apr_time_sec(dirent->time - now) < (365 * 86400 / 2))
+ {
+ apr_err = apr_strftime(timestr, &size, sizeof(timestr),
+ time_format_long, &exp_time);
+ }
+ else
+ {
+ apr_err = apr_strftime(timestr, &size, sizeof(timestr),
+ time_format_short, &exp_time);
+ }
+
+ /* if that failed, just zero out the string and print nothing */
+ if (apr_err)
+ timestr[0] = '\0';
+
+ /* we need it in UTF-8. */
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool));
+
+ sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT,
+ dirent->size);
+
+ return svn_cmdline_printf
+ (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n",
+ dirent->created_rev,
+ dirent->last_author ? dirent->last_author : " ? ",
+ lock ? 'O' : ' ',
+ (dirent->kind == svn_node_file) ? sizestr : "",
+ utf8_timestr,
+ entryname,
+ (dirent->kind == svn_node_dir) ? "/" : "");
+ }
+ else
+ {
+ return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname,
+ (dirent->kind == svn_node_dir)
+ ? "/" : "");
+ }
+}
+
+
+/* This implements the svn_client_list_func2_t API, printing a single dirent
+ in XML format. */
+static svn_error_t *
+print_dirent_xml(void *baton,
+ const char *path,
+ const svn_dirent_t *dirent,
+ const svn_lock_t *lock,
+ const char *abs_path,
+ const char *external_parent_url,
+ const char *external_target,
+ apr_pool_t *scratch_pool)
+{
+ struct print_baton *pb = baton;
+ const char *entryname;
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool);
+
+ SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
+ (external_parent_url && external_target));
+
+ if (strcmp(path, "") == 0)
+ {
+ if (dirent->kind == svn_node_file)
+ entryname = svn_dirent_basename(abs_path, scratch_pool);
+ else
+ /* Don't bother to list if no useful information will be shown. */
+ return SVN_NO_ERROR;
+ }
+ else
+ entryname = path;
+
+ if (pb->ctx->cancel_func)
+ SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
+
+ if (external_parent_url && external_target)
+ {
+ if ((pb->last_external_parent_url == NULL
+ && pb->last_external_target == NULL)
+ || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
+ || strcmp(pb->last_external_target, external_target) != 0))
+ {
+ if (pb->in_external)
+ {
+ /* The external item being listed is different from the previous
+ one, so close the tag. */
+ svn_xml_make_close_tag(&sb, scratch_pool, "external");
+ pb->in_external = FALSE;
+ }
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external",
+ "parent_url", external_parent_url,
+ "target", external_target,
+ NULL);
+
+ pb->last_external_parent_url = external_parent_url;
+ pb->last_external_target = external_target;
+ pb->in_external = TRUE;
+ }
+ }
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry",
+ "kind", svn_cl__node_kind_str_xml(dirent->kind),
+ NULL);
+
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname);
+
+ if (dirent->kind == svn_node_file)
+ {
+ svn_cl__xml_tagged_cdata
+ (&sb, scratch_pool, "size",
+ apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size));
+ }
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit",
+ "revision",
+ apr_psprintf(scratch_pool, "%ld", dirent->created_rev),
+ NULL);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author);
+ if (dirent->time)
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date",
+ svn_time_to_cstring(dirent->time, scratch_pool));
+ svn_xml_make_close_tag(&sb, scratch_pool, "commit");
+
+ if (lock)
+ {
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock", NULL);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created",
+ svn_time_to_cstring(lock->creation_date,
+ scratch_pool));
+ if (lock->expiration_date != 0)
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
+ svn_time_to_cstring
+ (lock->expiration_date, scratch_pool));
+ svn_xml_make_close_tag(&sb, scratch_pool, "lock");
+ }
+
+ svn_xml_make_close_tag(&sb, scratch_pool, "entry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__list(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_uint32_t dirent_fields;
+ struct print_baton pb;
+ svn_boolean_t seen_nonexistent_target = FALSE;
+ svn_error_t *err;
+ svn_error_t *externals_err = SVN_NO_ERROR;
+ struct svn_cl__check_externals_failed_notify_baton nwb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ if (opt_state->xml)
+ {
+ /* The XML output contains all the information, so "--verbose"
+ does not apply. */
+ if (opt_state->verbose)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'verbose' option invalid in XML mode"));
+
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("lists", pool));
+ }
+ else
+ {
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ if (opt_state->verbose || opt_state->xml)
+ dirent_fields = SVN_DIRENT_ALL;
+ else
+ dirent_fields = SVN_DIRENT_KIND; /* the only thing we actually need... */
+
+ pb.ctx = ctx;
+ pb.verbose = opt_state->verbose;
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_immediates;
+
+ if (opt_state->include_externals)
+ {
+ nwb.wrapped_func = ctx->notify_func2;
+ nwb.wrapped_baton = ctx->notify_baton2;
+ nwb.had_externals_error = FALSE;
+ ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
+ ctx->notify_baton2 = &nwb;
+ }
+
+ /* For each target, try to list it. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ /* Initialize the following variables for
+ every list target. */
+ pb.last_external_parent_url = NULL;
+ pb.last_external_target = NULL;
+ pb.in_external = FALSE;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Get peg revisions. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list",
+ "path", truepath[0] == '\0' ? "." : truepath,
+ NULL);
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ err = svn_client_list3(truepath, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ dirent_fields,
+ (opt_state->xml || opt_state->verbose),
+ opt_state->include_externals,
+ opt_state->xml ? print_dirent_xml : print_dirent,
+ &pb, ctx, subpool);
+
+ if (err)
+ {
+ /* If one of the targets is a non-existent URL or wc-entry,
+ don't bail out. Just warn and move on to the next target. */
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ svn_handle_warning2(stderr, err, "svn: ");
+ else
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ seen_nonexistent_target = TRUE;
+ }
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ if (pb.in_external)
+ {
+ /* close the final external item's tag */
+ svn_xml_make_close_tag(&sb, pool, "external");
+ pb.in_external = FALSE;
+ }
+
+ svn_xml_make_close_tag(&sb, pool, "list");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ if (opt_state->include_externals && nwb.had_externals_error)
+ {
+ externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ NULL,
+ _("Failure occurred processing one or "
+ "more externals definitions"));
+ }
+
+ if (opt_state->xml && ! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_footer("lists", pool));
+
+ if (seen_nonexistent_target)
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Could not list all targets because some targets don't exist"));
+
+ return svn_error_compose_create(externals_err, err);
+}
diff --git a/subversion/svn/lock-cmd.c b/subversion/svn/lock-cmd.c
new file mode 100644
index 000000000000..c2795da9add8
--- /dev/null
+++ b/subversion/svn/lock-cmd.c
@@ -0,0 +1,110 @@
+/*
+ * lock-cmd.c -- LOck a working copy path in the repository.
+ *
+ * ====================================================================
+ * 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 "svn_pools.h"
+#include "svn_client.h"
+#include "svn_subst.h"
+#include "svn_path.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_cmdline.h"
+#include "cl.h"
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Get a lock comment, allocate it in POOL and store it in *COMMENT. */
+static svn_error_t *
+get_comment(const char **comment, svn_client_ctx_t *ctx,
+ svn_cl__opt_state_t *opt_state, apr_pool_t *pool)
+{
+ svn_string_t *comment_string;
+
+ if (opt_state->filedata)
+ {
+ /* Get it from the -F argument. */
+ if (strlen(opt_state->filedata->data) < opt_state->filedata->len)
+ {
+ /* A message containing a zero byte can't be represented as a C
+ string. */
+ return svn_error_create(SVN_ERR_CL_BAD_LOG_MESSAGE, NULL,
+ _("Lock comment contains a zero byte"));
+ }
+ comment_string = svn_string_create(opt_state->filedata->data, pool);
+
+ }
+ else if (opt_state->message)
+ {
+ /* Get if from the -m option. */
+ comment_string = svn_string_create(opt_state->message, pool);
+ }
+ else
+ {
+ *comment = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Translate to UTF8/LF. */
+ SVN_ERR(svn_subst_translate_string2(&comment_string, NULL, NULL,
+ comment_string, opt_state->encoding,
+ FALSE, pool, pool));
+ *comment = comment_string->data;
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__lock(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *comment;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We only support locking files, so '.' is not valid. */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+
+ /* Get comment. */
+ SVN_ERR(get_comment(&comment, ctx, opt_state, pool));
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ return svn_client_lock(targets, comment, opt_state->force, ctx, pool);
+}
diff --git a/subversion/svn/log-cmd.c b/subversion/svn/log-cmd.c
new file mode 100644
index 000000000000..af57cf4de5b3
--- /dev/null
+++ b/subversion/svn/log-cmd.c
@@ -0,0 +1,875 @@
+/*
+ * log-cmd.c -- Display log messages
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#include <apr_fnmatch.h>
+
+#include "svn_client.h"
+#include "svn_compat.h"
+#include "svn_dirent_uri.h"
+#include "svn_string.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_sorts.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "svn_cmdline.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Baton for log_entry_receiver() and log_entry_receiver_xml(). */
+struct log_receiver_baton
+{
+ /* Client context. */
+ svn_client_ctx_t *ctx;
+
+ /* The target of the log operation. */
+ const char *target_path_or_url;
+ svn_opt_revision_t target_peg_revision;
+
+ /* Don't print log message body nor its line count. */
+ svn_boolean_t omit_log_message;
+
+ /* Whether to show diffs in the log. (maps to --diff) */
+ svn_boolean_t show_diff;
+
+ /* Depth applied to diff output. */
+ svn_depth_t depth;
+
+ /* Diff arguments received from command line. */
+ const char *diff_extensions;
+
+ /* Stack which keeps track of merge revision nesting, using svn_revnum_t's */
+ apr_array_header_t *merge_stack;
+
+ /* Log message search patterns. Log entries will only be shown if the author,
+ * the log message, or a changed path matches one of these patterns. */
+ apr_array_header_t *search_patterns;
+
+ /* Pool for persistent allocations. */
+ apr_pool_t *pool;
+};
+
+
+/* The separator between log messages. */
+#define SEP_STRING \
+ "------------------------------------------------------------------------\n"
+
+
+/* Display a diff of the subtree TARGET_PATH_OR_URL@TARGET_PEG_REVISION as
+ * it changed in the revision that LOG_ENTRY describes.
+ *
+ * Restrict the diff to depth DEPTH. Pass DIFF_EXTENSIONS along to the diff
+ * subroutine.
+ *
+ * Write the diff to OUTSTREAM and write any stderr output to ERRSTREAM.
+ * ### How is exit code handled? 0 and 1 -> SVN_NO_ERROR, else an svn error?
+ * ### Should we get rid of ERRSTREAM and use svn_error_t instead?
+ */
+static svn_error_t *
+display_diff(const svn_log_entry_t *log_entry,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_peg_revision,
+ svn_depth_t depth,
+ const char *diff_extensions,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *diff_options;
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+
+ /* Fall back to "" to get options initialized either way. */
+ if (diff_extensions)
+ diff_options = svn_cstring_split(diff_extensions, " \t\n\r",
+ TRUE, pool);
+ else
+ diff_options = NULL;
+
+ start_revision.kind = svn_opt_revision_number;
+ start_revision.value.number = log_entry->revision - 1;
+ end_revision.kind = svn_opt_revision_number;
+ end_revision.value.number = log_entry->revision;
+
+ SVN_ERR(svn_stream_puts(outstream, "\n"));
+ SVN_ERR(svn_client_diff_peg6(diff_options,
+ target_path_or_url,
+ target_peg_revision,
+ &start_revision, &end_revision,
+ NULL,
+ depth,
+ FALSE /* ignore ancestry */,
+ FALSE /* no diff added */,
+ TRUE /* no diff deleted */,
+ FALSE /* show copies as adds */,
+ FALSE /* ignore content type */,
+ FALSE /* ignore prop diff */,
+ FALSE /* properties only */,
+ FALSE /* use git diff format */,
+ svn_cmdline_output_encoding(pool),
+ outstream,
+ errstream,
+ NULL,
+ ctx, pool));
+ SVN_ERR(svn_stream_puts(outstream, _("\n")));
+ return SVN_NO_ERROR;
+}
+
+
+/* Return TRUE if SEARCH_PATTERN matches the AUTHOR, DATE, LOG_MESSAGE,
+ * or a path in the set of keys of the CHANGED_PATHS hash. Else, return FALSE.
+ * Any of AUTHOR, DATE, LOG_MESSAGE, and CHANGED_PATHS may be NULL. */
+static svn_boolean_t
+match_search_pattern(const char *search_pattern,
+ const char *author,
+ const char *date,
+ const char *log_message,
+ apr_hash_t *changed_paths,
+ apr_pool_t *pool)
+{
+ /* Match any substring containing the pattern, like UNIX 'grep' does. */
+ const char *pattern = apr_psprintf(pool, "*%s*", search_pattern);
+ int flags = 0;
+
+ /* Does the author match the search pattern? */
+ if (author && apr_fnmatch(pattern, author, flags) == APR_SUCCESS)
+ return TRUE;
+
+ /* Does the date the search pattern? */
+ if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS)
+ return TRUE;
+
+ /* Does the log message the search pattern? */
+ if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS)
+ return TRUE;
+
+ if (changed_paths)
+ {
+ apr_hash_index_t *hi;
+
+ /* Does a changed path match the search pattern? */
+ for (hi = apr_hash_first(pool, changed_paths);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_log_changed_path2_t *log_item;
+
+ if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS)
+ return TRUE;
+
+ /* Match copy-from paths, too. */
+ log_item = svn__apr_hash_index_val(hi);
+ if (log_item->copyfrom_path
+ && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev)
+ && apr_fnmatch(pattern,
+ log_item->copyfrom_path, flags) == APR_SUCCESS)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* Match all search patterns in SEARCH_PATTERNS against AUTHOR, DATE, MESSAGE,
+ * and CHANGED_PATHS. Return TRUE if any pattern matches, else FALSE.
+ * SCRACH_POOL is used for temporary allocations. */
+static svn_boolean_t
+match_search_patterns(apr_array_header_t *search_patterns,
+ const char *author,
+ const char *date,
+ const char *message,
+ apr_hash_t *changed_paths,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ svn_boolean_t match = FALSE;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < search_patterns->nelts; i++)
+ {
+ apr_array_header_t *pattern_group;
+ int j;
+
+ pattern_group = APR_ARRAY_IDX(search_patterns, i, apr_array_header_t *);
+
+ /* All patterns within the group must match. */
+ for (j = 0; j < pattern_group->nelts; j++)
+ {
+ const char *pattern;
+
+ svn_pool_clear(iterpool);
+
+ pattern = APR_ARRAY_IDX(pattern_group, j, const char *);
+ match = match_search_pattern(pattern, author, date, message,
+ changed_paths, iterpool);
+ if (!match)
+ break;
+ }
+
+ match = (match && j == pattern_group->nelts);
+ if (match)
+ break;
+ }
+ svn_pool_destroy(iterpool);
+
+ return match;
+}
+
+/* Implement `svn_log_entry_receiver_t', printing the logs in
+ * a human-readable and machine-parseable format.
+ *
+ * BATON is of type `struct log_receiver_baton'.
+ *
+ * First, print a header line. Then if CHANGED_PATHS is non-null,
+ * print all affected paths in a list headed "Changed paths:\n",
+ * immediately following the header line. Then print a newline
+ * followed by the message body, unless BATON->omit_log_message is true.
+ *
+ * Here are some examples of the output:
+ *
+ * $ svn log -r1847:1846
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
+ *
+ * Fix for Issue #694.
+ *
+ * * subversion/libsvn_repos/delta.c
+ * (delta_files): Rework the logic in this function to only call
+ * send_text_deltas if there are deltas to send, and within that case,
+ * only use a real delta stream if the caller wants real text deltas.
+ *
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
+ *
+ * imagine an example log message here
+ * ------------------------------------------------------------------------
+ *
+ * Or:
+ *
+ * $ svn log -r1847:1846 -v
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26 | 7 lines
+ * Changed paths:
+ * M /trunk/subversion/libsvn_repos/delta.c
+ *
+ * Fix for Issue #694.
+ *
+ * * subversion/libsvn_repos/delta.c
+ * (delta_files): Rework the logic in this function to only call
+ * send_text_deltas if there are deltas to send, and within that case,
+ * only use a real delta stream if the caller wants real text deltas.
+ *
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41 | 1 line
+ * Changed paths:
+ * M /trunk/notes/fs_dumprestore.txt
+ * M /trunk/subversion/libsvn_repos/dump.c
+ *
+ * imagine an example log message here
+ * ------------------------------------------------------------------------
+ *
+ * Or:
+ *
+ * $ svn log -r1847:1846 -q
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41
+ * ------------------------------------------------------------------------
+ *
+ * Or:
+ *
+ * $ svn log -r1847:1846 -qv
+ * ------------------------------------------------------------------------
+ * rev 1847: cmpilato | Wed 1 May 2002 15:44:26
+ * Changed paths:
+ * M /trunk/subversion/libsvn_repos/delta.c
+ * ------------------------------------------------------------------------
+ * rev 1846: whoever | Wed 1 May 2002 15:23:41
+ * Changed paths:
+ * M /trunk/notes/fs_dumprestore.txt
+ * M /trunk/subversion/libsvn_repos/dump.c
+ * ------------------------------------------------------------------------
+ *
+ */
+static svn_error_t *
+log_entry_receiver(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_receiver_baton *lb = baton;
+ const char *author;
+ const char *date;
+ const char *message;
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+ if (log_entry->revision == 0 && message == NULL)
+ return SVN_NO_ERROR;
+
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ apr_array_pop(lb->merge_stack);
+ return SVN_NO_ERROR;
+ }
+
+ /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807
+ for more on the fallback fuzzy conversions below. */
+
+ if (author == NULL)
+ author = _("(no author)");
+
+ if (date && date[0])
+ /* Convert date to a format for humans. */
+ SVN_ERR(svn_cl__time_cstring_to_human_cstring(&date, date, pool));
+ else
+ date = _("(no date)");
+
+ if (! lb->omit_log_message && message == NULL)
+ message = "";
+
+ if (lb->search_patterns &&
+ ! match_search_patterns(lb->search_patterns, author, date, message,
+ log_entry->changed_paths2, pool))
+ {
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ SEP_STRING "r%ld | %s | %s",
+ log_entry->revision, author, date));
+
+ if (message != NULL)
+ {
+ /* Number of lines in the msg. */
+ int lines = svn_cstring_count_newlines(message) + 1;
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_(" | %d line", " | %d lines", lines),
+ lines));
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool, "\n"));
+
+ if (log_entry->changed_paths2)
+ {
+ apr_array_header_t *sorted_paths;
+ int i;
+
+ /* Get an array of sorted hash keys. */
+ sorted_paths = svn_sort__hash(log_entry->changed_paths2,
+ svn_sort_compare_items_as_paths, pool);
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Changed paths:\n")));
+ for (i = 0; i < sorted_paths->nelts; i++)
+ {
+ svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
+ svn_sort__item_t));
+ const char *path = item->key;
+ svn_log_changed_path2_t *log_item = item->value;
+ const char *copy_data = "";
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ if (log_item->copyfrom_path
+ && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
+ {
+ copy_data
+ = apr_psprintf(pool,
+ _(" (from %s:%ld)"),
+ log_item->copyfrom_path,
+ log_item->copyfrom_rev);
+ }
+ SVN_ERR(svn_cmdline_printf(pool, " %c %s%s\n",
+ log_item->action, path,
+ copy_data));
+ }
+ }
+
+ if (lb->merge_stack->nelts > 0)
+ {
+ int i;
+
+ /* Print the result of merge line */
+ if (log_entry->subtractive_merge)
+ SVN_ERR(svn_cmdline_printf(pool, _("Reverse merged via:")));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, _("Merged via:")));
+ for (i = 0; i < lb->merge_stack->nelts; i++)
+ {
+ svn_revnum_t rev = APR_ARRAY_IDX(lb->merge_stack, i, svn_revnum_t);
+
+ SVN_ERR(svn_cmdline_printf(pool, " r%ld%c", rev,
+ i == lb->merge_stack->nelts - 1 ?
+ '\n' : ','));
+ }
+ }
+
+ if (message != NULL)
+ {
+ /* A blank line always precedes the log message. */
+ SVN_ERR(svn_cmdline_printf(pool, "\n%s\n", message));
+ }
+
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_cmdline_fflush(stderr));
+
+ /* Print a diff if requested. */
+ if (lb->show_diff)
+ {
+ svn_stream_t *outstream;
+ svn_stream_t *errstream;
+
+ SVN_ERR(svn_stream_for_stdout(&outstream, pool));
+ SVN_ERR(svn_stream_for_stderr(&errstream, pool));
+
+ SVN_ERR(display_diff(log_entry,
+ lb->target_path_or_url, &lb->target_peg_revision,
+ lb->depth, lb->diff_extensions,
+ outstream, errstream,
+ lb->ctx, pool));
+
+ SVN_ERR(svn_stream_close(outstream));
+ SVN_ERR(svn_stream_close(errstream));
+ }
+
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_log_entry_receiver_t', printing the logs in XML.
+ *
+ * BATON is of type `struct log_receiver_baton'.
+ *
+ * Here is an example of the output; note that the "<log>" and
+ * "</log>" tags are not emitted by this function:
+ *
+ * $ svn log --xml -r 1648:1649
+ * <log>
+ * <logentry
+ * revision="1648">
+ * <author>david</author>
+ * <date>2002-04-06T16:34:51.428043Z</date>
+ * <msg> * packages/rpm/subversion.spec : Now requires apache 2.0.36.
+ * </msg>
+ * </logentry>
+ * <logentry
+ * revision="1649">
+ * <author>cmpilato</author>
+ * <date>2002-04-06T17:01:28.185136Z</date>
+ * <msg>Fix error handling when the $EDITOR is needed but unavailable. Ah
+ * ... now that&apos;s *much* nicer.
+ *
+ * * subversion/clients/cmdline/util.c
+ * (svn_cl__edit_externally): Clean up the &quot;no external editor&quot;
+ * error message.
+ * (svn_cl__get_log_message): Wrap &quot;no external editor&quot;
+ * errors with helpful hints about the -m and -F options.
+ *
+ * * subversion/libsvn_client/commit.c
+ * (svn_client_commit): Actually capture and propagate &quot;no external
+ * editor&quot; errors.</msg>
+ * </logentry>
+ * </log>
+ *
+ */
+static svn_error_t *
+log_entry_receiver_xml(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ struct log_receiver_baton *lb = baton;
+ /* Collate whole log message into sb before printing. */
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ char *revstr;
+ const char *author;
+ const char *date;
+ const char *message;
+
+ if (lb->ctx->cancel_func)
+ SVN_ERR(lb->ctx->cancel_func(lb->ctx->cancel_baton));
+
+ svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
+
+ if (log_entry->revision == 0 && message == NULL)
+ return SVN_NO_ERROR;
+
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ svn_xml_make_close_tag(&sb, pool, "logentry");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ apr_array_pop(lb->merge_stack);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Match search pattern before XML-escaping. */
+ if (lb->search_patterns &&
+ ! match_search_patterns(lb->search_patterns, author, date, message,
+ log_entry->changed_paths2, pool))
+ {
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+
+ return SVN_NO_ERROR;
+ }
+
+ if (author)
+ author = svn_xml_fuzzy_escape(author, pool);
+ if (date)
+ date = svn_xml_fuzzy_escape(date, pool);
+ if (message)
+ message = svn_xml_fuzzy_escape(message, pool);
+
+ revstr = apr_psprintf(pool, "%ld", log_entry->revision);
+ /* <logentry revision="xxx"> */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "logentry",
+ "revision", revstr, NULL);
+
+ /* <author>xxx</author> */
+ svn_cl__xml_tagged_cdata(&sb, pool, "author", author);
+
+ /* Print the full, uncut, date. This is machine output. */
+ /* According to the docs for svn_log_entry_receiver_t, either
+ NULL or the empty string represents no date. Avoid outputting an
+ empty date element. */
+ if (date && date[0] == '\0')
+ date = NULL;
+ /* <date>xxx</date> */
+ svn_cl__xml_tagged_cdata(&sb, pool, "date", date);
+
+ if (log_entry->changed_paths2)
+ {
+ apr_array_header_t *sorted_paths;
+ int i;
+
+ /* <paths> */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
+ NULL);
+
+ /* Get an array of sorted hash keys. */
+ sorted_paths = svn_sort__hash(log_entry->changed_paths2,
+ svn_sort_compare_items_as_paths, pool);
+
+ for (i = 0; i < sorted_paths->nelts; i++)
+ {
+ svn_sort__item_t *item = &(APR_ARRAY_IDX(sorted_paths, i,
+ svn_sort__item_t));
+ const char *path = item->key;
+ svn_log_changed_path2_t *log_item = item->value;
+ char action[2];
+
+ action[0] = log_item->action;
+ action[1] = '\0';
+ if (log_item->copyfrom_path
+ && SVN_IS_VALID_REVNUM(log_item->copyfrom_rev))
+ {
+ /* <path action="X" copyfrom-path="xxx" copyfrom-rev="xxx"> */
+ revstr = apr_psprintf(pool, "%ld",
+ log_item->copyfrom_rev);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
+ "action", action,
+ "copyfrom-path", log_item->copyfrom_path,
+ "copyfrom-rev", revstr,
+ "kind", svn_cl__node_kind_str_xml(
+ log_item->node_kind),
+ "text-mods", svn_tristate__to_word(
+ log_item->text_modified),
+ "prop-mods", svn_tristate__to_word(
+ log_item->props_modified),
+ NULL);
+ }
+ else
+ {
+ /* <path action="X"> */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_protect_pcdata, "path",
+ "action", action,
+ "kind", svn_cl__node_kind_str_xml(
+ log_item->node_kind),
+ "text-mods", svn_tristate__to_word(
+ log_item->text_modified),
+ "prop-mods", svn_tristate__to_word(
+ log_item->props_modified),
+ NULL);
+ }
+ /* xxx</path> */
+ svn_xml_escape_cdata_cstring(&sb, path, pool);
+ svn_xml_make_close_tag(&sb, pool, "path");
+ }
+
+ /* </paths> */
+ svn_xml_make_close_tag(&sb, pool, "paths");
+ }
+
+ if (message != NULL)
+ {
+ /* <msg>xxx</msg> */
+ svn_cl__xml_tagged_cdata(&sb, pool, "msg", message);
+ }
+
+ svn_compat_log_revprops_clear(log_entry->revprops);
+ if (log_entry->revprops && apr_hash_count(log_entry->revprops) > 0)
+ {
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops", NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, log_entry->revprops,
+ FALSE, /* name_only */
+ FALSE, pool));
+ svn_xml_make_close_tag(&sb, pool, "revprops");
+ }
+
+ if (log_entry->has_children)
+ APR_ARRAY_PUSH(lb->merge_stack, svn_revnum_t) = log_entry->revision;
+ else
+ svn_xml_make_close_tag(&sb, pool, "logentry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__log(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ struct log_receiver_baton lb;
+ const char *target;
+ int i;
+ apr_array_header_t *revprops;
+
+ if (!opt_state->xml)
+ {
+ if (opt_state->all_revprops)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'with-all-revprops' option only valid in"
+ " XML mode"));
+ if (opt_state->no_revprops)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'with-no-revprops' option only valid in"
+ " XML mode"));
+ if (opt_state->revprop_table != NULL)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'with-revprop' option only valid in"
+ " XML mode"));
+ }
+ else
+ {
+ if (opt_state->show_diff)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'diff' option is not supported in "
+ "XML mode"));
+ }
+
+ if (opt_state->quiet && opt_state->show_diff)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'quiet' and 'diff' options are "
+ "mutually exclusive"));
+ if (opt_state->diff.diff_cmd && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'diff-cmd' option requires 'diff' "
+ "option"));
+ if (opt_state->diff.internal_diff && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'internal-diff' option requires "
+ "'diff' option"));
+ if (opt_state->extensions && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'extensions' option requires 'diff' "
+ "option"));
+
+ if (opt_state->depth != svn_depth_unknown && (! opt_state->show_diff))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'depth' option requires 'diff' option"));
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ /* Determine if they really want a two-revision range. */
+ if (opt_state->used_change_arg)
+ {
+ if (opt_state->used_revision_arg && opt_state->revision_ranges->nelts > 1)
+ {
+ return svn_error_create
+ (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("-c and -r are mutually exclusive"));
+ }
+ for (i = 0; i < opt_state->revision_ranges->nelts; i++)
+ {
+ svn_opt_revision_range_t *range;
+ range = APR_ARRAY_IDX(opt_state->revision_ranges, i,
+ svn_opt_revision_range_t *);
+ if (range->start.value.number < range->end.value.number)
+ range->start.value.number++;
+ else
+ range->end.value.number++;
+ }
+ }
+
+ /* Parse the first target into path-or-url and peg revision. */
+ target = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(svn_opt_parse_path(&lb.target_peg_revision, &lb.target_path_or_url,
+ target, pool));
+ if (lb.target_peg_revision.kind == svn_opt_revision_unspecified)
+ lb.target_peg_revision.kind = (svn_path_is_url(target)
+ ? svn_opt_revision_head
+ : svn_opt_revision_working);
+ APR_ARRAY_IDX(targets, 0, const char *) = lb.target_path_or_url;
+
+ if (svn_path_is_url(target))
+ {
+ for (i = 1; i < targets->nelts; i++)
+ {
+ target = APR_ARRAY_IDX(targets, i, const char *);
+
+ if (svn_path_is_url(target) || target[0] == '/')
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only relative paths can be specified"
+ " after a URL for 'svn log', "
+ "but '%s' is not a relative path"),
+ target);
+ }
+ }
+
+ lb.ctx = ctx;
+ lb.omit_log_message = opt_state->quiet;
+ lb.show_diff = opt_state->show_diff;
+ lb.depth = opt_state->depth == svn_depth_unknown ? svn_depth_infinity
+ : opt_state->depth;
+ lb.diff_extensions = opt_state->extensions;
+ lb.merge_stack = apr_array_make(pool, 0, sizeof(svn_revnum_t));
+ lb.search_patterns = opt_state->search_patterns;
+ lb.pool = pool;
+
+ if (opt_state->xml)
+ {
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("log", pool));
+
+ if (opt_state->all_revprops)
+ revprops = NULL;
+ else if(opt_state->no_revprops)
+ {
+ revprops = apr_array_make(pool, 0, sizeof(char *));
+ }
+ else if (opt_state->revprop_table != NULL)
+ {
+ apr_hash_index_t *hi;
+ revprops = apr_array_make(pool,
+ apr_hash_count(opt_state->revprop_table),
+ sizeof(char *));
+ for (hi = apr_hash_first(pool, opt_state->revprop_table);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *property = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+
+ if (value && value->data[0] != '\0')
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("cannot assign with 'with-revprop'"
+ " option (drop the '=')"));
+ APR_ARRAY_PUSH(revprops, const char *) = property;
+ }
+ }
+ else
+ {
+ revprops = apr_array_make(pool, 3, sizeof(char *));
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+ if (!opt_state->quiet)
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+ }
+ SVN_ERR(svn_client_log5(targets,
+ &lb.target_peg_revision,
+ opt_state->revision_ranges,
+ opt_state->limit,
+ opt_state->verbose,
+ opt_state->stop_on_copy,
+ opt_state->use_merge_history,
+ revprops,
+ log_entry_receiver_xml,
+ &lb,
+ ctx,
+ pool));
+
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_footer("log", pool));
+ }
+ else /* default output format */
+ {
+ revprops = apr_array_make(pool, 3, sizeof(char *));
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_DATE;
+ if (!opt_state->quiet)
+ APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_LOG;
+ SVN_ERR(svn_client_log5(targets,
+ &lb.target_peg_revision,
+ opt_state->revision_ranges,
+ opt_state->limit,
+ opt_state->verbose,
+ opt_state->stop_on_copy,
+ opt_state->use_merge_history,
+ revprops,
+ log_entry_receiver,
+ &lb,
+ ctx,
+ pool));
+
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cmdline_printf(pool, SEP_STRING));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c
new file mode 100644
index 000000000000..c14f769a8d26
--- /dev/null
+++ b/subversion/svn/merge-cmd.c
@@ -0,0 +1,467 @@
+/*
+ * merge-cmd.c -- Merging changes into a working copy.
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "cl.h"
+#include "private/svn_client_private.h"
+
+#include "svn_private_config.h"
+
+/* A handy constant */
+static const svn_opt_revision_t unspecified_revision
+ = { svn_opt_revision_unspecified, { 0 } };
+
+
+/*** Code. ***/
+
+/* Throw an error if PATH_OR_URL is a path and REVISION isn't a repository
+ * revision. */
+static svn_error_t *
+ensure_wc_path_has_repo_revision(const char *path_or_url,
+ const svn_opt_revision_t *revision,
+ apr_pool_t *scratch_pool)
+{
+ if (revision->kind != svn_opt_revision_number
+ && revision->kind != svn_opt_revision_date
+ && revision->kind != svn_opt_revision_head
+ && ! svn_path_is_url(path_or_url))
+ return svn_error_createf(
+ SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Invalid merge source '%s'; a working copy path can only be "
+ "used with a repository revision (a number, a date, or head)"),
+ svn_dirent_local_style(path_or_url, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Run a merge.
+ *
+ * (No docs yet, as this code was just hoisted out of svn_cl__merge().)
+ *
+ * Having FIRST_RANGE_START/_END params is ugly -- we should be able to use
+ * PEG_REVISION1/2 and/or RANGES_TO_MERGE instead, maybe adjusting the caller.
+ */
+static svn_error_t *
+run_merge(svn_boolean_t two_sources_specified,
+ const char *sourcepath1,
+ svn_opt_revision_t peg_revision1,
+ const char *sourcepath2,
+ const char *targetpath,
+ apr_array_header_t *ranges_to_merge,
+ svn_opt_revision_t first_range_start,
+ svn_opt_revision_t first_range_end,
+ svn_cl__opt_state_t *opt_state,
+ apr_array_header_t *options,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *merge_err;
+
+ if (opt_state->reintegrate)
+ {
+ merge_err = svn_cl__deprecated_merge_reintegrate(
+ sourcepath1, &peg_revision1, targetpath,
+ opt_state->dry_run, options, ctx, scratch_pool);
+ }
+ else if (! two_sources_specified)
+ {
+ /* If we don't have at least one valid revision range, pick a
+ good one that spans the entire set of revisions on our
+ source. */
+ if ((first_range_start.kind == svn_opt_revision_unspecified)
+ && (first_range_end.kind == svn_opt_revision_unspecified))
+ {
+ ranges_to_merge = NULL;
+
+ /* This must be a 'sync' merge so check branch relationship. */
+ if (opt_state->verbose)
+ SVN_ERR(svn_cmdline_printf(
+ scratch_pool, _("--- Checking branch relationship\n")));
+ SVN_ERR_W(svn_cl__check_related_source_and_target(
+ sourcepath1, &peg_revision1,
+ targetpath, &unspecified_revision, ctx, scratch_pool),
+ _("Source and target must be different but related branches"));
+ }
+
+ if (opt_state->verbose)
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
+ merge_err = svn_client_merge_peg5(sourcepath1,
+ ranges_to_merge,
+ &peg_revision1,
+ targetpath,
+ opt_state->depth,
+ opt_state->ignore_ancestry,
+ opt_state->ignore_ancestry,
+ opt_state->force, /* force_delete */
+ opt_state->record_only,
+ opt_state->dry_run,
+ opt_state->allow_mixed_rev,
+ options,
+ ctx,
+ scratch_pool);
+ }
+ else
+ {
+ if (svn_path_is_url(sourcepath1) != svn_path_is_url(sourcepath2))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Merge sources must both be "
+ "either paths or URLs"));
+
+ if (opt_state->verbose)
+ SVN_ERR(svn_cmdline_printf(scratch_pool, _("--- Merging\n")));
+ merge_err = svn_client_merge5(sourcepath1,
+ &first_range_start,
+ sourcepath2,
+ &first_range_end,
+ targetpath,
+ opt_state->depth,
+ opt_state->ignore_ancestry,
+ opt_state->ignore_ancestry,
+ opt_state->force, /* force_delete */
+ opt_state->record_only,
+ opt_state->dry_run,
+ opt_state->allow_mixed_rev,
+ options,
+ ctx,
+ scratch_pool);
+ }
+
+ return merge_err;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__merge(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = "";
+ svn_boolean_t two_sources_specified = TRUE;
+ svn_error_t *merge_err;
+ svn_opt_revision_t first_range_start, first_range_end, peg_revision1,
+ peg_revision2;
+ apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges;
+ svn_boolean_t has_explicit_target = FALSE;
+
+ /* Merge doesn't support specifying a revision or revision range
+ when using --reintegrate. */
+ if (opt_state->reintegrate
+ && opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("-r and -c can't be used with --reintegrate"));
+ }
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* For now, we require at least one source. That may change in
+ future versions of Subversion, for example if we have support for
+ negated mergeinfo. See this IRC conversation:
+
+ <bhuvan> kfogel: yeah, i think you are correct; we should
+ specify the source url
+
+ <kfogel> bhuvan: I'll change the help output and propose for
+ backport. Thanks.
+
+ <bhuvan> kfogel: np; while we are at it, 'svn merge' simply
+ returns nothing; i think we should say: """svn: Not
+ enough arguments provided; try 'svn help' for more
+ info"""
+
+ <kfogel> good idea
+
+ <kfogel> (in the future, 'svn merge' might actually do
+ something, but that's all the more reason to make
+ sure it errors now)
+
+ <cmpilato> actually, i'm pretty sure 'svn merge' does something
+
+ <cmpilato> it says "please merge any unmerged changes from
+ myself to myself."
+
+ <cmpilato> :-)
+
+ <kfogel> har har
+
+ <cmpilato> kfogel: i was serious.
+
+ <kfogel> cmpilato: urrr, uh. Is that meaningful? Is there
+ ever a reason for a user to run it?
+
+ <cmpilato> kfogel: not while we don't have support for negated
+ mergeinfo.
+
+ <kfogel> cmpilato: do you concur that until it does something
+ useful it should error?
+
+ <cmpilato> kfogel: yup.
+
+ <kfogel> cool
+ */
+ if (targets->nelts < 1)
+ {
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
+ _("Merge source required"));
+ }
+ else /* Parse at least one, and possible two, sources. */
+ {
+ SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,
+ APR_ARRAY_IDX(targets, 0, const char *),
+ pool));
+ if (targets->nelts >= 2)
+ SVN_ERR(svn_opt_parse_path(&peg_revision2, &sourcepath2,
+ APR_ARRAY_IDX(targets, 1, const char *),
+ pool));
+ }
+
+ /* We could have one or two sources. Deliberately written to stay
+ correct even if we someday permit implied merge source. */
+ if (targets->nelts <= 1)
+ {
+ two_sources_specified = FALSE;
+ }
+ else if (targets->nelts == 2)
+ {
+ if (svn_path_is_url(sourcepath1) && !svn_path_is_url(sourcepath2))
+ two_sources_specified = FALSE;
+ }
+
+ if (opt_state->revision_ranges->nelts > 0)
+ {
+ first_range_start = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
+ svn_opt_revision_range_t *)->start;
+ first_range_end = APR_ARRAY_IDX(opt_state->revision_ranges, 0,
+ svn_opt_revision_range_t *)->end;
+ }
+ else
+ {
+ first_range_start.kind = first_range_end.kind =
+ svn_opt_revision_unspecified;
+ }
+
+ /* If revision_ranges has at least one real range at this point, then
+ we know the user must have used the '-r' and/or '-c' switch(es).
+ This means we're *not* doing two distinct sources. */
+ if (first_range_start.kind != svn_opt_revision_unspecified)
+ {
+ /* A revision *range* is required. */
+ if (first_range_end.kind == svn_opt_revision_unspecified)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
+ _("Second revision required"));
+
+ two_sources_specified = FALSE;
+ }
+
+ if (! two_sources_specified) /* TODO: Switch order of if */
+ {
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ /* Set the default value for unspecified paths and peg revision. */
+ /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge
+ SOURCE WCPATH") here. */
+ sourcepath2 = sourcepath1;
+
+ if (peg_revision1.kind == svn_opt_revision_unspecified)
+ peg_revision1.kind = svn_path_is_url(sourcepath1)
+ ? svn_opt_revision_head : svn_opt_revision_working;
+
+ if (targets->nelts == 2)
+ {
+ targetpath = APR_ARRAY_IDX(targets, 1, const char *);
+ has_explicit_target = TRUE;
+ if (svn_path_is_url(targetpath))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot specify a revision range "
+ "with two URLs"));
+ }
+ }
+ else /* using @rev syntax */
+ {
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, NULL);
+ if (targets->nelts > 3)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ first_range_start = peg_revision1;
+ first_range_end = peg_revision2;
+
+ /* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit
+ revisions--since it ignores local modifications it may not do what
+ the user expects. That is, it doesn't read from the WC itself, it
+ reads from the WC's URL. Forcing the user to specify a repository
+ revision should avoid any confusion. */
+ SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath1, &first_range_start,
+ pool));
+ SVN_ERR(ensure_wc_path_has_repo_revision(sourcepath2, &first_range_end,
+ pool));
+
+ /* Default peg revisions to each URL's youngest revision. */
+ if (first_range_start.kind == svn_opt_revision_unspecified)
+ first_range_start.kind = svn_opt_revision_head;
+ if (first_range_end.kind == svn_opt_revision_unspecified)
+ first_range_end.kind = svn_opt_revision_head;
+
+ /* Decide where to apply the delta (defaulting to "."). */
+ if (targets->nelts == 3)
+ {
+ targetpath = APR_ARRAY_IDX(targets, 2, const char *);
+ has_explicit_target = TRUE;
+ }
+ }
+
+ /* If no targetpath was specified, see if we can infer it from the
+ sourcepaths. */
+ if (! has_explicit_target
+ && sourcepath1 && sourcepath2
+ && strcmp(targetpath, "") == 0)
+ {
+ /* If the sourcepath is a URL, it can only refer to a target in
+ the current working directory or which is the current working
+ directory. However, if the sourcepath is a local path, it can
+ refer to a target somewhere deeper in the directory structure. */
+ if (svn_path_is_url(sourcepath1))
+ {
+ const char *sp1_basename = svn_uri_basename(sourcepath1, pool);
+ const char *sp2_basename = svn_uri_basename(sourcepath2, pool);
+
+ if (strcmp(sp1_basename, sp2_basename) == 0)
+ {
+ const char *target_url;
+ const char *target_base;
+
+ SVN_ERR(svn_client_url_from_path2(&target_url, targetpath, ctx,
+ pool, pool));
+ target_base = svn_uri_basename(target_url, pool);
+
+ /* If the basename of the source is the same as the basename of
+ the cwd assume the cwd is the target. */
+ if (strcmp(sp1_basename, target_base) != 0)
+ {
+ svn_node_kind_t kind;
+
+ /* If the basename of the source differs from the basename
+ of the target. We still might assume the cwd is the
+ target, but first check if there is a file in the cwd
+ with the same name as the source basename. If there is,
+ then assume that file is the target. */
+ SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ targetpath = sp1_basename;
+ }
+ }
+ }
+ }
+ else if (strcmp(sourcepath1, sourcepath2) == 0)
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
+ if (kind == svn_node_file)
+ {
+ targetpath = sourcepath1;
+ }
+ }
+ }
+
+ if (opt_state->extensions)
+ options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool);
+ else
+ options = NULL;
+
+ /* More input validation. */
+ if (opt_state->reintegrate)
+ {
+ if (opt_state->ignore_ancestry)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--reintegrate cannot be used with "
+ "--ignore-ancestry"));
+
+ if (opt_state->record_only)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--reintegrate cannot be used with "
+ "--record-only"));
+
+ if (opt_state->depth != svn_depth_unknown)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--depth cannot be used with "
+ "--reintegrate"));
+
+ if (opt_state->force)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--force cannot be used with "
+ "--reintegrate"));
+
+ if (two_sources_specified)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--reintegrate can only be used with "
+ "a single merge source"));
+ if (opt_state->allow_mixed_rev)
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--allow-mixed-revisions cannot be used "
+ "with --reintegrate"));
+ }
+
+ merge_err = run_merge(two_sources_specified,
+ sourcepath1, peg_revision1,
+ sourcepath2,
+ targetpath,
+ ranges_to_merge, first_range_start, first_range_end,
+ opt_state, options, ctx, pool);
+ if (merge_err && merge_err->apr_err
+ == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
+ {
+ return svn_error_quick_wrap(
+ merge_err,
+ _("Merge tracking not possible, use --ignore-ancestry or\n"
+ "fix invalid mergeinfo in target with 'svn propset'"));
+ }
+
+ if (!opt_state->quiet)
+ {
+ svn_error_t *err = svn_cl__notifier_print_conflict_stats(
+ ctx->notify_baton2, pool);
+
+ merge_err = svn_error_compose_create(merge_err, err);
+ }
+
+ return svn_cl__may_need_force(merge_err);
+}
diff --git a/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c
new file mode 100644
index 000000000000..a78c42a6c388
--- /dev/null
+++ b/subversion/svn/mergeinfo-cmd.c
@@ -0,0 +1,349 @@
+/*
+ * mergeinfo-cmd.c -- Query merge-relative info.
+ *
+ * ====================================================================
+ * 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 "svn_pools.h"
+#include "svn_client.h"
+#include "svn_cmdline.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_types.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* Implements the svn_log_entry_receiver_t interface. */
+static svn_error_t *
+print_log_rev(void *baton,
+ svn_log_entry_t *log_entry,
+ apr_pool_t *pool)
+{
+ if (log_entry->non_inheritable)
+ SVN_ERR(svn_cmdline_printf(pool, "r%ld*\n", log_entry->revision));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, "r%ld\n", log_entry->revision));
+
+ return SVN_NO_ERROR;
+}
+
+/* Draw a diagram (by printing text to the console) summarizing the state
+ * of merging between two branches, given the merge description
+ * indicated by YCA, BASE, RIGHT, TARGET, REINTEGRATE_LIKE. */
+static svn_error_t *
+mergeinfo_diagram(const char *yca_url,
+ const char *base_url,
+ const char *right_url,
+ const char *target_url,
+ svn_revnum_t yca_rev,
+ svn_revnum_t base_rev,
+ svn_revnum_t right_rev,
+ svn_revnum_t target_rev,
+ const char *repos_root_url,
+ svn_boolean_t target_is_wc,
+ svn_boolean_t reintegrate_like,
+ apr_pool_t *pool)
+{
+ /* The graph occupies 4 rows of text, and the annotations occupy
+ * another 2 rows above and 2 rows below. The graph is constructed
+ * from left to right in discrete sections ("columns"), each of which
+ * can have a different width (measured in characters). Each element in
+ * the array is either a text string of the appropriate width, or can
+ * be NULL to draw a blank cell. */
+#define ROWS 8
+#define COLS 4
+ const char *g[ROWS][COLS] = {{0}};
+ int col_width[COLS];
+ int row, col;
+
+ /* The YCA (that is, the branching point). And an ellipsis, because we
+ * don't show information about earlier merges */
+ g[0][0] = apr_psprintf(pool, " %-8ld ", yca_rev);
+ g[1][0] = " | ";
+ if (strcmp(yca_url, right_url) == 0)
+ {
+ g[2][0] = "-------| |--";
+ g[3][0] = " \\ ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+ else if (strcmp(yca_url, target_url) == 0)
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = " / ";
+ g[4][0] = " / ";
+ g[5][0] = "-------| |--";
+ }
+ else
+ {
+ g[2][0] = " --| |--";
+ g[3][0] = "... / ";
+ g[4][0] = " \\ ";
+ g[5][0] = " --| |--";
+ }
+
+ /* The last full merge */
+ if ((base_rev > yca_rev) && reintegrate_like)
+ {
+ g[2][2] = "---------";
+ g[3][2] = " / ";
+ g[4][2] = " / ";
+ g[5][2] = "---------";
+ g[6][2] = "| ";
+ g[7][2] = apr_psprintf(pool, "%-8ld ", base_rev);
+ }
+ else if (base_rev > yca_rev)
+ {
+ g[0][2] = apr_psprintf(pool, "%-8ld ", base_rev);
+ g[1][2] = "| ";
+ g[2][2] = "---------";
+ g[3][2] = " \\ ";
+ g[4][2] = " \\ ";
+ g[5][2] = "---------";
+ }
+ else
+ {
+ g[2][2] = "---------";
+ g[3][2] = " ";
+ g[4][2] = " ";
+ g[5][2] = "---------";
+ }
+
+ /* The tips of the branches */
+ {
+ g[0][3] = apr_psprintf(pool, "%-8ld", right_rev);
+ g[1][3] = "| ";
+ g[2][3] = "- ";
+ g[3][3] = " ";
+ g[4][3] = " ";
+ g[5][3] = "- ";
+ g[6][3] = "| ";
+ g[7][3] = target_is_wc ? "WC "
+ : apr_psprintf(pool, "%-8ld", target_rev);
+ }
+
+ /* Find the width of each column, so we know how to print blank cells */
+ for (col = 0; col < COLS; col++)
+ {
+ col_width[col] = 0;
+ for (row = 0; row < ROWS; row++)
+ {
+ if (g[row][col] && ((int)strlen(g[row][col]) > col_width[col]))
+ col_width[col] = (int)strlen(g[row][col]);
+ }
+ }
+
+ /* Column headings */
+ SVN_ERR(svn_cmdline_printf(pool,
+ " %s\n"
+ " | %s\n"
+ " | | %s\n"
+ " | | | %s\n"
+ "\n",
+ _("youngest common ancestor"), _("last full merge"),
+ _("tip of branch"), _("repository path")));
+
+ /* Print the diagram, row by row */
+ for (row = 0; row < ROWS; row++)
+ {
+ SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
+ for (col = 0; col < COLS; col++)
+ {
+ if (g[row][col])
+ {
+ SVN_ERR(svn_cmdline_fputs(g[row][col], stdout, pool));
+ }
+ else
+ {
+ /* Print <column-width> spaces */
+ SVN_ERR(svn_cmdline_printf(pool, "%*s", col_width[col], ""));
+ }
+ }
+ if (row == 2)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_uri_skip_ancestor(repos_root_url, right_url, pool)));
+ if (row == 5)
+ SVN_ERR(svn_cmdline_printf(pool, " %s",
+ svn_uri_skip_ancestor(repos_root_url, target_url, pool)));
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Display a summary of the state of merging between the two branches
+ * SOURCE_PATH_OR_URL@SOURCE_REVISION and
+ * TARGET_PATH_OR_URL@TARGET_REVISION. */
+static svn_error_t *
+mergeinfo_summary(
+ const char *source_path_or_url,
+ const svn_opt_revision_t *source_revision,
+ const char *target_path_or_url,
+ const svn_opt_revision_t *target_revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *yca_url, *base_url, *right_url, *target_url;
+ svn_revnum_t yca_rev, base_rev, right_rev, target_rev;
+ const char *repos_root_url;
+ svn_boolean_t target_is_wc, is_reintegration;
+
+ target_is_wc = (! svn_path_is_url(target_path_or_url))
+ && (target_revision->kind == svn_opt_revision_unspecified
+ || target_revision->kind == svn_opt_revision_working);
+ SVN_ERR(svn_client_get_merging_summary(
+ &is_reintegration,
+ &yca_url, &yca_rev,
+ &base_url, &base_rev,
+ &right_url, &right_rev,
+ &target_url, &target_rev,
+ &repos_root_url,
+ source_path_or_url, source_revision,
+ target_path_or_url, target_revision,
+ ctx, pool, pool));
+
+ SVN_ERR(mergeinfo_diagram(yca_url, base_url, right_url, target_url,
+ yca_rev, base_rev, right_rev, target_rev,
+ repos_root_url, target_is_wc, is_reintegration,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__mergeinfo(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *source, *target;
+ svn_opt_revision_t src_peg_revision, tgt_peg_revision;
+ svn_opt_revision_t *src_start_revision, *src_end_revision;
+ /* Default to depth empty. */
+ svn_depth_t depth = (opt_state->depth == svn_depth_unknown)
+ ? svn_depth_empty : opt_state->depth;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Parse the arguments: SOURCE[@REV] optionally followed by TARGET[@REV]. */
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Not enough arguments given"));
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+ SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source,
+ APR_ARRAY_IDX(targets, 0, const char *), pool));
+ if (targets->nelts == 2)
+ {
+ SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target,
+ APR_ARRAY_IDX(targets, 1, const char *),
+ pool));
+ }
+ else
+ {
+ target = "";
+ tgt_peg_revision.kind = svn_opt_revision_unspecified;
+ }
+
+ /* If no peg-rev was attached to the source URL, assume HEAD. */
+ /* ### But what if SOURCE is a WC path not a URL -- shouldn't we then use
+ * BASE (but not WORKING: that would be inconsistent with 'svn merge')? */
+ if (src_peg_revision.kind == svn_opt_revision_unspecified)
+ src_peg_revision.kind = svn_opt_revision_head;
+
+ /* If no peg-rev was attached to a URL target, then assume HEAD; if
+ no peg-rev was attached to a non-URL target, then assume BASE. */
+ /* ### But we would like to be able to examine a working copy with an
+ uncommitted merge in it, so change this to use WORKING not BASE? */
+ if (tgt_peg_revision.kind == svn_opt_revision_unspecified)
+ {
+ if (svn_path_is_url(target))
+ tgt_peg_revision.kind = svn_opt_revision_head;
+ else
+ tgt_peg_revision.kind = svn_opt_revision_base;
+ }
+
+ SVN_ERR_W(svn_cl__check_related_source_and_target(source, &src_peg_revision,
+ target, &tgt_peg_revision,
+ ctx, pool),
+ _("Source and target must be different but related branches"));
+
+ src_start_revision = &(opt_state->start_revision);
+ if (opt_state->end_revision.kind == svn_opt_revision_unspecified)
+ src_end_revision = src_start_revision;
+ else
+ src_end_revision = &(opt_state->end_revision);
+
+ /* Do the real work, depending on the requested data flavor. */
+ if (opt_state->show_revs == svn_cl__show_revs_merged)
+ {
+ SVN_ERR(svn_client_mergeinfo_log2(TRUE, target, &tgt_peg_revision,
+ source, &src_peg_revision,
+ src_start_revision,
+ src_end_revision,
+ print_log_rev, NULL,
+ TRUE, depth, NULL, ctx,
+ pool));
+ }
+ else if (opt_state->show_revs == svn_cl__show_revs_eligible)
+ {
+ SVN_ERR(svn_client_mergeinfo_log2(FALSE, target, &tgt_peg_revision,
+ source, &src_peg_revision,
+ src_start_revision,
+ src_end_revision,
+ print_log_rev, NULL,
+ TRUE, depth, NULL, ctx,
+ pool));
+ }
+ else
+ {
+ if ((opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ || (opt_state->end_revision.kind != svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--revision (-r) option valid only with "
+ "--show-revs option"));
+ if (opt_state->depth != svn_depth_unknown)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Depth specification options valid only "
+ "with --show-revs option"));
+
+ SVN_ERR(mergeinfo_summary(source, &src_peg_revision,
+ target, &tgt_peg_revision,
+ ctx, pool));
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/mkdir-cmd.c b/subversion/svn/mkdir-cmd.c
new file mode 100644
index 000000000000..64cb4f94957f
--- /dev/null
+++ b/subversion/svn/mkdir-cmd.c
@@ -0,0 +1,104 @@
+/*
+ * mkdir-cmd.c -- Subversion mkdir command
+ *
+ * ====================================================================
+ * 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 "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__mkdir(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+
+ if (! svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)))
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+ }
+ else
+ {
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ err = svn_client_mkdir4(targets, opt_state->parents,
+ opt_state->revprop_table,
+ (opt_state->quiet ? NULL : svn_cl__print_commit_info),
+ NULL, ctx, pool);
+
+ if (ctx->log_msg_func3)
+ err = svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool);
+
+ if (err)
+ {
+ if (err->apr_err == APR_EEXIST)
+ return svn_error_quick_wrap
+ (err, _("Try 'svn add' or 'svn add --non-recursive' instead?"));
+ else if (!(opt_state->parents) &&
+ (APR_STATUS_IS_ENOENT(err->apr_err) || /* in wc */
+ err->apr_err == SVN_ERR_FS_NOT_DIRECTORY ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND /* all ra layers */))
+ return svn_error_quick_wrap
+ (err, _("Try 'svn mkdir --parents' instead?"));
+ else
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/move-cmd.c b/subversion/svn/move-cmd.c
new file mode 100644
index 000000000000..bb7104388845
--- /dev/null
+++ b/subversion/svn/move-cmd.c
@@ -0,0 +1,105 @@
+/*
+ * move-cmd.c -- Subversion move command
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__move(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ const char *dst_path;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, TRUE, pool));
+
+ if (targets->nelts < 2)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->start_revision.kind != svn_opt_revision_unspecified
+ && opt_state->start_revision.kind != svn_opt_revision_head)
+ {
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot specify revisions (except HEAD) with move operations"));
+ }
+
+ dst_path = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *);
+ apr_array_pop(targets);
+
+ if (! svn_path_is_url(dst_path))
+ {
+ ctx->log_msg_func3 = NULL;
+ if (opt_state->message || opt_state->filedata || opt_state->revprop_table)
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state,
+ NULL, ctx->config, pool));
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ err = svn_client_move7(targets, dst_path,
+ TRUE /* move_as_child */,
+ opt_state->parents /* make_parents */,
+ opt_state->allow_mixed_rev /* allow_mixed_revisions*/,
+ FALSE /* metadata_only */,
+ opt_state->revprop_table,
+ (opt_state->quiet ? NULL : svn_cl__print_commit_info),
+ NULL, ctx, pool);
+
+ if (err)
+ err = svn_cl__may_need_force(err);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/notify.c b/subversion/svn/notify.c
new file mode 100644
index 000000000000..6498fb17839c
--- /dev/null
+++ b/subversion/svn/notify.c
@@ -0,0 +1,1222 @@
+/*
+ * notify.c: feedback handlers for cmdline client.
+ *
+ * ====================================================================
+ * 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. ***/
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_sorts.h"
+#include "svn_hash.h"
+#include "cl.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+
+#include "svn_private_config.h"
+
+
+/* Baton for notify and friends. */
+struct notify_baton
+{
+ svn_boolean_t received_some_change;
+ svn_boolean_t is_checkout;
+ svn_boolean_t is_export;
+ svn_boolean_t is_wc_to_repos_copy;
+ svn_boolean_t sent_first_txdelta;
+ svn_boolean_t in_external;
+ svn_boolean_t had_print_error; /* Used to not keep printing error messages
+ when we've already had one print error. */
+
+ svn_cl__conflict_stats_t *conflict_stats;
+
+ /* The cwd, for use in decomposing absolute paths. */
+ const char *path_prefix;
+};
+
+/* Conflict stats for operations such as update and merge. */
+struct svn_cl__conflict_stats_t
+{
+ apr_pool_t *stats_pool;
+ apr_hash_t *text_conflicts, *prop_conflicts, *tree_conflicts;
+ int text_conflicts_resolved, prop_conflicts_resolved, tree_conflicts_resolved;
+ int skipped_paths;
+};
+
+svn_cl__conflict_stats_t *
+svn_cl__conflict_stats_create(apr_pool_t *pool)
+{
+ svn_cl__conflict_stats_t *conflict_stats
+ = apr_palloc(pool, sizeof(*conflict_stats));
+
+ conflict_stats->stats_pool = pool;
+ conflict_stats->text_conflicts = apr_hash_make(pool);
+ conflict_stats->prop_conflicts = apr_hash_make(pool);
+ conflict_stats->tree_conflicts = apr_hash_make(pool);
+ conflict_stats->text_conflicts_resolved = 0;
+ conflict_stats->prop_conflicts_resolved = 0;
+ conflict_stats->tree_conflicts_resolved = 0;
+ conflict_stats->skipped_paths = 0;
+ return conflict_stats;
+}
+
+/* Add the PATH (as a key, with a meaningless value) into the HASH in NB. */
+static void
+store_path(struct notify_baton *nb, apr_hash_t *hash, const char *path)
+{
+ svn_hash_sets(hash, apr_pstrdup(nb->conflict_stats->stats_pool, path), "");
+}
+
+void
+svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats,
+ const char *path_local,
+ svn_wc_conflict_kind_t conflict_kind)
+{
+ switch (conflict_kind)
+ {
+ case svn_wc_conflict_kind_text:
+ if (svn_hash_gets(conflict_stats->text_conflicts, path_local))
+ {
+ svn_hash_sets(conflict_stats->text_conflicts, path_local, NULL);
+ conflict_stats->text_conflicts_resolved++;
+ }
+ break;
+ case svn_wc_conflict_kind_property:
+ if (svn_hash_gets(conflict_stats->prop_conflicts, path_local))
+ {
+ svn_hash_sets(conflict_stats->prop_conflicts, path_local, NULL);
+ conflict_stats->prop_conflicts_resolved++;
+ }
+ break;
+ case svn_wc_conflict_kind_tree:
+ if (svn_hash_gets(conflict_stats->tree_conflicts, path_local))
+ {
+ svn_hash_sets(conflict_stats->tree_conflicts, path_local, NULL);
+ conflict_stats->tree_conflicts_resolved++;
+ }
+ break;
+ }
+}
+
+static const char *
+remaining_str(apr_pool_t *pool, int n_remaining)
+{
+ return apr_psprintf(pool, Q_("%d remaining",
+ "%d remaining",
+ n_remaining),
+ n_remaining);
+}
+
+static const char *
+resolved_str(apr_pool_t *pool, int n_resolved)
+{
+ return apr_psprintf(pool, Q_("and %d already resolved",
+ "and %d already resolved",
+ n_resolved),
+ n_resolved);
+}
+
+svn_error_t *
+svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool)
+{
+ struct notify_baton *nb = baton;
+ int n_text = apr_hash_count(nb->conflict_stats->text_conflicts);
+ int n_prop = apr_hash_count(nb->conflict_stats->prop_conflicts);
+ int n_tree = apr_hash_count(nb->conflict_stats->tree_conflicts);
+ int n_text_r = nb->conflict_stats->text_conflicts_resolved;
+ int n_prop_r = nb->conflict_stats->prop_conflicts_resolved;
+ int n_tree_r = nb->conflict_stats->tree_conflicts_resolved;
+
+ if (n_text > 0 || n_text_r > 0
+ || n_prop > 0 || n_prop_r > 0
+ || n_tree > 0 || n_tree_r > 0
+ || nb->conflict_stats->skipped_paths > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("Summary of conflicts:\n")));
+
+ if (n_text_r == 0 && n_prop_r == 0 && n_tree_r == 0)
+ {
+ if (n_text > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Text conflicts: %d\n"),
+ n_text));
+ if (n_prop > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Property conflicts: %d\n"),
+ n_prop));
+ if (n_tree > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Tree conflicts: %d\n"),
+ n_tree));
+ }
+ else
+ {
+ if (n_text > 0 || n_text_r > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Text conflicts: %s (%s)\n"),
+ remaining_str(scratch_pool, n_text),
+ resolved_str(scratch_pool, n_text_r)));
+ if (n_prop > 0 || n_prop_r > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Property conflicts: %s (%s)\n"),
+ remaining_str(scratch_pool, n_prop),
+ resolved_str(scratch_pool, n_prop_r)));
+ if (n_tree > 0 || n_tree_r > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Tree conflicts: %s (%s)\n"),
+ remaining_str(scratch_pool, n_tree),
+ resolved_str(scratch_pool, n_tree_r)));
+ }
+ if (nb->conflict_stats->skipped_paths > 0)
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _(" Skipped paths: %d\n"),
+ nb->conflict_stats->skipped_paths));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_wc_notify_func2_t'.
+ * NOTE: This function can't fail, so we just ignore any print errors. */
+static void
+notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
+{
+ struct notify_baton *nb = baton;
+ char statchar_buf[5] = " ";
+ const char *path_local;
+ svn_error_t *err;
+
+ if (n->url)
+ path_local = n->url;
+ else
+ {
+ if (n->path_prefix)
+ path_local = svn_cl__local_style_skip_ancestor(n->path_prefix, n->path,
+ pool);
+ else /* skip nb->path_prefix, if it's non-null */
+ path_local = svn_cl__local_style_skip_ancestor(nb->path_prefix, n->path,
+ pool);
+ }
+
+ switch (n->action)
+ {
+ case svn_wc_notify_skip:
+ nb->conflict_stats->skipped_paths++;
+ if (n->content_state == svn_wc_notify_state_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped missing target: '%s'\n"),
+ path_local)))
+ goto print_error;
+ }
+ else if (n->content_state == svn_wc_notify_state_source_missing)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped target: '%s' -- copy-source is missing\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, _("Skipped '%s'\n"), path_local)))
+ goto print_error;
+ }
+ break;
+ case svn_wc_notify_update_skip_obstruction:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- An obstructing working copy was found\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_working_only:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Has no versioned parent\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_skip_access_denied:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Access denied\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_skip_conflicted:
+ nb->conflict_stats->skipped_paths++;
+ if ((err = svn_cmdline_printf(
+ pool, _("Skipped '%s' -- Node remains in conflict\n"),
+ path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_delete:
+ case svn_wc_notify_exclude:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n", path_local)))
+ goto print_error;
+ break;
+ case svn_wc_notify_update_broken_lock:
+ if ((err = svn_cmdline_printf(pool, "B %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_external_removed:
+ nb->received_some_change = TRUE;
+ if (n->err && n->err->message)
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s': %s\n",
+ path_local, n->err->message)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "Removed external '%s'\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_left_local_modifications:
+ if ((err = svn_cmdline_printf(pool, "Left local modifications as '%s'\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_replace:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "R %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_add:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ if ((err = svn_cmdline_printf(pool, "C %s\n", path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n", path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_exists:
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ statchar_buf[0] = 'C';
+ }
+ else
+ statchar_buf[0] = 'E';
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+
+ if ((err = svn_cmdline_printf(pool, "%s %s\n", statchar_buf, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_restore:
+ if ((err = svn_cmdline_printf(pool, _("Restored '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revert:
+ if ((err = svn_cmdline_printf(pool, _("Reverted '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_revert:
+ if (( err = svn_cmdline_printf(pool, _("Failed to revert '%s' -- "
+ "try updating instead.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_resolved:
+ if ((err = svn_cmdline_printf(pool,
+ _("Resolved conflicted state of '%s'\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_add:
+ /* We *should* only get the MIME_TYPE if PATH is a file. If we
+ do get it, and the mime-type is not textual, note that this
+ is a binary addition. */
+ if (n->mime_type && (svn_mime_type_is_binary(n->mime_type)))
+ {
+ if ((err = svn_cmdline_printf(pool, "A (bin) %s\n",
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool, "A %s\n",
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, "D %s\n",
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch:
+ {
+ nb->received_some_change = TRUE;
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_patch_applied_hunk:
+ nb->received_some_change = TRUE;
+ if (n->hunk_original_start != n->hunk_matched_line)
+ {
+ apr_uint64_t off;
+ const char *s;
+ const char *minus;
+
+ if (n->hunk_matched_line > n->hunk_original_start)
+ {
+ /* If we are patching from the start of an empty file,
+ it is nicer to show offset 0 */
+ if (n->hunk_original_start == 0 && n->hunk_matched_line == 1)
+ off = 0; /* No offset, just adding */
+ else
+ off = n->hunk_matched_line - n->hunk_original_start;
+
+ minus = "";
+ }
+ else
+ {
+ off = n->hunk_original_start - n->hunk_matched_line;
+ minus = "-";
+ }
+
+ /* ### We're creating the localized strings without
+ * ### APR_INT64_T_FMT since it isn't translator-friendly */
+ if (n->hunk_fuzz)
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz,
+ n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT
+ " and fuzz %lu\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->hunk_fuzz);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ else
+ {
+
+ if (n->prop_name)
+ {
+ s = _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT" (%s)\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off, n->prop_name);
+ }
+ else
+ {
+ s = _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with offset %s");
+ err = svn_cmdline_printf(pool,
+ apr_pstrcat(pool, s,
+ "%"APR_UINT64_T_FMT"\n",
+ (char *)NULL),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ minus, off);
+ }
+
+ if (err)
+ goto print_error;
+ }
+ }
+ else if (n->hunk_fuzz)
+ {
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk ## -%lu,%lu +%lu,%lu ## "
+ "with fuzz %lu (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> applied hunk @@ -%lu,%lu +%lu,%lu @@ "
+ "with fuzz %lu\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->hunk_fuzz);
+ if (err)
+ goto print_error;
+
+ }
+ break;
+
+ case svn_wc_notify_patch_rejected_hunk:
+ nb->received_some_change = TRUE;
+
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "## -%lu,%lu +%lu,%lu ## (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> rejected hunk "
+ "@@ -%lu,%lu +%lu,%lu @@\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_patch_hunk_already_applied:
+ nb->received_some_change = TRUE;
+ if (n->prop_name)
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "## -%lu,%lu +%lu,%lu ## "
+ "already applied (%s)\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length,
+ n->prop_name);
+ else
+ err = svn_cmdline_printf(pool,
+ _("> hunk "
+ "@@ -%lu,%lu +%lu,%lu @@ "
+ "already applied\n"),
+ n->hunk_original_start,
+ n->hunk_original_length,
+ n->hunk_modified_start,
+ n->hunk_modified_length);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_update:
+ case svn_wc_notify_merge_record_info:
+ {
+ if (n->content_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
+ statchar_buf[0] = 'C';
+ }
+ else if (n->kind == svn_node_file)
+ {
+ if (n->content_state == svn_wc_notify_state_merged)
+ statchar_buf[0] = 'G';
+ else if (n->content_state == svn_wc_notify_state_changed)
+ statchar_buf[0] = 'U';
+ }
+
+ if (n->prop_state == svn_wc_notify_state_conflicted)
+ {
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
+ statchar_buf[1] = 'C';
+ }
+ else if (n->prop_state == svn_wc_notify_state_merged)
+ statchar_buf[1] = 'G';
+ else if (n->prop_state == svn_wc_notify_state_changed)
+ statchar_buf[1] = 'U';
+
+ if (n->lock_state == svn_wc_notify_lock_state_unlocked)
+ statchar_buf[2] = 'B';
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ')
+ nb->received_some_change = TRUE;
+
+ if (statchar_buf[0] != ' ' || statchar_buf[1] != ' '
+ || statchar_buf[2] != ' ')
+ {
+ if ((err = svn_cmdline_printf(pool, "%s %s\n",
+ statchar_buf, path_local)))
+ goto print_error;
+ }
+ }
+ break;
+
+ case svn_wc_notify_update_external:
+ /* Remember that we're now "inside" an externals definition. */
+ nb->in_external = TRUE;
+
+ /* Currently this is used for checkouts and switches too. If we
+ want different output, we'll have to add new actions. */
+ if ((err = svn_cmdline_printf(pool,
+ _("\nFetching external item into '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_external:
+ /* If we are currently inside the handling of an externals
+ definition, then we can simply present n->err as a warning
+ and feel confident that after this, we aren't handling that
+ externals definition any longer. */
+ if (nb->in_external)
+ {
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ /* Otherwise, we'll just print two warnings. Why? Because
+ svn_handle_warning2() only shows the single "best message",
+ but we have two pretty important ones: that the external at
+ '/some/path' didn't pan out, and then the more specific
+ reason why (from n->err). */
+ else
+ {
+ svn_error_t *warn_err =
+ svn_error_createf(SVN_ERR_BASE, NULL,
+ _("Error handling externals definition for '%s':"),
+ path_local);
+ svn_handle_warning2(stderr, warn_err, "svn: ");
+ svn_error_clear(warn_err);
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ }
+ break;
+
+ case svn_wc_notify_update_started:
+ if (! (nb->in_external ||
+ nb->is_checkout ||
+ nb->is_export))
+ {
+ if ((err = svn_cmdline_printf(pool, _("Updating '%s':\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_update_completed:
+ {
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Exported external at revision %ld.\n")
+ : _("Exported revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Checked out external at revision %ld.\n")
+ : _("Checked out revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if (nb->received_some_change)
+ {
+ nb->received_some_change = FALSE;
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("Updated external to revision %ld.\n")
+ : _("Updated to revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External at revision %ld.\n")
+ : _("At revision %ld.\n"),
+ n->revision)))
+ goto print_error;
+ }
+ }
+ }
+ else /* no revision */
+ {
+ if (nb->is_export)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External export complete.\n")
+ : _("Export complete.\n"))))
+ goto print_error;
+ }
+ else if (nb->is_checkout)
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External checkout complete.\n")
+ : _("Checkout complete.\n"))))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf
+ (pool, nb->in_external
+ ? _("External update complete.\n")
+ : _("Update complete.\n"))))
+ goto print_error;
+ }
+ }
+ }
+
+ if (nb->in_external)
+ {
+ nb->in_external = FALSE;
+ if ((err = svn_cmdline_printf(pool, "\n")))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_status_external:
+ if ((err = svn_cmdline_printf
+ (pool, _("\nPerforming status on external item at '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_status_completed:
+ if (SVN_IS_VALID_REVNUM(n->revision))
+ if ((err = svn_cmdline_printf(pool,
+ _("Status against revision: %6ld\n"),
+ n->revision)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_modified:
+ /* xgettext: Align the %s's on this and the following 4 messages */
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Sending copy of %s\n")
+ : _("Sending %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_added:
+ case svn_wc_notify_commit_copied:
+ if (n->mime_type && svn_mime_type_is_binary(n->mime_type))
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of (bin) %s\n")
+ : _("Adding (bin) %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ else
+ {
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Adding copy of %s\n")
+ : _("Adding %s\n"),
+ path_local)))
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_commit_deleted:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Deleting copy of %s\n")
+ : _("Deleting %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_replaced:
+ case svn_wc_notify_commit_copied_replaced:
+ if ((err = svn_cmdline_printf(pool,
+ nb->is_wc_to_repos_copy
+ ? _("Replacing copy of %s\n")
+ : _("Replacing %s\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_commit_postfix_txdelta:
+ if (! nb->sent_first_txdelta)
+ {
+ nb->sent_first_txdelta = TRUE;
+ if ((err = svn_cmdline_printf(pool,
+ _("Transmitting file data "))))
+ goto print_error;
+ }
+
+ if ((err = svn_cmdline_printf(pool, ".")))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_locked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
+ path_local, n->lock->owner)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_unlocked:
+ if ((err = svn_cmdline_printf(pool, _("'%s' unlocked.\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_failed_lock:
+ case svn_wc_notify_failed_unlock:
+ svn_handle_warning2(stderr, n->err, "svn: ");
+ break;
+
+ case svn_wc_notify_changelist_set:
+ if ((err = svn_cmdline_printf(pool, "A [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_changelist_clear:
+ case svn_wc_notify_changelist_moved:
+ if ((err = svn_cmdline_printf(pool,
+ "D [%s] %s\n",
+ n->changelist_name, path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool, _("--- Merging r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging r%ld through r%ld "
+ "into '%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_record_info_begin:
+ if (!n->merge_range)
+ {
+ err = svn_cmdline_printf(pool,
+ _("--- Recording mergeinfo for merge "
+ "between repository URLs into '%s':\n"),
+ path_local);
+ }
+ else
+ {
+ if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1, n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Recording mergeinfo for reverse merge of r%ld through r%ld into '%s':\n"),
+ n->merge_range->start, n->merge_range->end + 1, path_local);
+ }
+
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_merge_elide_info:
+ if ((err = svn_cmdline_printf(pool,
+ _("--- Eliding mergeinfo from '%s':\n"),
+ path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_foreign_merge_begin:
+ if (n->merge_range == NULL)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging differences between "
+ "foreign repository URLs into '%s':\n"),
+ path_local);
+ else if (n->merge_range->start == n->merge_range->end - 1
+ || n->merge_range->start == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld into '%s':\n"),
+ n->merge_range->end, path_local);
+ else if (n->merge_range->start - 1 == n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld into '%s':\n"),
+ n->merge_range->start, path_local);
+ else if (n->merge_range->start < n->merge_range->end)
+ err = svn_cmdline_printf(pool,
+ _("--- Merging (from foreign repository) "
+ "r%ld through r%ld into '%s':\n"),
+ n->merge_range->start + 1,
+ n->merge_range->end, path_local);
+ else /* n->merge_range->start > n->merge_range->end - 1 */
+ err = svn_cmdline_printf(pool,
+ _("--- Reverse-merging (from foreign "
+ "repository) r%ld through r%ld into "
+ "'%s':\n"),
+ n->merge_range->start,
+ n->merge_range->end + 1, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_tree_conflict:
+ store_path(nb, nb->conflict_stats->tree_conflicts, path_local);
+ if ((err = svn_cmdline_printf(pool, " C %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_add:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " A %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_update:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " U %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_update_shadowed_delete:
+ nb->received_some_change = TRUE;
+ if ((err = svn_cmdline_printf(pool, " D %s\n", path_local)))
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_modified:
+ case svn_wc_notify_property_added:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on '%s'\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from '%s'.\n"),
+ n->prop_name, path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_property_deleted_nonexistent:
+ err = svn_cmdline_printf(pool,
+ _("Attempting to delete nonexistent "
+ "property '%s' on '%s'\n"), n->prop_name,
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_set:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' set on repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_revprop_deleted:
+ err = svn_cmdline_printf(pool,
+ _("property '%s' deleted from repository revision %ld\n"),
+ n->prop_name, n->revision);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_upgraded_path:
+ err = svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_url_redirect:
+ err = svn_cmdline_printf(pool, _("Redirecting to URL '%s':\n"),
+ n->url);
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_path_nonexistent:
+ err = svn_cmdline_printf(pool, "%s\n",
+ apr_psprintf(pool, _("'%s' is not under version control"),
+ path_local));
+ if (err)
+ goto print_error;
+ break;
+
+ case svn_wc_notify_conflict_resolver_starting:
+ /* Once all operations invoke the interactive conflict resolution after
+ * they've completed, we can run svn_cl__notifier_print_conflict_stats()
+ * here. */
+ break;
+
+ case svn_wc_notify_conflict_resolver_done:
+ break;
+
+ case svn_wc_notify_foreign_copy_begin:
+ if (n->merge_range == NULL)
+ {
+ err = svn_cmdline_printf(
+ pool,
+ _("--- Copying from foreign repository URL '%s':\n"),
+ n->url);
+ if (err)
+ goto print_error;
+ }
+ break;
+
+ case svn_wc_notify_move_broken:
+ err = svn_cmdline_printf(pool,
+ _("Breaking move with source path '%s'\n"),
+ path_local);
+ if (err)
+ goto print_error;
+ break;
+
+ default:
+ break;
+ }
+
+ if ((err = svn_cmdline_fflush(stdout)))
+ goto print_error;
+
+ return;
+
+ print_error:
+ /* If we had no errors before, print this error to stderr. Else, don't print
+ anything. The user already knows there were some output errors,
+ so there is no point in flooding her with an error per notification. */
+ if (!nb->had_print_error)
+ {
+ nb->had_print_error = TRUE;
+ /* Issue #3014:
+ * Don't print anything on broken pipes. The pipe was likely
+ * closed by the process at the other end. We expect that
+ * process to perform error reporting as necessary.
+ *
+ * ### This assumes that there is only one error in a chain for
+ * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
+ if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
+ svn_handle_error2(err, stderr, FALSE, "svn: ");
+ }
+ svn_error_clear(err);
+}
+
+
+svn_error_t *
+svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
+ void **notify_baton_p,
+ svn_cl__conflict_stats_t *conflict_stats,
+ apr_pool_t *pool)
+{
+ struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb));
+
+ nb->received_some_change = FALSE;
+ nb->sent_first_txdelta = FALSE;
+ nb->is_checkout = FALSE;
+ nb->is_export = FALSE;
+ nb->is_wc_to_repos_copy = FALSE;
+ nb->in_external = FALSE;
+ nb->had_print_error = FALSE;
+ nb->conflict_stats = conflict_stats;
+ SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool));
+
+ *notify_func_p = notify;
+ *notify_baton_p = nb;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_checkout(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_checkout = TRUE;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_export(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_export = TRUE;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_cl__notifier_mark_wc_to_repos_copy(void *baton)
+{
+ struct notify_baton *nb = baton;
+
+ nb->is_wc_to_repos_copy = TRUE;
+ return SVN_NO_ERROR;
+}
+
+void
+svn_cl__check_externals_failed_notify_wrapper(void *baton,
+ const svn_wc_notify_t *n,
+ apr_pool_t *pool)
+{
+ struct svn_cl__check_externals_failed_notify_baton *nwb = baton;
+
+ if (n->action == svn_wc_notify_failed_external)
+ nwb->had_externals_error = TRUE;
+
+ if (nwb->wrapped_func)
+ nwb->wrapped_func(nwb->wrapped_baton, n, pool);
+}
diff --git a/subversion/svn/patch-cmd.c b/subversion/svn/patch-cmd.c
new file mode 100644
index 000000000000..83707c63e2ad
--- /dev/null
+++ b/subversion/svn/patch-cmd.c
@@ -0,0 +1,98 @@
+/*
+ * patch-cmd.c -- Apply changes to a working copy.
+ *
+ * ====================================================================
+ * 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 "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_types.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__patch(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_client_ctx_t *ctx;
+ apr_array_header_t *targets;
+ const char *abs_patch_path;
+ const char *patch_path;
+ const char *abs_target_path;
+ const char *target_path;
+
+ opt_state = ((svn_cl__cmd_baton_t *)baton)->opt_state;
+ ctx = ((svn_cl__cmd_baton_t *)baton)->ctx;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (targets->nelts > 2)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
+
+ patch_path = APR_ARRAY_IDX(targets, 0, const char *);
+
+ SVN_ERR(svn_cl__check_target_is_local_path(patch_path));
+
+ SVN_ERR(svn_dirent_get_absolute(&abs_patch_path, patch_path, pool));
+
+ if (targets->nelts == 1)
+ target_path = ""; /* "" is the canonical form of "." */
+ else
+ {
+ target_path = APR_ARRAY_IDX(targets, 1, const char *);
+
+ SVN_ERR(svn_cl__check_target_is_local_path(target_path));
+ }
+ SVN_ERR(svn_dirent_get_absolute(&abs_target_path, target_path, pool));
+
+ SVN_ERR(svn_client_patch(abs_patch_path, abs_target_path,
+ opt_state->dry_run, opt_state->strip,
+ opt_state->reverse_diff,
+ opt_state->ignore_whitespace,
+ TRUE, NULL, NULL, ctx, pool));
+
+
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cl__notifier_print_conflict_stats(ctx->notify_baton2, pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/propdel-cmd.c b/subversion/svn/propdel-cmd.c
new file mode 100644
index 000000000000..28c95970b41d
--- /dev/null
+++ b/subversion/svn/propdel-cmd.c
@@ -0,0 +1,103 @@
+/*
+ * propdel-cmd.c -- Remove property from files/dirs
+ *
+ * ====================================================================
+ * 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 "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_path.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propdel(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ apr_array_header_t *args, *targets;
+
+ /* Get the property's name (and a UTF-8 version of that name). */
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
+ /* No need to check svn_prop_name_is_valid for *deleting*
+ properties, and it may even be useful to allow, in case invalid
+ properties sneaked through somehow. */
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+
+ /* Add "." if user passed 0 file arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_set2(pname_utf8, NULL, NULL,
+ URL, &(opt_state->start_revision),
+ &rev, FALSE, ctx, pool));
+ }
+ else if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("Cannot specify revision for deleting versioned property '%s'"),
+ pname);
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ /* For each target, remove the property PNAME. */
+ SVN_ERR(svn_client_propset_local(pname_utf8, NULL, targets,
+ opt_state->depth, FALSE,
+ opt_state->changelists, ctx, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/propedit-cmd.c b/subversion/svn/propedit-cmd.c
new file mode 100644
index 000000000000..520fe6c00c87
--- /dev/null
+++ b/subversion/svn/propedit-cmd.c
@@ -0,0 +1,356 @@
+/*
+ * propedit-cmd.c -- Edit properties of files/dirs using $EDITOR
+ *
+ * ====================================================================
+ * 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 "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_props.h"
+#include "cl.h"
+
+#include "private/svn_cmdline_private.h"
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+struct commit_info_baton
+{
+ const char *pname_utf8;
+ const char *target_local;
+};
+
+static svn_error_t *
+commit_info_handler(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct commit_info_baton *cib = baton;
+
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Set new value for property '%s' on '%s'\n"),
+ cib->pname_utf8, cib->target_local));
+ SVN_ERR(svn_cl__print_commit_info(commit_info, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propedit(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ apr_array_header_t *args, *targets;
+
+ /* Validate the input and get the property's name (and a UTF-8
+ version of that name). */
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
+ if (! svn_prop_name_is_valid(pname_utf8))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid Subversion property name"),
+ pname_utf8);
+ if (!opt_state->force)
+ SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop,
+ svn_cl__prop_use_edit, pool));
+
+ if (opt_state->encoding && !svn_prop_needs_translation(pname_utf8))
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("--encoding option applies only to textual"
+ " Subversion-controlled properties"));
+
+ /* Suck up all the remaining arguments into a targets array */
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* We do our own notifications */
+ ctx->notify_func2 = NULL;
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+ svn_string_t *propval;
+ svn_string_t original_propval;
+ const char *temp_dir;
+
+ /* Implicit "." is okay for revision properties; it just helps
+ us find the right repository. */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, pool));
+
+ /* Fetch the current property. */
+ SVN_ERR(svn_client_revprop_get(pname_utf8, &propval,
+ URL, &(opt_state->start_revision),
+ &rev, ctx, pool));
+
+ if (! propval)
+ {
+ propval = svn_string_create_empty(pool);
+ /* This is how we signify to svn_client_revprop_set2() that
+ we want it to check that the original value hasn't
+ changed, but that that original value was non-existent: */
+ original_propval.data = NULL; /* and .len is ignored */
+ }
+ else
+ {
+ original_propval = *propval;
+ }
+
+ /* Run the editor on a temporary file which contains the
+ original property value... */
+ SVN_ERR(svn_io_temp_dir(&temp_dir, pool));
+ SVN_ERR(svn_cmdline__edit_string_externally(
+ &propval, NULL,
+ opt_state->editor_cmd, temp_dir,
+ propval, "svn-prop",
+ ctx->config,
+ svn_prop_needs_translation(pname_utf8),
+ opt_state->encoding, pool));
+
+ /* ...and re-set the property's value accordingly. */
+ if (propval)
+ {
+ SVN_ERR(svn_client_revprop_set2(pname_utf8,
+ propval, &original_propval,
+ URL, &(opt_state->start_revision),
+ &rev, opt_state->force, ctx, pool));
+
+ SVN_ERR
+ (svn_cmdline_printf
+ (pool,
+ _("Set new value for property '%s' on revision %ld\n"),
+ pname_utf8, rev));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf
+ (pool, _("No changes to property '%s' on revision %ld\n"),
+ pname_utf8, rev));
+ }
+ }
+ else if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot specify revision for editing versioned property '%s'"),
+ pname_utf8);
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ struct commit_info_baton cib;
+ int i;
+
+ /* The customary implicit dot rule has been prone to user error
+ * here. For example, Jon Trowbridge <trow@gnu.og> did
+ *
+ * $ svn propedit HACKING
+ *
+ * and then when he closed his editor, he was surprised to see
+ *
+ * Set new value for property 'HACKING' on ''
+ *
+ * ...meaning that the property named 'HACKING' had been set on
+ * the current working directory, with the value taken from the
+ * editor. So we don't do the implicit dot thing anymore; an
+ * explicit target is always required when editing a versioned
+ * property.
+ */
+ if (targets->nelts == 0)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Explicit target argument required"));
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+
+ cib.pname_utf8 = pname_utf8;
+
+ /* For each target, edit the property PNAME. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ apr_hash_t *props;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_string_t *propval, *edited_propval;
+ const char *base_dir = target;
+ const char *target_local;
+ const char *abspath_or_url;
+ svn_node_kind_t kind;
+ svn_opt_revision_t peg_revision;
+ svn_revnum_t base_rev = SVN_INVALID_REVNUM;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ if (!svn_path_is_url(target))
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url, target, subpool));
+ else
+ abspath_or_url = target;
+
+ /* Propedits can only happen on HEAD or the working copy, so
+ the peg revision can be as unspecified. */
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ /* Fetch the current property. */
+ SVN_ERR(svn_client_propget5(&props, NULL, pname_utf8, abspath_or_url,
+ &peg_revision,
+ &(opt_state->start_revision),
+ &base_rev, svn_depth_empty,
+ NULL, ctx, subpool, subpool));
+
+ /* Get the property value. */
+ propval = svn_hash_gets(props, abspath_or_url);
+ if (! propval)
+ propval = svn_string_create_empty(subpool);
+
+ if (svn_path_is_url(target))
+ {
+ /* For URLs, put the temporary file in the current directory. */
+ base_dir = ".";
+ }
+ else
+ {
+ if (opt_state->message || opt_state->filedata ||
+ opt_state->revprop_table)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL,
+ _("Local, non-commit operations do not take a log message "
+ "or revision properties"));
+ }
+
+ /* Split the path if it is a file path. */
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath_or_url,
+ FALSE, FALSE, subpool));
+
+ if (kind == svn_node_none)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("'%s' does not appear to be a working copy path"), target);
+ if (kind == svn_node_file)
+ base_dir = svn_dirent_dirname(target, subpool);
+ }
+
+ /* Run the editor on a temporary file which contains the
+ original property value... */
+ SVN_ERR(svn_cmdline__edit_string_externally(&edited_propval, NULL,
+ opt_state->editor_cmd,
+ base_dir,
+ propval,
+ "svn-prop",
+ ctx->config,
+ svn_prop_needs_translation
+ (pname_utf8),
+ opt_state->encoding,
+ subpool));
+
+ target_local = svn_path_is_url(target) ? target
+ : svn_dirent_local_style(target, subpool);
+ cib.target_local = target_local;
+
+ /* ...and re-set the property's value accordingly. */
+ if (edited_propval && !svn_string_compare(propval, edited_propval))
+ {
+ svn_error_t *err = SVN_NO_ERROR;
+
+ svn_cl__check_boolean_prop_val(pname_utf8, edited_propval->data,
+ subpool);
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3),
+ opt_state, NULL, ctx->config,
+ subpool));
+ if (svn_path_is_url(target))
+ {
+ err = svn_client_propset_remote(pname_utf8, edited_propval,
+ target, opt_state->force,
+ base_rev,
+ opt_state->revprop_table,
+ commit_info_handler, &cib,
+ ctx, subpool);
+ }
+ else
+ {
+ apr_array_header_t *targs = apr_array_make(subpool, 1,
+ sizeof(const char *));
+
+ APR_ARRAY_PUSH(targs, const char *) = target;
+
+ SVN_ERR(svn_cl__propset_print_binary_mime_type_warning(
+ targs, pname_utf8, propval, subpool));
+
+ err = svn_client_propset_local(pname_utf8, edited_propval,
+ targs, svn_depth_empty,
+ opt_state->force, NULL,
+ ctx, subpool);
+ }
+
+ if (ctx->log_msg_func3)
+ SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3,
+ err, pool));
+ else if (err)
+ return svn_error_trace(err);
+
+ /* Print a message if we successfully committed or if it
+ was just a wc propset (but not if the user aborted a URL
+ propedit). */
+ if (!svn_path_is_url(target))
+ SVN_ERR(svn_cmdline_printf(
+ subpool, _("Set new value for property '%s' on '%s'\n"),
+ pname_utf8, target_local));
+ }
+ else
+ {
+ SVN_ERR
+ (svn_cmdline_printf
+ (subpool, _("No changes to property '%s' on '%s'\n"),
+ pname_utf8, target_local));
+ }
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/propget-cmd.c b/subversion/svn/propget-cmd.c
new file mode 100644
index 000000000000..e291911845b0
--- /dev/null
+++ b/subversion/svn/propget-cmd.c
@@ -0,0 +1,493 @@
+/*
+ * propget-cmd.c -- Print properties and values of files/dirs
+ *
+ * ====================================================================
+ * 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 "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_xml.h"
+#include "cl.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+static svn_error_t *
+stream_write(svn_stream_t *out,
+ const char *data,
+ apr_size_t len)
+{
+ apr_size_t write_len = len;
+
+ /* We're gonna bail on an incomplete write here only because we know
+ that this stream is really stdout, which should never be blocking
+ on us. */
+ SVN_ERR(svn_stream_write(out, data, &write_len));
+ if (write_len != len)
+ return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Error writing to stream"));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+print_properties_xml(const char *pname,
+ apr_hash_t *props,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *sorted_props;
+ int i;
+ apr_pool_t *iterpool = NULL;
+ svn_stringbuf_t *sb;
+
+ if (inherited_props && inherited_props->nelts)
+ {
+ iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ const char *name_local;
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+ svn_string_t *propval = svn__apr_hash_index_val(
+ apr_hash_first(pool, iprop->prop_hash));
+
+ sb = NULL;
+ svn_pool_clear(iterpool);
+
+ if (svn_path_is_url(iprop->path_or_url))
+ name_local = iprop->path_or_url;
+ else
+ name_local = svn_dirent_local_style(iprop->path_or_url, iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+
+ svn_cmdline__print_xml_prop(&sb, pname, propval, TRUE, iterpool);
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ }
+
+ if (iterpool == NULL)
+ iterpool = svn_pool_create(iterpool);
+
+ sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
+ for (i = 0; i < sorted_props->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
+ const char *filename = item.key;
+ svn_string_t *propval = item.value;
+
+ sb = NULL;
+ svn_pool_clear(iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", filename, NULL);
+ svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, iterpool);
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ if (iterpool)
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Print the property PNAME_UTF with the value PROPVAL set on ABSPATH_OR_URL
+ to the stream OUT.
+
+ If INHERITED_PROPERTY is true then the property described is inherited,
+ otherwise it is explicit.
+
+ WC_PATH_PREFIX is the absolute path of the current working directory (and
+ is ignored if ABSPATH_OR_URL is a URL).
+
+ All other arguments are as per print_properties. */
+static svn_error_t *
+print_single_prop(svn_string_t *propval,
+ const char *target_abspath_or_url,
+ const char *abspath_or_URL,
+ const char *wc_path_prefix,
+ svn_stream_t *out,
+ const char *pname_utf8,
+ svn_boolean_t print_filenames,
+ svn_boolean_t omit_newline,
+ svn_boolean_t like_proplist,
+ svn_boolean_t inherited_property,
+ apr_pool_t *scratch_pool)
+{
+ if (print_filenames)
+ {
+ const char *header;
+
+ /* Print the file name. */
+
+ if (! svn_path_is_url(abspath_or_URL))
+ abspath_or_URL = svn_cl__local_style_skip_ancestor(wc_path_prefix,
+ abspath_or_URL,
+ scratch_pool);
+
+ /* In verbose mode, print exactly same as "proplist" does;
+ * otherwise, print a brief header. */
+ if (inherited_property)
+ {
+ if (like_proplist)
+ {
+ if (! svn_path_is_url(target_abspath_or_url))
+ target_abspath_or_url =
+ svn_cl__local_style_skip_ancestor(wc_path_prefix,
+ target_abspath_or_url,
+ scratch_pool);
+ header = apr_psprintf(
+ scratch_pool,
+ _("Inherited properties on '%s',\nfrom '%s':\n"),
+ target_abspath_or_url, abspath_or_URL);
+ }
+ else
+ {
+ header = apr_psprintf(scratch_pool, "%s - ", abspath_or_URL);
+ }
+ }
+ else
+ header = apr_psprintf(scratch_pool, like_proplist
+ ? _("Properties on '%s':\n")
+ : "%s - ", abspath_or_URL);
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, scratch_pool));
+ SVN_ERR(svn_subst_translate_cstring2(header, &header,
+ APR_EOL_STR, /* 'native' eol */
+ FALSE, /* no repair */
+ NULL, /* no keywords */
+ FALSE, /* no expansion */
+ scratch_pool));
+ SVN_ERR(stream_write(out, header, strlen(header)));
+ }
+
+ if (like_proplist)
+ {
+ /* Print the property name and value just as "proplist -v" does */
+ apr_hash_t *hash = apr_hash_make(scratch_pool);
+
+ svn_hash_sets(hash, pname_utf8, propval);
+ SVN_ERR(svn_cmdline__print_prop_hash(out, hash, FALSE, scratch_pool));
+ }
+ else
+ {
+ /* If this is a special Subversion property, it is stored as
+ UTF8, so convert to the native format. */
+ if (svn_prop_needs_translation(pname_utf8))
+ SVN_ERR(svn_subst_detranslate_string(&propval, propval,
+ TRUE, scratch_pool));
+
+ SVN_ERR(stream_write(out, propval->data, propval->len));
+
+ if (! omit_newline)
+ SVN_ERR(stream_write(out, APR_EOL_STR,
+ strlen(APR_EOL_STR)));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Print the properties in PROPS and/or *INHERITED_PROPS to the stream OUT.
+ PROPS is a hash mapping (const char *) path to (svn_string_t) property
+ value. INHERITED_PROPS is a depth-first ordered array of
+ svn_prop_inherited_item_t * structures.
+
+ TARGET_ABSPATH_OR_URL is the path which inherits INHERITED_PROPS.
+
+ PROPS may be an empty hash, but is never null. INHERITED_PROPS may be
+ null.
+
+ If IS_URL is true, all paths in PROPS are URLs, else all paths are local
+ paths.
+
+ PNAME_UTF8 is the property name of all the properties.
+
+ If PRINT_FILENAMES is true, print the item's path before each property.
+
+ If OMIT_NEWLINE is true, don't add a newline at the end of each property.
+
+ If LIKE_PROPLIST is true, print everything in a more verbose format
+ like "svn proplist -v" does. */
+static svn_error_t *
+print_properties(svn_stream_t *out,
+ const char *target_abspath_or_url,
+ const char *pname_utf8,
+ apr_hash_t *props,
+ apr_array_header_t *inherited_props,
+ svn_boolean_t print_filenames,
+ svn_boolean_t omit_newline,
+ svn_boolean_t like_proplist,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *sorted_props;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ const char *path_prefix;
+
+ SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
+
+ if (inherited_props)
+ {
+ svn_pool_clear(iterpool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+ svn_string_t *propval = svn__apr_hash_index_val(apr_hash_first(pool,
+ iprop->prop_hash));
+ SVN_ERR(print_single_prop(propval, target_abspath_or_url,
+ iprop->path_or_url,
+ path_prefix, out, pname_utf8,
+ print_filenames, omit_newline,
+ like_proplist, TRUE, iterpool));
+ }
+ }
+
+ sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
+ for (i = 0; i < sorted_props->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
+ const char *filename = item.key;
+ svn_string_t *propval = item.value;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(print_single_prop(propval, target_abspath_or_url, filename,
+ path_prefix, out, pname_utf8, print_filenames,
+ omit_newline, like_proplist, FALSE,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propget(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ apr_array_header_t *args, *targets;
+ svn_stream_t *out;
+
+ if (opt_state->verbose && (opt_state->revprop || opt_state->strict
+ || opt_state->xml))
+ return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--verbose cannot be used with --revprop or "
+ "--strict or --xml"));
+
+ /* PNAME is first argument (and PNAME_UTF8 will be a UTF-8 version
+ thereof) */
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
+ if (! svn_prop_name_is_valid(pname_utf8))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid Subversion property name"),
+ pname_utf8);
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE, pool));
+
+ /* Add "." if user passed 0 file arguments */
+ svn_opt_push_implicit_dot_target(targets, pool);
+
+ /* Open a stream to stdout. */
+ SVN_ERR(svn_stream_for_stdout(&out, pool));
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+ svn_string_t *propval;
+
+ if (opt_state->show_inherited_props)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--show-inherited-props can't be used with --revprop"));
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_get(pname_utf8, &propval,
+ URL, &(opt_state->start_revision),
+ &rev, ctx, pool));
+
+ if (propval != NULL)
+ {
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = NULL;
+ char *revstr = apr_psprintf(pool, "%ld", rev);
+
+ SVN_ERR(svn_cl__xml_print_header("properties", pool));
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal,
+ "revprops",
+ "rev", revstr, NULL);
+
+ svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, FALSE,
+ pool);
+
+ svn_xml_make_close_tag(&sb, pool, "revprops");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ SVN_ERR(svn_cl__xml_print_footer("properties", pool));
+ }
+ else
+ {
+ svn_string_t *printable_val = propval;
+
+ /* If this is a special Subversion property, it is stored as
+ UTF8 and LF, so convert to the native locale and eol-style. */
+
+ if (svn_prop_needs_translation(pname_utf8))
+ SVN_ERR(svn_subst_detranslate_string(&printable_val, propval,
+ TRUE, pool));
+
+ SVN_ERR(stream_write(out, printable_val->data,
+ printable_val->len));
+ if (! opt_state->strict)
+ SVN_ERR(stream_write(out, APR_EOL_STR, strlen(APR_EOL_STR)));
+ }
+ }
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ int i;
+
+ if (opt_state->xml)
+ SVN_ERR(svn_cl__xml_print_header("properties", subpool));
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ /* Strict mode only makes sense for a single target. So make
+ sure we have only a single target, and that we're not being
+ asked to recurse on that target. */
+ if (opt_state->strict
+ && ((targets->nelts > 1) || (opt_state->depth != svn_depth_empty)))
+ return svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Strict output of property values only available for single-"
+ "target, non-recursive propget operations"));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ apr_hash_t *props;
+ svn_boolean_t print_filenames;
+ svn_boolean_t omit_newline;
+ svn_boolean_t like_proplist;
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+ apr_array_header_t *inherited_props;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ /* Check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ subpool));
+
+ if (!svn_path_is_url(truepath))
+ SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
+
+ SVN_ERR(svn_client_propget5(
+ &props,
+ opt_state->show_inherited_props ? &inherited_props : NULL,
+ pname_utf8, truepath,
+ &peg_revision,
+ &(opt_state->start_revision),
+ NULL, opt_state->depth,
+ opt_state->changelists, ctx, subpool,
+ subpool));
+
+ /* Any time there is more than one thing to print, or where
+ the path associated with a printed thing is not obvious,
+ we'll print filenames. That is, unless we've been told
+ not to do so with the --strict option. */
+ print_filenames = ((opt_state->depth > svn_depth_empty
+ || targets->nelts > 1
+ || apr_hash_count(props) > 1
+ || opt_state->verbose
+ || opt_state->show_inherited_props)
+ && (! opt_state->strict));
+ omit_newline = opt_state->strict;
+ like_proplist = opt_state->verbose && !opt_state->strict;
+
+ if (opt_state->xml)
+ SVN_ERR(print_properties_xml(
+ pname_utf8, props,
+ opt_state->show_inherited_props ? inherited_props : NULL,
+ subpool));
+ else
+ SVN_ERR(print_properties(
+ out, truepath, pname_utf8,
+ props,
+ opt_state->show_inherited_props ? inherited_props : NULL,
+ print_filenames,
+ omit_newline, like_proplist, subpool));
+ }
+
+ if (opt_state->xml)
+ SVN_ERR(svn_cl__xml_print_footer("properties", subpool));
+
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/proplist-cmd.c b/subversion/svn/proplist-cmd.c
new file mode 100644
index 000000000000..fe23a67f6143
--- /dev/null
+++ b/subversion/svn/proplist-cmd.c
@@ -0,0 +1,336 @@
+/*
+ * proplist-cmd.c -- List properties of files/dirs
+ *
+ * ====================================================================
+ * 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 "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "cl.h"
+
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+typedef struct proplist_baton_t
+{
+ svn_cl__opt_state_t *opt_state;
+ svn_boolean_t is_url;
+} proplist_baton_t;
+
+
+/*** Code. ***/
+
+/* This implements the svn_proplist_receiver2_t interface, printing XML to
+ stdout. */
+static svn_error_t *
+proplist_receiver_xml(void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state;
+ svn_boolean_t is_url = ((proplist_baton_t *)baton)->is_url;
+ svn_stringbuf_t *sb;
+ const char *name_local;
+
+ if (inherited_props && inherited_props->nelts)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ sb = NULL;
+
+ if (svn_path_is_url(iprop->path_or_url))
+ name_local = iprop->path_or_url;
+ else
+ name_local = svn_dirent_local_style(iprop->path_or_url, iterpool);
+
+ svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, iprop->prop_hash,
+ (! opt_state->verbose),
+ TRUE, iterpool));
+ svn_xml_make_close_tag(&sb, iterpool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ if (! is_url)
+ name_local = svn_dirent_local_style(path, pool);
+ else
+ name_local = path;
+
+ sb = NULL;
+
+
+ if (prop_hash)
+ {
+ /* "<target ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", name_local, NULL);
+
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, prop_hash,
+ (! opt_state->verbose),
+ FALSE, pool));
+
+ /* "</target>" */
+ svn_xml_make_close_tag(&sb, pool, "target");
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the svn_proplist_receiver2_t interface. */
+static svn_error_t *
+proplist_receiver(void *baton,
+ const char *path,
+ apr_hash_t *prop_hash,
+ apr_array_header_t *inherited_props,
+ apr_pool_t *pool)
+{
+ svn_cl__opt_state_t *opt_state = ((proplist_baton_t *)baton)->opt_state;
+ svn_boolean_t is_url = ((proplist_baton_t *)baton)->is_url;
+ const char *name_local;
+
+ if (! is_url)
+ name_local = svn_dirent_local_style(path, pool);
+ else
+ name_local = path;
+
+ if (inherited_props)
+ {
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *iprop =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ svn_pool_clear(iterpool);
+
+ if (!opt_state->quiet)
+ {
+ if (svn_path_is_url(iprop->path_or_url))
+ SVN_ERR(svn_cmdline_printf(
+ iterpool, _("Inherited properties on '%s',\nfrom '%s':\n"),
+ name_local, iprop->path_or_url));
+ else
+ SVN_ERR(svn_cmdline_printf(
+ iterpool, _("Inherited properties on '%s',\nfrom '%s':\n"),
+ name_local, svn_dirent_local_style(iprop->path_or_url,
+ iterpool)));
+ }
+
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, iprop->prop_hash,
+ (! opt_state->verbose),
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ if (prop_hash && apr_hash_count(prop_hash))
+ {
+ if (!opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"),
+ name_local));
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, prop_hash,
+ (! opt_state->verbose), pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__proplist(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_array_header_t *errors = apr_array_make(scratch_pool, 0,
+ sizeof(apr_status_t));
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Add "." if user passed 0 file arguments */
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ if (opt_state->revprop) /* operate on revprops */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+ apr_hash_t *proplist;
+
+ if (opt_state->show_inherited_props)
+ return svn_error_create(
+ SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--show-inherited-props can't be used with --revprop"));
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, scratch_pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_list(&proplist,
+ URL, &(opt_state->start_revision),
+ &rev, ctx, scratch_pool));
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_t *sb = NULL;
+ char *revstr = apr_psprintf(scratch_pool, "%ld", rev);
+
+ SVN_ERR(svn_cl__xml_print_header("properties", scratch_pool));
+
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal,
+ "revprops",
+ "rev", revstr, NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, proplist,
+ (! opt_state->verbose),
+ FALSE, scratch_pool));
+ svn_xml_make_close_tag(&sb, scratch_pool, "revprops");
+
+ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
+ SVN_ERR(svn_cl__xml_print_footer("properties", scratch_pool));
+ }
+ else
+ {
+ SVN_ERR
+ (svn_cmdline_printf(scratch_pool,
+ _("Unversioned properties on revision %ld:\n"),
+ rev));
+
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, proplist,
+ (! opt_state->verbose),
+ scratch_pool));
+ }
+ }
+ else /* operate on normal, versioned properties (not revprops) */
+ {
+ int i;
+ apr_pool_t *iterpool;
+ svn_proplist_receiver2_t pl_receiver;
+
+ if (opt_state->xml)
+ {
+ SVN_ERR(svn_cl__xml_print_header("properties", scratch_pool));
+ pl_receiver = proplist_receiver_xml;
+ }
+ else
+ {
+ pl_receiver = proplist_receiver;
+ }
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ proplist_baton_t pl_baton;
+ const char *truepath;
+ svn_opt_revision_t peg_revision;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ pl_baton.is_url = svn_path_is_url(target);
+ pl_baton.opt_state = opt_state;
+
+ /* Check for a peg revision. */
+ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
+ iterpool));
+
+ SVN_ERR(svn_cl__try(
+ svn_client_proplist4(truepath, &peg_revision,
+ &(opt_state->start_revision),
+ opt_state->depth,
+ opt_state->changelists,
+ opt_state->show_inherited_props,
+ pl_receiver, &pl_baton,
+ ctx, iterpool),
+ errors, opt_state->quiet,
+ SVN_ERR_UNVERSIONED_RESOURCE,
+ SVN_ERR_ENTRY_NOT_FOUND,
+ SVN_NO_ERROR));
+ }
+ svn_pool_destroy(iterpool);
+
+ if (opt_state->xml)
+ SVN_ERR(svn_cl__xml_print_footer("properties", scratch_pool));
+
+ /* Error out *after* we closed the XML element */
+ if (errors->nelts > 0)
+ {
+ svn_error_t *err;
+
+ err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, NULL);
+ for (i = 0; i < errors->nelts; i++)
+ {
+ apr_status_t status = APR_ARRAY_IDX(errors, i, apr_status_t);
+
+ if (status == SVN_ERR_ENTRY_NOT_FOUND)
+ err = svn_error_quick_wrap(err,
+ _("Could not display properties "
+ "of all targets because some "
+ "targets don't exist"));
+ else if (status == SVN_ERR_UNVERSIONED_RESOURCE)
+ err = svn_error_quick_wrap(err,
+ _("Could not display properties "
+ "of all targets because some "
+ "targets are not versioned"));
+ }
+
+ return svn_error_trace(err);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/props.c b/subversion/svn/props.c
new file mode 100644
index 000000000000..2a41ac81dcac
--- /dev/null
+++ b/subversion/svn/props.c
@@ -0,0 +1,356 @@
+/*
+ * props.c: Utility functions for property handling
+ *
+ * ====================================================================
+ * 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 <stdlib.h>
+
+#include <apr_hash.h>
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "svn_props.h"
+#include "svn_string.h"
+#include "svn_opt.h"
+#include "svn_xml.h"
+#include "svn_base64.h"
+#include "cl.h"
+
+#include "private/svn_string_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+svn_error_t *
+svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
+ const apr_array_header_t *targets,
+ const char **URL,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *target;
+
+ if (revision->kind != svn_opt_revision_number
+ && revision->kind != svn_opt_revision_date
+ && revision->kind != svn_opt_revision_head)
+ return svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Must specify the revision as a number, a date or 'HEAD' "
+ "when operating on a revision property"));
+
+ /* There must be exactly one target at this point. If it was optional and
+ unspecified by the user, the caller has already added the implicit '.'. */
+ if (targets->nelts != 1)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Wrong number of targets specified"));
+
+ /* (The docs say the target must be either a URL or implicit '.', but
+ explicit WC targets are also accepted.) */
+ target = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(svn_client_url_from_path2(URL, target, ctx, pool, pool));
+ if (*URL == NULL)
+ return svn_error_create
+ (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("Either a URL or versioned item is required"));
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *propbuf;
+
+ if (!svn_prop_is_boolean(propname))
+ return;
+
+ propbuf = svn_stringbuf_create(propval, pool);
+ svn_stringbuf_strip_whitespace(propbuf);
+
+ if (propbuf->data[0] == '\0'
+ || svn_cstring_casecmp(propbuf->data, "0") == 0
+ || svn_cstring_casecmp(propbuf->data, "no") == 0
+ || svn_cstring_casecmp(propbuf->data, "off") == 0
+ || svn_cstring_casecmp(propbuf->data, "false") == 0)
+ {
+ svn_error_t *err = svn_error_createf
+ (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
+ _("To turn off the %s property, use 'svn propdel';\n"
+ "setting the property to '%s' will not turn it off."),
+ propname, propval);
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ }
+}
+
+
+/* Context for sorting property names */
+struct simprop_context_t
+{
+ svn_string_t name; /* The name of the property we're comparing with */
+ svn_membuf_t buffer; /* Buffer for similarity testing */
+};
+
+struct simprop_t
+{
+ const char *propname; /* The original svn: property name */
+ svn_string_t name; /* The property name without the svn: prefix */
+ unsigned int score; /* The similarity score */
+ apr_size_t diff; /* Number of chars different from context.name */
+ struct simprop_context_t *context; /* Sorting context for qsort() */
+};
+
+/* Similarity test between two property names */
+static APR_INLINE unsigned int
+simprop_key_diff(const svn_string_t *key, const svn_string_t *ctx,
+ svn_membuf_t *buffer, apr_size_t *diff)
+{
+ apr_size_t lcs;
+ const unsigned int score = svn_string__similarity(key, ctx, buffer, &lcs);
+ if (key->len > ctx->len)
+ *diff = key->len - lcs;
+ else
+ *diff = ctx->len - lcs;
+ return score;
+}
+
+/* Key comparator for qsort for simprop_t */
+static int
+simprop_compare(const void *pkeya, const void *pkeyb)
+{
+ struct simprop_t *const keya = *(struct simprop_t *const *)pkeya;
+ struct simprop_t *const keyb = *(struct simprop_t *const *)pkeyb;
+ struct simprop_context_t *const context = keya->context;
+
+ if (keya->score == -1)
+ keya->score = simprop_key_diff(&keya->name, &context->name,
+ &context->buffer, &keya->diff);
+ if (keyb->score == -1)
+ keyb->score = simprop_key_diff(&keyb->name, &context->name,
+ &context->buffer, &keyb->diff);
+
+ return (keya->score < keyb->score ? 1
+ : (keya->score > keyb->score ? -1
+ : (keya->diff > keyb->diff ? 1
+ : (keya->diff < keyb->diff ? -1 : 0))));
+}
+
+
+static const char*
+force_prop_option_message(svn_cl__prop_use_t prop_use, const char *prop_name,
+ apr_pool_t *scratch_pool)
+{
+ switch (prop_use)
+ {
+ case svn_cl__prop_use_set:
+ return apr_psprintf(
+ scratch_pool,
+ _("(To set the '%s' property, re-run with '--force'.)"),
+ prop_name);
+ case svn_cl__prop_use_edit:
+ return apr_psprintf(
+ scratch_pool,
+ _("(To edit the '%s' property, re-run with '--force'.)"),
+ prop_name);
+ case svn_cl__prop_use_use:
+ default:
+ return apr_psprintf(
+ scratch_pool,
+ _("(To use the '%s' property, re-run with '--force'.)"),
+ prop_name);
+ }
+}
+
+static const char*
+wrong_prop_error_message(svn_cl__prop_use_t prop_use, const char *prop_name,
+ apr_pool_t *scratch_pool)
+{
+ switch (prop_use)
+ {
+ case svn_cl__prop_use_set:
+ return apr_psprintf(
+ scratch_pool,
+ _("'%s' is not a valid %s property name;"
+ " re-run with '--force' to set it"),
+ prop_name, SVN_PROP_PREFIX);
+ case svn_cl__prop_use_edit:
+ return apr_psprintf(
+ scratch_pool,
+ _("'%s' is not a valid %s property name;"
+ " re-run with '--force' to edit it"),
+ prop_name, SVN_PROP_PREFIX);
+ case svn_cl__prop_use_use:
+ default:
+ return apr_psprintf(
+ scratch_pool,
+ _("'%s' is not a valid %s property name;"
+ " re-run with '--force' to use it"),
+ prop_name, SVN_PROP_PREFIX);
+ }
+}
+
+svn_error_t *
+svn_cl__check_svn_prop_name(const char *propname,
+ svn_boolean_t revprop,
+ svn_cl__prop_use_t prop_use,
+ apr_pool_t *scratch_pool)
+{
+ static const char *const nodeprops[] =
+ {
+ SVN_PROP_NODE_ALL_PROPS
+ };
+ static const apr_size_t nodeprops_len = sizeof(nodeprops)/sizeof(*nodeprops);
+
+ static const char *const revprops[] =
+ {
+ SVN_PROP_REVISION_ALL_PROPS
+ };
+ static const apr_size_t revprops_len = sizeof(revprops)/sizeof(*revprops);
+
+ const char *const *const proplist = (revprop ? revprops : nodeprops);
+ const apr_size_t numprops = (revprop ? revprops_len : nodeprops_len);
+
+ struct simprop_t **propkeys;
+ struct simprop_t *propbuf;
+ apr_size_t i;
+
+ struct simprop_context_t context;
+ svn_string_t prefix;
+
+ context.name.data = propname;
+ context.name.len = strlen(propname);
+ prefix.data = SVN_PROP_PREFIX;
+ prefix.len = strlen(SVN_PROP_PREFIX);
+
+ svn_membuf__create(&context.buffer, 0, scratch_pool);
+
+ /* First, check if the name is even close to being in the svn: namespace.
+ It must contain a colon in the right place, and we only allow
+ one-char typos or a single transposition. */
+ if (context.name.len < prefix.len
+ || context.name.data[prefix.len - 1] != prefix.data[prefix.len - 1])
+ return SVN_NO_ERROR; /* Wrong prefix, ignore */
+ else
+ {
+ apr_size_t lcs;
+ const apr_size_t name_len = context.name.len;
+ context.name.len = prefix.len; /* Only check up to the prefix length */
+ svn_string__similarity(&context.name, &prefix, &context.buffer, &lcs);
+ context.name.len = name_len; /* Restore the original propname length */
+ if (lcs < prefix.len - 1)
+ return SVN_NO_ERROR; /* Wrong prefix, ignore */
+
+ /* If the prefix is slightly different, the rest must be
+ identical in order to trigger the error. */
+ if (lcs == prefix.len - 1)
+ {
+ for (i = 0; i < numprops; ++i)
+ {
+ if (0 == strcmp(proplist[i] + prefix.len, propname + prefix.len))
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name;"
+ " did you mean '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX, proplist[i],
+ force_prop_option_message(prop_use, propname, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Now find the closest match from amongst the set of reserved
+ node or revision property names. Skip the prefix while matching,
+ we already know that it's the same and looking at it would only
+ skew the results. */
+ propkeys = apr_palloc(scratch_pool,
+ numprops * sizeof(struct simprop_t*));
+ propbuf = apr_palloc(scratch_pool,
+ numprops * sizeof(struct simprop_t));
+ context.name.data += prefix.len;
+ context.name.len -= prefix.len;
+ for (i = 0; i < numprops; ++i)
+ {
+ propkeys[i] = &propbuf[i];
+ propbuf[i].propname = proplist[i];
+ propbuf[i].name.data = proplist[i] + prefix.len;
+ propbuf[i].name.len = strlen(propbuf[i].name.data);
+ propbuf[i].score = (unsigned int)-1;
+ propbuf[i].context = &context;
+ }
+
+ qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
+
+ if (0 == propkeys[0]->diff)
+ return SVN_NO_ERROR; /* We found an exact match. */
+
+ /* See if we can suggest a sane alternative spelling */
+ for (i = 0; i < numprops; ++i)
+ if (propkeys[i]->score < 666) /* 2/3 similarity required */
+ break;
+
+ switch (i)
+ {
+ case 0:
+ /* The best alternative isn't good enough */
+ return svn_error_create(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ wrong_prop_error_message(prop_use, propname, scratch_pool));
+
+ case 1:
+ /* There is only one good candidate */
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name; did you mean '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX, propkeys[0]->propname,
+ force_prop_option_message(prop_use, propname, scratch_pool));
+
+ case 2:
+ /* Suggest a list of the most likely candidates */
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name\n"
+ "Did you mean '%s' or '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX,
+ propkeys[0]->propname, propkeys[1]->propname,
+ force_prop_option_message(prop_use, propname, scratch_pool));
+
+ default:
+ /* Never suggest more than three candidates */
+ return svn_error_createf(
+ SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid %s property name\n"
+ "Did you mean '%s', '%s' or '%s'?\n%s"),
+ propname, SVN_PROP_PREFIX,
+ propkeys[0]->propname, propkeys[1]->propname, propkeys[2]->propname,
+ force_prop_option_message(prop_use, propname, scratch_pool));
+ }
+}
diff --git a/subversion/svn/propset-cmd.c b/subversion/svn/propset-cmd.c
new file mode 100644
index 000000000000..07b9bbdb57a4
--- /dev/null
+++ b/subversion/svn/propset-cmd.c
@@ -0,0 +1,191 @@
+/*
+ * propset-cmd.c -- Set property values on files/dirs
+ *
+ * ====================================================================
+ * 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 "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__propset(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ const char *pname, *pname_utf8;
+ svn_string_t *propval = NULL;
+ svn_boolean_t propval_came_from_cmdline;
+ apr_array_header_t *args, *targets;
+
+ /* PNAME and PROPVAL expected as first 2 arguments if filedata was
+ NULL, else PNAME alone will precede the targets. Get a UTF-8
+ version of the name, too. */
+ SVN_ERR(svn_opt_parse_num_args(&args, os,
+ opt_state->filedata ? 1 : 2, scratch_pool));
+ pname = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, scratch_pool));
+ if (! svn_prop_name_is_valid(pname_utf8))
+ return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
+ _("'%s' is not a valid Subversion property name"),
+ pname_utf8);
+ if (!opt_state->force)
+ SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop,
+ svn_cl__prop_use_set, scratch_pool));
+
+ /* Get the PROPVAL from either an external file, or from the command
+ line. */
+ if (opt_state->filedata)
+ {
+ propval = svn_string_create_from_buf(opt_state->filedata, scratch_pool);
+ propval_came_from_cmdline = FALSE;
+ }
+ else
+ {
+ propval = svn_string_create(APR_ARRAY_IDX(args, 1, const char *),
+ scratch_pool);
+ propval_came_from_cmdline = TRUE;
+ }
+
+ /* We only want special Subversion property values to be in UTF-8
+ and LF line endings. All other propvals are taken literally. */
+ if (svn_prop_needs_translation(pname_utf8))
+ SVN_ERR(svn_subst_translate_string2(&propval, NULL, NULL, propval,
+ opt_state->encoding, FALSE,
+ scratch_pool, scratch_pool));
+ else if (opt_state->encoding)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("--encoding option applies only to textual"
+ " Subversion-controlled properties"));
+
+ /* Suck up all the remaining arguments into a targets array */
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Implicit "." is okay for revision properties; it just helps
+ us find the right repository. */
+ if (opt_state->revprop)
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ if (opt_state->revprop) /* operate on a revprop */
+ {
+ svn_revnum_t rev;
+ const char *URL;
+
+ SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
+ &URL, ctx, scratch_pool));
+
+ /* Let libsvn_client do the real work. */
+ SVN_ERR(svn_client_revprop_set2(pname_utf8, propval, NULL,
+ URL, &(opt_state->start_revision),
+ &rev, opt_state->force, ctx,
+ scratch_pool));
+ }
+ else if (opt_state->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Cannot specify revision for setting versioned property '%s'"),
+ pname);
+ }
+ else /* operate on a normal, versioned property (not a revprop) */
+ {
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ /* The customary implicit dot rule has been prone to user error
+ * here. People would do intuitive things like
+ *
+ * $ svn propset svn:executable script
+ *
+ * and then be surprised to get an error like:
+ *
+ * svn: Illegal target for the requested operation
+ * svn: Cannot set svn:executable on a directory ()
+ *
+ * So we don't do the implicit dot thing anymore. A * target
+ * must always be explicitly provided when setting a versioned
+ * property. See
+ *
+ * http://subversion.tigris.org/issues/show_bug.cgi?id=924
+ *
+ * for more details.
+ */
+
+ if (targets->nelts == 0)
+ {
+ if (propval_came_from_cmdline)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Explicit target required ('%s' interpreted as prop value)"),
+ propval->data);
+ }
+ else
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Explicit target argument required"));
+ }
+ }
+
+ SVN_ERR(svn_cl__propset_print_binary_mime_type_warning(targets,
+ pname_utf8,
+ propval,
+ scratch_pool));
+
+ SVN_ERR(svn_client_propset_local(pname_utf8, propval, targets,
+ opt_state->depth, opt_state->force,
+ opt_state->changelists, ctx,
+ scratch_pool));
+
+ if (! opt_state->quiet)
+ svn_cl__check_boolean_prop_val(pname_utf8, propval->data, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/relocate-cmd.c b/subversion/svn/relocate-cmd.c
new file mode 100644
index 000000000000..fe50f6601476
--- /dev/null
+++ b/subversion/svn/relocate-cmd.c
@@ -0,0 +1,120 @@
+/*
+ * relocate-cmd.c -- Update working tree administrative data to
+ * account for repository change-of-address.
+ *
+ * ====================================================================
+ * 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 "svn_wc.h"
+#include "svn_client.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__relocate(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ svn_boolean_t ignore_externals = opt_state->ignore_externals;
+ apr_array_header_t *targets;
+ const char *from, *to, *path;
+
+ /* We've got two different syntaxes to support:
+
+ 1. relocate FROM-PREFIX TO-PREFIX [PATH ...]
+ 2. relocate TO-URL [PATH]
+ */
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+ if (targets->nelts < 1)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ /* If we have a single target, we're in form #2. If we have two
+ targets and the first is a URL and the second is not, we're also
+ in form #2. */
+ if ((targets->nelts == 1) ||
+ ((targets->nelts == 2)
+ && (svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)))
+ && (! svn_path_is_url(APR_ARRAY_IDX(targets, 1, const char *)))))
+
+ {
+ to = APR_ARRAY_IDX(targets, 0, const char *);
+ path = (targets->nelts == 2) ? APR_ARRAY_IDX(targets, 1, const char *)
+ : "";
+
+ SVN_ERR(svn_client_url_from_path2(&from, path, ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_client_relocate2(path, from, to, ignore_externals,
+ ctx, scratch_pool));
+ }
+ /* ... Everything else is form #1. */
+ else
+ {
+ from = APR_ARRAY_IDX(targets, 0, const char *);
+ to = APR_ARRAY_IDX(targets, 1, const char *);
+
+ if (targets->nelts == 2)
+ {
+ SVN_ERR(svn_client_relocate2("", from, to, ignore_externals,
+ ctx, scratch_pool));
+ }
+ else
+ {
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ int i;
+
+ /* Target working copy root dir must be local. */
+ for (i = 2; i < targets->nelts; i++)
+ {
+ path = APR_ARRAY_IDX(targets, i, const char *);
+ SVN_ERR(svn_cl__check_target_is_local_path(path));
+ }
+
+ for (i = 2; i < targets->nelts; i++)
+ {
+ svn_pool_clear(subpool);
+ path = APR_ARRAY_IDX(targets, i, const char *);
+ SVN_ERR(svn_client_relocate2(path, from, to, ignore_externals,
+ ctx, subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/resolve-cmd.c b/subversion/svn/resolve-cmd.c
new file mode 100644
index 000000000000..ce4818eade2b
--- /dev/null
+++ b/subversion/svn/resolve-cmd.c
@@ -0,0 +1,131 @@
+/*
+ * resolve-cmd.c -- Subversion resolve subcommand
+ *
+ * ====================================================================
+ * 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 "svn_path.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__resolve(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ svn_wc_conflict_choice_t conflict_choice;
+ svn_error_t *err;
+ apr_array_header_t *targets;
+ int i;
+ apr_pool_t *iterpool;
+ svn_boolean_t had_error = FALSE;
+
+ switch (opt_state->accept_which)
+ {
+ case svn_cl__accept_working:
+ conflict_choice = svn_wc_conflict_choose_merged;
+ break;
+ case svn_cl__accept_base:
+ conflict_choice = svn_wc_conflict_choose_base;
+ break;
+ case svn_cl__accept_theirs_conflict:
+ conflict_choice = svn_wc_conflict_choose_theirs_conflict;
+ break;
+ case svn_cl__accept_mine_conflict:
+ conflict_choice = svn_wc_conflict_choose_mine_conflict;
+ break;
+ case svn_cl__accept_theirs_full:
+ conflict_choice = svn_wc_conflict_choose_theirs_full;
+ break;
+ case svn_cl__accept_mine_full:
+ conflict_choice = svn_wc_conflict_choose_mine_full;
+ break;
+ case svn_cl__accept_unspecified:
+ if (opt_state->non_interactive)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("missing --accept option"));
+ conflict_choice = svn_wc_conflict_choose_unspecified;
+ break;
+ default:
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("invalid 'accept' ARG"));
+ }
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+ if (! targets->nelts)
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ if (opt_state->depth == svn_depth_unknown)
+ {
+ if (opt_state->accept_which == svn_cl__accept_unspecified)
+ opt_state->depth = svn_depth_infinity;
+ else
+ opt_state->depth = svn_depth_empty;
+ }
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ 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_cl__check_cancel(ctx->cancel_baton));
+ err = svn_client_resolve(target,
+ opt_state->depth, conflict_choice,
+ ctx,
+ iterpool);
+ if (err)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ had_error = TRUE;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ if (had_error)
+ return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL,
+ _("Failure occurred resolving one or more "
+ "conflicts"));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/resolved-cmd.c b/subversion/svn/resolved-cmd.c
new file mode 100644
index 000000000000..51e2da17bcf9
--- /dev/null
+++ b/subversion/svn/resolved-cmd.c
@@ -0,0 +1,88 @@
+/*
+ * resolved-cmd.c -- Subversion resolved subcommand
+ *
+ * ====================================================================
+ * 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 "svn_path.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__resolved(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ svn_error_t *err;
+ apr_array_header_t *targets;
+ apr_pool_t *iterpool;
+ int i;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ 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_cl__check_cancel(ctx->cancel_baton));
+ err = svn_client_resolve(target,
+ opt_state->depth,
+ svn_wc_conflict_choose_merged,
+ ctx,
+ iterpool);
+ if (err)
+ {
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/revert-cmd.c b/subversion/svn/revert-cmd.c
new file mode 100644
index 000000000000..d17aeb6f7e18
--- /dev/null
+++ b/subversion/svn/revert-cmd.c
@@ -0,0 +1,81 @@
+/*
+ * revert-cmd.c -- Subversion revert command
+ *
+ * ====================================================================
+ * 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 "svn_path.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__revert(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets = NULL;
+ svn_error_t *err;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Revert has no implicit dot-target `.', so don't you put that code here! */
+ if (! targets->nelts)
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+
+ /* Revert is especially conservative, by default it is as
+ nonrecursive as possible. */
+ if (opt_state->depth == svn_depth_unknown)
+ opt_state->depth = svn_depth_empty;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ err = svn_client_revert2(targets, opt_state->depth,
+ opt_state->changelists, ctx, scratch_pool);
+ if (err
+ && (err->apr_err == SVN_ERR_WC_INVALID_OPERATION_DEPTH)
+ && (! SVN_DEPTH_IS_RECURSIVE(opt_state->depth)))
+ {
+ err = svn_error_quick_wrap
+ (err, _("Try 'svn revert --depth infinity' instead?"));
+ }
+
+ return svn_error_trace(err);
+}
diff --git a/subversion/svn/schema/blame.rnc b/subversion/svn/schema/blame.rnc
new file mode 100644
index 000000000000..b6a1e41c2c2d
--- /dev/null
+++ b/subversion/svn/schema/blame.rnc
@@ -0,0 +1,42 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn blame"
+
+
+include "common.rnc"
+
+start = blame
+
+blame = element blame { target* }
+
+## Information for one blamed file.
+target = element target { attlist.target, entry* }
+attlist.target &= attribute path { target.type }
+
+## Information for one line of a blamed file.
+## NOTE: The order of entries in a target element is insignificant.
+entry = element entry { attlist.entry, commit?, merged? }
+attlist.entry &=
+ ## Line number.
+ attribute line-number { xsd:integer { minInclusive = "1" } }
+
+## The merged commit
+merged = element merged { attlist.merged, commit }
+attlist.merged &= attribute path { string }
diff --git a/subversion/svn/schema/common.rnc b/subversion/svn/schema/common.rnc
new file mode 100644
index 000000000000..95729e39c948
--- /dev/null
+++ b/subversion/svn/schema/common.rnc
@@ -0,0 +1,77 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# Common declarations
+
+# Data types.
+
+## A revision number.
+revnum.type = xsd:nonNegativeInteger
+
+## A user name.
+username.type = string
+
+## A path or URL.
+target.type = string | xsd:anyURI
+
+## An UUID.
+uuid.type = string
+
+## An MD5 checksum.
+md5sum.type = xsd:hexBinary { length = "16" }
+
+# Common elements
+
+## Commit info.
+commit = element commit { attlist.commit, author?, date? }
+attlist.commit &= attribute revision { revnum.type }
+
+author = element author { username.type }
+
+date = element date { xsd:dateTime }
+
+## Lock info stored in repository or working copy.
+lock =
+ element lock {
+ \token, owner, comment?, created, expires?
+ }
+
+## Lock token.
+\token = element token { xsd:anyURI }
+
+## Lock owner.
+owner = element owner { username.type }
+
+## Lock comment.
+comment = element comment { text }
+
+## Creation date.
+created = element created { xsd:dateTime }
+
+## Expiration date.
+expires = element expires { xsd:dateTime }
+
+## Node and revision properties.
+property = element property { attlist.property, text }
+attlist.property &=
+ ## The property name
+ attribute name { string },
+ ## The encoding of the element content. If not present, the value
+ ## is the raw content of the element.
+ attribute encoding { "base64" }?
diff --git a/subversion/svn/schema/diff.rnc b/subversion/svn/schema/diff.rnc
new file mode 100644
index 000000000000..ab89b81531dd
--- /dev/null
+++ b/subversion/svn/schema/diff.rnc
@@ -0,0 +1,39 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn diff --summarize --xml"
+
+include "common.rnc"
+
+start = diff
+
+diff = element diff { paths }
+
+paths = element paths { path* }
+
+## A path entry
+path = element path { attlist.path, text }
+attlist.path &=
+ ## The props of the entry.
+ attribute props { "none" | "modified" },
+ ## The kind of the entry.
+ attribute kind { "dir" | "file" },
+ ## The action performed against this path. This terminology
+ ## was chosen for consistency with 'svn status'.
+ attribute item { "none" | "added" | "modified" | "deleted" }
diff --git a/subversion/svn/schema/info.rnc b/subversion/svn/schema/info.rnc
new file mode 100644
index 000000000000..3dc43f6d0bf9
--- /dev/null
+++ b/subversion/svn/schema/info.rnc
@@ -0,0 +1,134 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn info"
+
+include "common.rnc"
+
+start = info
+
+info = element info { entry* }
+
+entry =
+ element entry {
+ attlist.entry, url?, relative-url?, repository?, wc-info?,
+ commit?, conflict?, lock?, tree-conflict?
+ }
+attlist.entry &=
+ ## Local path.
+ attribute path { string },
+ ## Path type.
+ attribute kind { "file" | "dir" },
+ ## Revision number of path/URL.
+ attribute revision { revnum.type }
+
+## URL of this item in the repository.
+url = element url { xsd:anyURI }
+
+## Repository relative URL (^/...) of this item in the repository.
+relative-url = element relative-url { string }
+
+## Information of this item's repository.
+repository = element repository { root?, uuid? }
+
+## URL of the repository.
+root = element root { xsd:anyURI }
+
+## UUID of the repository.
+uuid = element uuid { uuid.type }
+
+## Info in the working copy entry.
+wc-info =
+ element wc-info {
+ wcroot-abspath?,
+ schedule?,
+ changelist?,
+ copy-from-url?,
+ copy-from-rev?,
+ depth?,
+ text-updated?,
+ prop-updated?,
+ checksum?,
+ moved-from?,
+ moved-to?
+ }
+
+wcroot-abspath = element wcroot-abspath { string }
+
+schedule =
+ element schedule { "normal" | "add" | "delete" | "replace" | "none" }
+
+## The name of the changelist that the path may be a member of.
+changelist = element changelist { string }
+
+copy-from-url = element copy-from-url { xsd:anyURI }
+
+copy-from-rev = element copy-from-rev { revnum.type }
+
+# Date when text was last updated.
+text-updated = element text-updated { xsd:dateTime }
+
+# Date when properties were last updated.
+prop-updated = element prop-updated { xsd:dateTime }
+
+checksum = element checksum { md5sum.type }
+
+moved-from = element moved-from { string }
+
+moved-to = element moved-to { string }
+
+conflict =
+ element conflict {
+ prev-base-file,
+ prev-wc-file?,
+ cur-base-file,
+ prop-file?
+ }
+
+## Previous base file.
+prev-base-file = element prev-base-file { string }
+
+## Previous WC file.
+prev-wc-file = element prev-wc-file { string }
+
+## Current base file.
+cur-base-file = element cur-base-file { string }
+
+## Current properties file.
+prop-file = element prop-file { string }
+
+## Depth of this directory, always "infinity" for non-directories
+depth = element depth { "infinity" | "immediates" | "files" | "empty" }
+
+tree-conflict =
+ element tree-conflict { attlist.tree-conflict }
+
+attlist.tree-conflict &=
+ ## Local path to the original victim.
+ attribute victim { string },
+ ## Path type.
+ attribute kind { "file" | "dir" },
+ ## Operation causing the tree conflict.
+ attribute operation { "update" | "merge" | "switch" },
+ ## Operation's action on the victim.
+ attribute action { "edit" | "add" | "delete" | "replace" },
+ ## Local reason for the conflict.
+ attribute reason { "edit" | "obstruction" | "delete" | "add" |
+ "missing" | "unversioned" | "replace" |
+ "moved-away" | "moved-here" }
diff --git a/subversion/svn/schema/list.rnc b/subversion/svn/schema/list.rnc
new file mode 100644
index 000000000000..13d5897c1ea8
--- /dev/null
+++ b/subversion/svn/schema/list.rnc
@@ -0,0 +1,45 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn list"
+
+include "common.rnc"
+
+start = lists
+
+lists = element lists { \list+ }
+
+## A target to the list command.
+\list = element list { attlist.list, entry* }
+attlist.list &=
+ ## Local path or repository URL.
+ attribute path { target.type }
+
+## A directory entry.
+entry = element entry { attlist.entry, name, size?, commit, lock? }
+attlist.entry &=
+ ## The kind of the entry.
+ attribute kind { "dir" | "file" }
+
+## Name of the file or directory.
+name = element name { string }
+
+## File size in bytes.
+size = element size { xsd:nonNegativeInteger }
+
diff --git a/subversion/svn/schema/log.rnc b/subversion/svn/schema/log.rnc
new file mode 100644
index 000000000000..14a8b7e572bc
--- /dev/null
+++ b/subversion/svn/schema/log.rnc
@@ -0,0 +1,55 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn log"
+
+include "common.rnc"
+
+start = log
+
+log = element log { logentry* }
+
+logentry =
+ element logentry { attlist.logentry, author?, date?, paths?, msg?, revprops?, logentry* }
+attlist.logentry &=
+ attribute revision { revnum.type }
+
+## Changed paths information.
+paths = element paths { path+ }
+
+## Path within repository.
+path = element path { attlist.path, text }
+attlist.path &=
+ ## "action code": A)dd, D)elete, R)eplace or M)odify
+ attribute action { "A" | "D" | "R" | "M" },
+ ## kind is "" when repository was < 1.6 when committing
+ attribute kind { "file" | "dir" | "" },
+ attribute text-mods { "true" | "false" }?,
+ attribute prop-mods { "true" | "false" }?,
+ (
+ ## The copyfrom path within repository.
+ attribute copyfrom-path { text },
+ ## Copyfrom revision number.
+ attribute copyfrom-rev { revnum.type })?
+
+## Log message.
+msg = element msg { text }
+
+## Revision properties.
+revprops = element revprops { property+ }
diff --git a/subversion/svn/schema/props.rnc b/subversion/svn/schema/props.rnc
new file mode 100644
index 000000000000..260c93ef4a6c
--- /dev/null
+++ b/subversion/svn/schema/props.rnc
@@ -0,0 +1,36 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn proplist"
+
+include "common.rnc"
+
+start = properties
+
+properties = element properties { target* | revprops }
+
+target = element target { attlist.target, property* }
+attlist.target &=
+ ## The target path.
+ attribute path { string }
+
+revprops = element revprops { attlist.revprops, property*}
+attlist.revprops &=
+ ## The revision
+ attribute rev { revnum.type }
diff --git a/subversion/svn/schema/status.rnc b/subversion/svn/schema/status.rnc
new file mode 100644
index 000000000000..73d0ca0f7e4b
--- /dev/null
+++ b/subversion/svn/schema/status.rnc
@@ -0,0 +1,92 @@
+# 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.
+#
+#
+# XML RELAX NG schema for Subversion command-line client output
+# For "svn status"
+
+# The DTD compatibility annotations namespace, used for adding default
+# attribute values.
+namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
+
+include "common.rnc"
+
+start = status
+
+status = element status { (target | changelist)* }
+
+target = element target { attlist.target, entry*, against? }
+attlist.target &=
+ ## The target path.
+ attribute path { string }
+
+changelist = element changelist { attlist.changelist, entry*, against? }
+attlist.changelist &=
+ ## The changelist name.
+ attribute name { string }
+
+## Status information for a path under the target.
+entry = element entry { attlist.entry, wc-status, repos-status? }
+attlist.entry &=
+ ## Path inside the target.
+ attribute path { text }
+
+## Status of the entry in the working copy.
+wc-status = element wc-status { attlist.wc-status, commit?, lock? }
+
+attlist.wc-status &=
+ ## Item/text status.
+ attribute item {
+ "added" | "conflicted" | "deleted" | "external" | "ignored" |
+ "incomplete" | "merged" | "missing" | "modified" | "none" |
+ "normal" | "obstructed" | "replaced" | "unversioned"
+ },
+ ## Properties status.
+ attribute props { "conflicted" | "modified" | "normal" | "none" },
+ ## Base revision number.
+ attribute revision { revnum.type }?,
+ ## WC directory locked.
+ [ a:defaultValue = "false" ]
+ attribute wc-locked { "true" | "false" }?,
+ ## Add with history.
+ [ a:defaultValue = "false" ]
+ attribute copied { "true" | "false" }?,
+ # Item switched relative to its parent.
+ [ a:defaultValue = "false" ]
+ attribute switched { "true" | "false" }?,
+ ## Tree-conflict status of the item.
+ [ a:defaultValue = "false" ]
+ attribute tree-conflicted { "true" | "false" }?,
+ ## If root of a move-here, the local path to the move source.
+ attribute moved-from { text }?,
+ ## If root of a move-away, the local path to the move destination.
+ attribute moved-to { text }?
+
+## Status in repository (if --update was specified).
+repos-status = element repos-status { attlist.repos-status, lock? }
+attlist.repos-status &=
+ ## Text/item status in the repository.
+ attribute item {
+ "added" | "deleted" | "modified" | "replaced" | "none"
+ },
+ ## Properties status in repository.
+ attribute props { "modified" | "none" }
+
+against = element against { attlist.against, empty }
+attlist.against &=
+ ## Revision number at which the repository information was obtained.
+ attribute revision { revnum.type }
diff --git a/subversion/svn/status-cmd.c b/subversion/svn/status-cmd.c
new file mode 100644
index 000000000000..0a73daccc710
--- /dev/null
+++ b/subversion/svn/status-cmd.c
@@ -0,0 +1,416 @@
+/*
+ * status-cmd.c -- Display status information in current directory
+ *
+ * ====================================================================
+ * 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 "svn_hash.h"
+#include "svn_string.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_error_codes.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "svn_xml.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_cmdline.h"
+#include "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+
+
+/*** Code. ***/
+
+struct status_baton
+{
+ /* These fields all correspond to the ones in the
+ svn_cl__print_status() interface. */
+ const char *cwd_abspath;
+ svn_boolean_t suppress_externals_placeholders;
+ svn_boolean_t detailed;
+ svn_boolean_t show_last_committed;
+ svn_boolean_t skip_unrecognized;
+ svn_boolean_t repos_locks;
+
+ apr_hash_t *cached_changelists;
+ apr_pool_t *cl_pool; /* where cached changelists are allocated */
+
+ svn_boolean_t had_print_error; /* To avoid printing lots of errors if we get
+ errors while printing to stdout */
+ svn_boolean_t xml_mode;
+
+ /* Conflict stats. */
+ unsigned int text_conflicts;
+ unsigned int prop_conflicts;
+ unsigned int tree_conflicts;
+
+ svn_client_ctx_t *ctx;
+};
+
+
+struct status_cache
+{
+ const char *path;
+ svn_client_status_t *status;
+};
+
+/* Print conflict stats accumulated in status baton SB.
+ * Do temporary allocations in POOL. */
+static svn_error_t *
+print_conflict_stats(struct status_baton *sb, apr_pool_t *pool)
+{
+ if (sb->text_conflicts > 0 || sb->prop_conflicts > 0 ||
+ sb->tree_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n")));
+
+ if (sb->text_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf
+ (pool, _(" Text conflicts: %u\n"), sb->text_conflicts));
+
+ if (sb->prop_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf
+ (pool, _(" Property conflicts: %u\n"), sb->prop_conflicts));
+
+ if (sb->tree_conflicts > 0)
+ SVN_ERR(svn_cmdline_printf
+ (pool, _(" Tree conflicts: %u\n"), sb->tree_conflicts));
+
+ return SVN_NO_ERROR;
+}
+
+/* Prints XML target element with path attribute TARGET, using POOL for
+ temporary allocations. */
+static svn_error_t *
+print_start_target_xml(const char *target, apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", target, NULL);
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* Finish a target element by optionally printing an against element if
+ * REPOS_REV is a valid revision number, and then printing an target end tag.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+print_finish_target_xml(svn_revnum_t repos_rev,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+
+ if (SVN_IS_VALID_REVNUM(repos_rev))
+ {
+ const char *repos_rev_str;
+ repos_rev_str = apr_psprintf(pool, "%ld", repos_rev);
+ svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "against",
+ "revision", repos_rev_str, NULL);
+ }
+
+ svn_xml_make_close_tag(&sb, pool, "target");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+
+/* Function which *actually* causes a status structure to be output to
+ the user. Called by both print_status() and svn_cl__status(). */
+static svn_error_t *
+print_status_normal_or_xml(void *baton,
+ const char *path,
+ const svn_client_status_t *status,
+ apr_pool_t *pool)
+{
+ struct status_baton *sb = baton;
+
+ if (sb->xml_mode)
+ return svn_cl__print_status_xml(sb->cwd_abspath, path, status,
+ sb->ctx, pool);
+ else
+ return svn_cl__print_status(sb->cwd_abspath, path, status,
+ sb->suppress_externals_placeholders,
+ sb->detailed,
+ sb->show_last_committed,
+ sb->skip_unrecognized,
+ sb->repos_locks,
+ &sb->text_conflicts,
+ &sb->prop_conflicts,
+ &sb->tree_conflicts,
+ sb->ctx,
+ pool);
+}
+
+
+/* A status callback function for printing STATUS for PATH. */
+static svn_error_t *
+print_status(void *baton,
+ const char *path,
+ const svn_client_status_t *status,
+ apr_pool_t *pool)
+{
+ struct status_baton *sb = baton;
+ const char *local_abspath = status->local_abspath;
+
+ /* ### The revision information with associates are based on what
+ * ### _read_info() returns. The svn_wc_status_func4_t callback is
+ * ### suppposed to handle the gathering of additional information from the
+ * ### WORKING nodes on its own. Until we've agreed on how the CLI should
+ * ### handle the revision information, we use this appproach to stay compat
+ * ### with our testsuite. */
+ if (status->versioned
+ && !SVN_IS_VALID_REVNUM(status->revision)
+ && !status->copied
+ && (status->node_status == svn_wc_status_deleted
+ || status->node_status == svn_wc_status_replaced))
+ {
+ svn_client_status_t *twks = svn_client_status_dup(status, sb->cl_pool);
+
+ /* Copied is FALSE, so either we have a local addition, or we have
+ a delete that directly shadows a BASE node */
+
+ switch(status->node_status)
+ {
+ case svn_wc_status_replaced:
+ /* Just retrieve the revision below the replacement.
+ The other fields are filled by a copy.
+ (With ! copied, we know we have a BASE node)
+
+ ### Is this really what we want to provide? */
+ SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
+ NULL, NULL, NULL,
+ sb->ctx->wc_ctx,
+ local_abspath,
+ sb->cl_pool, pool));
+ break;
+ case svn_wc_status_deleted:
+ /* Retrieve some data from the original version below the delete */
+ SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision,
+ &twks->changed_rev,
+ &twks->changed_date,
+ &twks->changed_author,
+ sb->ctx->wc_ctx,
+ local_abspath,
+ sb->cl_pool, pool));
+ break;
+
+ default:
+ /* This space intentionally left blank. */
+ break;
+ }
+
+ status = twks;
+ }
+
+ /* If the path is part of a changelist, then we don't print
+ the item, but instead dup & cache the status structure for later. */
+ if (status->changelist)
+ {
+ /* The hash maps a changelist name to an array of status_cache
+ structures. */
+ apr_array_header_t *path_array;
+ const char *cl_key = apr_pstrdup(sb->cl_pool, status->changelist);
+ struct status_cache *scache = apr_pcalloc(sb->cl_pool, sizeof(*scache));
+ scache->path = apr_pstrdup(sb->cl_pool, path);
+ scache->status = svn_client_status_dup(status, sb->cl_pool);
+
+ path_array =
+ svn_hash_gets(sb->cached_changelists, cl_key);
+ if (path_array == NULL)
+ {
+ path_array = apr_array_make(sb->cl_pool, 1,
+ sizeof(struct status_cache *));
+ svn_hash_sets(sb->cached_changelists, cl_key, path_array);
+ }
+
+ APR_ARRAY_PUSH(path_array, struct status_cache *) = scache;
+ return SVN_NO_ERROR;
+ }
+
+ return print_status_normal_or_xml(baton, path, status, pool);
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+svn_error_t *
+svn_cl__status(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
+ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
+ apr_array_header_t *targets;
+ apr_pool_t *iterpool;
+ apr_hash_t *master_cl_hash = apr_hash_make(scratch_pool);
+ int i;
+ svn_opt_revision_t rev;
+ struct status_baton sb;
+
+ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
+ opt_state->targets,
+ ctx, FALSE,
+ scratch_pool));
+
+ /* Add "." if user passed 0 arguments */
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
+
+ SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
+
+ /* We want our -u statuses to be against HEAD. */
+ rev.kind = svn_opt_revision_head;
+
+ sb.had_print_error = FALSE;
+
+ if (opt_state->xml)
+ {
+ /* If output is not incremental, output the XML header and wrap
+ everything in a top-level element. This makes the output in
+ its entirety a well-formed XML document. */
+ if (! opt_state->incremental)
+ SVN_ERR(svn_cl__xml_print_header("status", scratch_pool));
+ }
+ else
+ {
+ if (opt_state->incremental)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'incremental' option only valid in XML "
+ "mode"));
+ }
+
+ SVN_ERR(svn_dirent_get_absolute(&(sb.cwd_abspath), "", scratch_pool));
+ sb.suppress_externals_placeholders = (opt_state->quiet
+ && (! opt_state->verbose));
+ sb.detailed = (opt_state->verbose || opt_state->update);
+ sb.show_last_committed = opt_state->verbose;
+ sb.skip_unrecognized = opt_state->quiet;
+ sb.repos_locks = opt_state->update;
+ sb.xml_mode = opt_state->xml;
+ sb.cached_changelists = master_cl_hash;
+ sb.cl_pool = scratch_pool;
+ sb.text_conflicts = 0;
+ sb.prop_conflicts = 0;
+ sb.tree_conflicts = 0;
+ sb.ctx = ctx;
+
+ SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ svn_revnum_t repos_rev = SVN_INVALID_REVNUM;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
+
+ if (opt_state->xml)
+ SVN_ERR(print_start_target_xml(svn_dirent_local_style(target, iterpool),
+ iterpool));
+
+ /* Retrieve a hash of status structures with the information
+ requested by the user. */
+ SVN_ERR(svn_cl__try(svn_client_status5(&repos_rev, ctx, target, &rev,
+ opt_state->depth,
+ opt_state->verbose,
+ opt_state->update,
+ opt_state->no_ignore,
+ opt_state->ignore_externals,
+ FALSE /* depth_as_sticky */,
+ opt_state->changelists,
+ print_status, &sb,
+ iterpool),
+ NULL, opt_state->quiet,
+ /* not versioned: */
+ SVN_ERR_WC_NOT_WORKING_COPY,
+ SVN_ERR_WC_PATH_NOT_FOUND));
+
+ if (opt_state->xml)
+ SVN_ERR(print_finish_target_xml(repos_rev, iterpool));
+ }
+
+ /* If any paths were cached because they were associatied with
+ changelists, we can now display them as grouped changelists. */
+ if (apr_hash_count(master_cl_hash) > 0)
+ {
+ apr_hash_index_t *hi;
+ svn_stringbuf_t *buf;
+
+ if (opt_state->xml)
+ buf = svn_stringbuf_create_empty(scratch_pool);
+
+ for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *changelist_name = svn__apr_hash_index_key(hi);
+ apr_array_header_t *path_array = svn__apr_hash_index_val(hi);
+ int j;
+
+ /* ### TODO: For non-XML output, we shouldn't print the
+ ### leading \n on the first changelist if there were no
+ ### non-changelist entries. */
+ if (opt_state->xml)
+ {
+ svn_stringbuf_setempty(buf);
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "changelist", "name", changelist_name,
+ NULL);
+ SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
+ }
+ else
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("\n--- Changelist '%s':\n"),
+ changelist_name));
+
+ for (j = 0; j < path_array->nelts; j++)
+ {
+ struct status_cache *scache =
+ APR_ARRAY_IDX(path_array, j, struct status_cache *);
+ SVN_ERR(print_status_normal_or_xml(&sb, scache->path,
+ scache->status, scratch_pool));
+ }
+
+ if (opt_state->xml)
+ {
+ svn_stringbuf_setempty(buf);
+ svn_xml_make_close_tag(&buf, scratch_pool, "changelist");
+ SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ if (opt_state->xml && (! opt_state->incremental))
+ SVN_ERR(svn_cl__xml_print_footer("status", scratch_pool));
+
+ if (! opt_state->quiet && ! opt_state->xml)
+ SVN_ERR(print_conflict_stats(&sb, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/svn/status.c b/subversion/svn/status.c
new file mode 100644
index 000000000000..3679bfff9f8c
--- /dev/null
+++ b/subversion/svn/status.c
@@ -0,0 +1,607 @@
+/*
+ * status.c: the command-line's portion of the "svn status" command
+ *
+ * ====================================================================
+ * 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 "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_wc.h"
+#include "svn_dirent_uri.h"
+#include "svn_xml.h"
+#include "svn_time.h"
+#include "cl.h"
+#include "svn_private_config.h"
+#include "cl-conflicts.h"
+#include "private/svn_wc_private.h"
+
+/* Return the single character representation of STATUS */
+static char
+generate_status_code(enum svn_wc_status_kind status)
+{
+ switch (status)
+ {
+ case svn_wc_status_none: return ' ';
+ case svn_wc_status_normal: return ' ';
+ case svn_wc_status_added: return 'A';
+ case svn_wc_status_missing: return '!';
+ case svn_wc_status_incomplete: return '!';
+ case svn_wc_status_deleted: return 'D';
+ case svn_wc_status_replaced: return 'R';
+ case svn_wc_status_modified: return 'M';
+ case svn_wc_status_conflicted: return 'C';
+ case svn_wc_status_obstructed: return '~';
+ case svn_wc_status_ignored: return 'I';
+ case svn_wc_status_external: return 'X';
+ case svn_wc_status_unversioned: return '?';
+ default: return '?';
+ }
+}
+
+/* Return the combined STATUS as shown in 'svn status' based
+ on the node status and text status */
+static enum svn_wc_status_kind
+combined_status(const svn_client_status_t *status)
+{
+ enum svn_wc_status_kind new_status = status->node_status;
+
+ switch (status->node_status)
+ {
+ case svn_wc_status_conflicted:
+ if (!status->versioned && status->conflicted)
+ {
+ /* Report unversioned tree conflict victims as missing: '!' */
+ new_status = svn_wc_status_missing;
+ break;
+ }
+ /* fall through */
+ case svn_wc_status_modified:
+ /* This value might be the property status */
+ new_status = status->text_status;
+ break;
+ default:
+ break;
+ }
+
+ return new_status;
+}
+
+/* Return the combined repository STATUS as shown in 'svn status' based
+ on the repository node status and repository text status */
+static enum svn_wc_status_kind
+combined_repos_status(const svn_client_status_t *status)
+{
+ if (status->repos_node_status == svn_wc_status_modified)
+ return status->repos_text_status;
+
+ return status->repos_node_status;
+}
+
+/* Return the single character representation of the switched column
+ status. */
+static char
+generate_switch_column_code(const svn_client_status_t *status)
+{
+ if (status->switched)
+ return 'S';
+ else if (status->file_external)
+ return 'X';
+ else
+ return ' ';
+}
+
+/* Return the detailed string representation of STATUS */
+static const char *
+generate_status_desc(enum svn_wc_status_kind status)
+{
+ switch (status)
+ {
+ case svn_wc_status_none: return "none";
+ case svn_wc_status_normal: return "normal";
+ case svn_wc_status_added: return "added";
+ case svn_wc_status_missing: return "missing";
+ case svn_wc_status_incomplete: return "incomplete";
+ case svn_wc_status_deleted: return "deleted";
+ case svn_wc_status_replaced: return "replaced";
+ case svn_wc_status_modified: return "modified";
+ case svn_wc_status_conflicted: return "conflicted";
+ case svn_wc_status_obstructed: return "obstructed";
+ case svn_wc_status_ignored: return "ignored";
+ case svn_wc_status_external: return "external";
+ case svn_wc_status_unversioned: return "unversioned";
+ default:
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+ }
+}
+
+/* Make a relative path containing '..' elements as needed.
+ RELATIVE_TO_PATH must be the path to a directory (not a file!) and
+ TARGET_PATH must be the path to any file or directory. Both
+ RELATIVE_TO_PATH and TARGET_PATH must be based on the same parent path,
+ i.e. they can either both be absolute or they can both be relative to the
+ same parent directory. Both paths are expected to be canonical.
+
+ If above conditions are met, a relative path that leads to TARGET_ABSPATH
+ from RELATIVE_TO_PATH is returned, but there is no error checking involved.
+
+ The returned path is allocated from RESULT_POOL, all other allocations are
+ made in SCRATCH_POOL. */
+static const char *
+make_relpath(const char *relative_to_path,
+ const char *target_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *la;
+ const char *parent_dir_els = "";
+
+ /* An example:
+ * relative_to_path = /a/b/c
+ * target_path = /a/x/y/z
+ * result = ../../x/y/z
+ *
+ * Another example (Windows specific):
+ * relative_to_path = F:/wc
+ * target_path = C:/wc
+ * result = C:/wc
+ */
+
+ /* Skip the common ancestor of both paths, here '/a'. */
+ la = svn_dirent_get_longest_ancestor(relative_to_path, target_path,
+ scratch_pool);
+ if (*la == '\0')
+ {
+ /* Nothing in common: E.g. C:/ vs F:/ on Windows */
+ return apr_pstrdup(result_pool, target_path);
+ }
+ relative_to_path = svn_dirent_skip_ancestor(la, relative_to_path);
+ target_path = svn_dirent_skip_ancestor(la, target_path);
+
+ /* In above example, we'd now have:
+ * relative_to_path = b/c
+ * target_path = x/y/z */
+
+ /* Count the elements of relative_to_path and prepend as many '..' elements
+ * to target_path. */
+ while (*relative_to_path)
+ {
+ svn_dirent_split(&relative_to_path, NULL, relative_to_path,
+ scratch_pool);
+ parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool);
+ }
+
+ return svn_dirent_join(parent_dir_els, target_path, result_pool);
+}
+
+
+/* Print STATUS and PATH in a format determined by DETAILED and
+ SHOW_LAST_COMMITTED. */
+static svn_error_t *
+print_status(const char *cwd_abspath, const char *path,
+ svn_boolean_t detailed,
+ svn_boolean_t show_last_committed,
+ svn_boolean_t repos_locks,
+ const svn_client_status_t *status,
+ unsigned int *text_conflicts,
+ unsigned int *prop_conflicts,
+ unsigned int *tree_conflicts,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ enum svn_wc_status_kind node_status = status->node_status;
+ enum svn_wc_status_kind prop_status = status->prop_status;
+ char tree_status_code = ' ';
+ const char *tree_desc_line = "";
+ const char *moved_from_line = "";
+ const char *moved_to_line = "";
+
+ path = make_relpath(cwd_abspath, path, pool, pool);
+
+ /* For historic reasons svn ignores the property status for added nodes, even
+ if these nodes were copied and have local property changes.
+
+ Note that it doesn't do this on replacements, or children of copies.
+
+ ### Our test suite would catch more errors if we reported property
+ changes on copies. */
+ if (node_status == svn_wc_status_added)
+ prop_status = svn_wc_status_none;
+
+ /* To indicate this node is the victim of a tree conflict, we show
+ 'C' in the tree-conflict column, overriding any other status.
+ We also print a separate line describing the nature of the tree
+ conflict. */
+ if (status->conflicted)
+ {
+ const char *desc;
+ const char *local_abspath = status->local_abspath;
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ svn_boolean_t tree_conflicted;
+
+ if (status->versioned)
+ {
+ svn_error_t *err;
+
+ err = svn_wc_conflicted_p3(&text_conflicted,
+ &prop_conflicted,
+ &tree_conflicted, ctx->wc_ctx,
+ local_abspath, pool);
+
+ if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
+ {
+ svn_error_clear(err);
+ text_conflicted = FALSE;
+ prop_conflicted = FALSE;
+ tree_conflicted = FALSE;
+ }
+ else
+ SVN_ERR(err);
+ }
+ else
+ {
+ text_conflicted = FALSE;
+ prop_conflicted = FALSE;
+ tree_conflicted = TRUE;
+ }
+
+ if (tree_conflicted)
+ {
+ const svn_wc_conflict_description2_t *tree_conflict;
+ SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx,
+ local_abspath, pool, pool));
+ SVN_ERR_ASSERT(tree_conflict != NULL);
+
+ tree_status_code = 'C';
+ SVN_ERR(svn_cl__get_human_readable_tree_conflict_description(
+ &desc, tree_conflict, pool));
+ tree_desc_line = apr_psprintf(pool, "\n > %s", desc);
+ (*tree_conflicts)++;
+ }
+ else if (text_conflicted)
+ (*text_conflicts)++;
+ else if (prop_conflicted)
+ (*prop_conflicts)++;
+ }
+
+ /* Note that moved-from and moved-to information is only available in STATUS
+ * for (op-)roots of a move. Those are exactly the nodes we want to show
+ * move info for in 'svn status'. See also comments in svn_wc_status3_t. */
+ if (status->moved_from_abspath && status->moved_to_abspath &&
+ strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0)
+ {
+ const char *relpath;
+
+ relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ moved_from_line = apr_pstrcat(pool, "\n > ",
+ apr_psprintf(pool,
+ _("swapped places with %s"),
+ relpath),
+ (char *)NULL);
+ }
+ else if (status->moved_from_abspath || status->moved_to_abspath)
+ {
+ const char *relpath;
+
+ if (status->moved_from_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ moved_from_line = apr_pstrcat(pool, "\n > ",
+ apr_psprintf(pool, _("moved from %s"),
+ relpath),
+ (char *)NULL);
+ }
+
+ if (status->moved_to_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ moved_to_line = apr_pstrcat(pool, "\n > ",
+ apr_psprintf(pool, _("moved to %s"),
+ relpath),
+ (char *)NULL);
+ }
+ }
+
+ if (detailed)
+ {
+ char ood_status, lock_status;
+ const char *working_rev;
+
+ if (! status->versioned)
+ working_rev = "";
+ else if (status->copied
+ || ! SVN_IS_VALID_REVNUM(status->revision))
+ working_rev = "-";
+ else
+ working_rev = apr_psprintf(pool, "%ld", status->revision);
+
+ if (status->repos_node_status != svn_wc_status_none)
+ ood_status = '*';
+ else
+ ood_status = ' ';
+
+ if (repos_locks)
+ {
+ if (status->repos_lock)
+ {
+ if (status->lock)
+ {
+ if (strcmp(status->repos_lock->token, status->lock->token)
+ == 0)
+ lock_status = 'K';
+ else
+ lock_status = 'T';
+ }
+ else
+ lock_status = 'O';
+ }
+ else if (status->lock)
+ lock_status = 'B';
+ else
+ lock_status = ' ';
+ }
+ else
+ lock_status = (status->lock) ? 'K' : ' ';
+
+ if (show_last_committed)
+ {
+ const char *commit_rev;
+ const char *commit_author;
+
+ if (SVN_IS_VALID_REVNUM(status->changed_rev))
+ commit_rev = apr_psprintf(pool, "%ld", status->changed_rev);
+ else if (status->versioned)
+ commit_rev = " ? ";
+ else
+ commit_rev = "";
+
+ if (status->changed_author)
+ commit_author = status->changed_author;
+ else if (status->versioned)
+ commit_author = " ? ";
+ else
+ commit_author = "";
+
+ SVN_ERR
+ (svn_cmdline_printf(pool,
+ "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n",
+ generate_status_code(combined_status(status)),
+ generate_status_code(prop_status),
+ status->wc_is_locked ? 'L' : ' ',
+ status->copied ? '+' : ' ',
+ generate_switch_column_code(status),
+ lock_status,
+ tree_status_code,
+ ood_status,
+ working_rev,
+ commit_rev,
+ commit_author,
+ path,
+ moved_to_line,
+ moved_from_line,
+ tree_desc_line));
+ }
+ else
+ SVN_ERR(
+ svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n",
+ generate_status_code(combined_status(status)),
+ generate_status_code(prop_status),
+ status->wc_is_locked ? 'L' : ' ',
+ status->copied ? '+' : ' ',
+ generate_switch_column_code(status),
+ lock_status,
+ tree_status_code,
+ ood_status,
+ working_rev,
+ path,
+ moved_to_line,
+ moved_from_line,
+ tree_desc_line));
+ }
+ else
+ SVN_ERR(
+ svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n",
+ generate_status_code(combined_status(status)),
+ generate_status_code(prop_status),
+ status->wc_is_locked ? 'L' : ' ',
+ status->copied ? '+' : ' ',
+ generate_switch_column_code(status),
+ ((status->lock)
+ ? 'K' : ' '),
+ tree_status_code,
+ path,
+ moved_to_line,
+ moved_from_line,
+ tree_desc_line));
+
+ return svn_cmdline_fflush(stdout);
+}
+
+
+svn_error_t *
+svn_cl__print_status_xml(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
+ apr_hash_t *att_hash;
+ const char *local_abspath = status->local_abspath;
+ svn_boolean_t tree_conflicted = FALSE;
+
+ if (status->node_status == svn_wc_status_none
+ && status->repos_node_status == svn_wc_status_none)
+ return SVN_NO_ERROR;
+
+ if (status->conflicted)
+ SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
+ ctx->wc_ctx, local_abspath, pool));
+
+ path = make_relpath(cwd_abspath, path, pool, pool);
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
+ "path", svn_dirent_local_style(path, pool), NULL);
+
+ att_hash = apr_hash_make(pool);
+ svn_hash_sets(att_hash, "item",
+ generate_status_desc(combined_status(status)));
+
+ svn_hash_sets(att_hash, "props",
+ generate_status_desc(
+ (status->node_status != svn_wc_status_deleted)
+ ? status->prop_status
+ : svn_wc_status_none));
+ if (status->wc_is_locked)
+ svn_hash_sets(att_hash, "wc-locked", "true");
+ if (status->copied)
+ svn_hash_sets(att_hash, "copied", "true");
+ if (status->switched)
+ svn_hash_sets(att_hash, "switched", "true");
+ if (status->file_external)
+ svn_hash_sets(att_hash, "file-external", "true");
+ if (status->versioned && ! status->copied)
+ svn_hash_sets(att_hash, "revision",
+ apr_psprintf(pool, "%ld", status->revision));
+ if (tree_conflicted)
+ svn_hash_sets(att_hash, "tree-conflicted", "true");
+ if (status->moved_from_abspath || status->moved_to_abspath)
+ {
+ const char *relpath;
+
+ if (status->moved_from_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_from_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ svn_hash_sets(att_hash, "moved-from", relpath);
+ }
+ if (status->moved_to_abspath)
+ {
+ relpath = make_relpath(cwd_abspath, status->moved_to_abspath,
+ pool, pool);
+ relpath = svn_dirent_local_style(relpath, pool);
+ svn_hash_sets(att_hash, "moved-to", relpath);
+ }
+ }
+ svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status",
+ att_hash);
+
+ if (SVN_IS_VALID_REVNUM(status->changed_rev))
+ {
+ svn_cl__print_xml_commit(&sb, status->changed_rev,
+ status->changed_author,
+ svn_time_to_cstring(status->changed_date,
+ pool),
+ pool);
+ }
+
+ if (status->lock)
+ svn_cl__print_xml_lock(&sb, status->lock, pool);
+
+ svn_xml_make_close_tag(&sb, pool, "wc-status");
+
+ if (status->repos_node_status != svn_wc_status_none
+ || status->repos_lock)
+ {
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status",
+ "item",
+ generate_status_desc(combined_repos_status(status)),
+ "props",
+ generate_status_desc(status->repos_prop_status),
+ NULL);
+ if (status->repos_lock)
+ svn_cl__print_xml_lock(&sb, status->repos_lock, pool);
+
+ svn_xml_make_close_tag(&sb, pool, "repos-status");
+ }
+
+ svn_xml_make_close_tag(&sb, pool, "entry");
+
+ return svn_cl__error_checked_fputs(sb->data, stdout);
+}
+
+/* Called by status-cmd.c */
+svn_error_t *
+svn_cl__print_status(const char *cwd_abspath,
+ const char *path,
+ const svn_client_status_t *status,
+ svn_boolean_t suppress_externals_placeholders,
+ svn_boolean_t detailed,
+ svn_boolean_t show_last_committed,
+ svn_boolean_t skip_unrecognized,
+ svn_boolean_t repos_locks,
+ unsigned int *text_conflicts,
+ unsigned int *prop_conflicts,
+ unsigned int *tree_conflicts,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (! status
+ || (skip_unrecognized
+ && !(status->versioned
+ || status->conflicted
+ || status->node_status == svn_wc_status_external))
+ || (status->node_status == svn_wc_status_none
+ && status->repos_node_status == svn_wc_status_none))
+ return SVN_NO_ERROR;
+
+ /* If we're trying not to print boring "X /path/to/external"
+ lines..." */
+ if (suppress_externals_placeholders)
+ {
+ /* ... skip regular externals unmodified in the repository. */
+ if ((status->node_status == svn_wc_status_external)
+ && (status->repos_node_status == svn_wc_status_none)
+ && (! status->conflicted))
+ return SVN_NO_ERROR;
+
+ /* ... skip file externals that aren't modified locally or
+ remotely, changelisted, or locked (in either sense of the
+ word). */
+ if ((status->file_external)
+ && (status->repos_node_status == svn_wc_status_none)
+ && ((status->node_status == svn_wc_status_normal)
+ || (status->node_status == svn_wc_status_none))
+ && ((status->prop_status == svn_wc_status_normal)
+ || (status->prop_status == svn_wc_status_none))
+ && (! status->changelist)
+ && (! status->lock)
+ && (! status->wc_is_locked)
+ && (! status->conflicted))
+ return SVN_NO_ERROR;
+ }
+
+ return print_status(cwd_abspath, svn_dirent_local_style(path, pool),
+ detailed, show_last_committed, repos_locks, status,
+ text_conflicts, prop_conflicts, tree_conflicts,
+ ctx, pool);
+}
diff --git a/subversion/svn/svn.1 b/subversion/svn/svn.1
new file mode 100644
index 000000000000..f4ad25184744
--- /dev/null
+++ b/subversion/svn/svn.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" 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.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svn 1
+.SH NAME
+svn \- Subversion command line client tool
+.SH SYNOPSIS
+.TP
+\fBsvn\fP \fIcommand\fP [\fIoptions\fP] [\fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svn help' to access the built-in tool documentation.
diff --git a/subversion/svn/svn.c b/subversion/svn/svn.c
new file mode 100644
index 000000000000..cbcec87cf07a
--- /dev/null
+++ b/subversion/svn/svn.c
@@ -0,0 +1,2961 @@
+/*
+ * svn.c: Subversion command line client main file.
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_general.h>
+#include <apr_signal.h>
+
+#include "svn_cmdline.h"
+#include "svn_pools.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_config.h"
+#include "svn_string.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_delta.h"
+#include "svn_diff.h"
+#include "svn_error.h"
+#include "svn_io.h"
+#include "svn_opt.h"
+#include "svn_utf.h"
+#include "svn_auth.h"
+#include "svn_hash.h"
+#include "svn_version.h"
+#include "cl.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Option Processing ***/
+
+/* Add an identifier here for long options that don't have a short
+ option. Options that have both long and short options should just
+ use the short option letter as identifier. */
+typedef enum svn_cl__longopt_t {
+ opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
+ opt_auth_username,
+ opt_autoprops,
+ opt_changelist,
+ opt_config_dir,
+ opt_config_options,
+ /* diff options */
+ opt_diff_cmd,
+ opt_internal_diff,
+ opt_no_diff_added,
+ opt_no_diff_deleted,
+ opt_show_copies_as_adds,
+ opt_notice_ancestry,
+ opt_summarize,
+ opt_use_git_diff_format,
+ opt_ignore_properties,
+ opt_properties_only,
+ opt_patch_compatible,
+ /* end of diff options */
+ opt_dry_run,
+ opt_editor_cmd,
+ opt_encoding,
+ opt_force_log,
+ opt_force,
+ opt_keep_changelists,
+ opt_ignore_ancestry,
+ opt_ignore_externals,
+ opt_incremental,
+ opt_merge_cmd,
+ opt_native_eol,
+ opt_new_cmd,
+ opt_no_auth_cache,
+ opt_no_autoprops,
+ opt_no_ignore,
+ opt_no_unlock,
+ opt_non_interactive,
+ opt_force_interactive,
+ opt_old_cmd,
+ opt_record_only,
+ opt_relocate,
+ opt_remove,
+ opt_revprop,
+ opt_stop_on_copy,
+ opt_strict,
+ opt_targets,
+ opt_depth,
+ opt_set_depth,
+ opt_version,
+ opt_xml,
+ opt_keep_local,
+ opt_with_revprop,
+ opt_with_all_revprops,
+ opt_with_no_revprops,
+ opt_parents,
+ opt_accept,
+ opt_show_revs,
+ opt_reintegrate,
+ opt_trust_server_cert,
+ opt_strip,
+ opt_ignore_keywords,
+ opt_reverse_diff,
+ opt_ignore_whitespace,
+ opt_diff,
+ opt_allow_mixed_revisions,
+ opt_include_externals,
+ opt_show_inherited_props,
+ opt_search,
+ opt_search_and
+} svn_cl__longopt_t;
+
+
+/* Option codes and descriptions for the command line client.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ */
+const apr_getopt_option_t svn_cl__options[] =
+{
+ {"force", opt_force, 0, N_("force operation to run")},
+ {"force-log", opt_force_log, 0,
+ N_("force validity of log message source")},
+ {"help", 'h', 0, N_("show help on a subcommand")},
+ {NULL, '?', 0, N_("show help on a subcommand")},
+ {"message", 'm', 1, N_("specify log message ARG")},
+ {"quiet", 'q', 0, N_("print nothing, or only summary information")},
+ {"recursive", 'R', 0, N_("descend recursively, same as --depth=infinity")},
+ {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")},
+ {"change", 'c', 1,
+ N_("the change made by revision ARG (like -r ARG-1:ARG)\n"
+ " "
+ "If ARG is negative this is like -r ARG:ARG-1\n"
+ " "
+ "If ARG is of the form ARG1-ARG2 then this is like\n"
+ " "
+ "ARG1:ARG2, where ARG1 is inclusive")},
+ {"revision", 'r', 1,
+ N_("ARG (some commands also take ARG1:ARG2 range)\n"
+ " "
+ "A revision argument can be one of:\n"
+ " "
+ " NUMBER revision number\n"
+ " "
+ " '{' DATE '}' revision at start of the date\n"
+ " "
+ " 'HEAD' latest in repository\n"
+ " "
+ " 'BASE' base rev of item's working copy\n"
+ " "
+ " 'COMMITTED' last commit at or before BASE\n"
+ " "
+ " 'PREV' revision just before COMMITTED")},
+ {"file", 'F', 1, N_("read log message from file ARG")},
+ {"incremental", opt_incremental, 0,
+ N_("give output suitable for concatenation")},
+ {"encoding", opt_encoding, 1,
+ N_("treat value as being in charset encoding ARG")},
+ {"version", opt_version, 0, N_("show program version information")},
+ {"verbose", 'v', 0, N_("print extra information")},
+ {"show-updates", 'u', 0, N_("display update information")},
+ {"username", opt_auth_username, 1, N_("specify a username ARG")},
+ {"password", opt_auth_password, 1, N_("specify a password ARG")},
+ {"extensions", 'x', 1,
+ N_("Specify differencing options for external diff or\n"
+ " "
+ "internal diff or blame. Default: '-u'. Options are\n"
+ " "
+ "separated by spaces. Internal diff and blame take:\n"
+ " "
+ " -u, --unified: Show 3 lines of unified context\n"
+ " "
+ " -b, --ignore-space-change: Ignore changes in\n"
+ " "
+ " amount of white space\n"
+ " "
+ " -w, --ignore-all-space: Ignore all white space\n"
+ " "
+ " --ignore-eol-style: Ignore changes in EOL style\n"
+ " "
+ " -p, --show-c-function: Show C function name")},
+ {"targets", opt_targets, 1,
+ N_("pass contents of file ARG as additional args")},
+ {"depth", opt_depth, 1,
+ N_("limit operation by depth ARG ('empty', 'files',\n"
+ " "
+ "'immediates', or 'infinity')")},
+ {"set-depth", opt_set_depth, 1,
+ N_("set new working copy depth to ARG ('exclude',\n"
+ " "
+ "'empty', 'files', 'immediates', or 'infinity')")},
+ {"xml", opt_xml, 0, N_("output in XML")},
+ {"strict", opt_strict, 0, N_("use strict semantics")},
+ {"stop-on-copy", opt_stop_on_copy, 0,
+ N_("do not cross copies while traversing history")},
+ {"no-ignore", opt_no_ignore, 0,
+ N_("disregard default and svn:ignore and\n"
+ " "
+ "svn:global-ignores property ignores")},
+ {"no-auth-cache", opt_no_auth_cache, 0,
+ N_("do not cache authentication tokens")},
+ {"trust-server-cert", opt_trust_server_cert, 0,
+ N_("accept SSL server certificates from unknown\n"
+ " "
+ "certificate authorities without prompting (but only\n"
+ " "
+ "with '--non-interactive')") },
+ {"non-interactive", opt_non_interactive, 0,
+ N_("do no interactive prompting (default is to prompt\n"
+ " "
+ "only if standard input is a terminal device)")},
+ {"force-interactive", opt_force_interactive, 0,
+ N_("do interactive prompting even if standard input\n"
+ " "
+ "is not a terminal device")},
+ {"dry-run", opt_dry_run, 0,
+ N_("try operation but make no changes")},
+ {"ignore-ancestry", opt_ignore_ancestry, 0,
+ N_("disable merge tracking; diff nodes as if related")},
+ {"ignore-externals", opt_ignore_externals, 0,
+ N_("ignore externals definitions")},
+ {"diff3-cmd", opt_merge_cmd, 1, N_("use ARG as merge command")},
+ {"editor-cmd", opt_editor_cmd, 1, N_("use ARG as external editor")},
+ {"record-only", opt_record_only, 0,
+ N_("merge only mergeinfo differences")},
+ {"old", opt_old_cmd, 1, N_("use ARG as the older target")},
+ {"new", opt_new_cmd, 1, N_("use ARG as the newer target")},
+ {"revprop", opt_revprop, 0,
+ N_("operate on a revision property (use with -r)")},
+ {"relocate", opt_relocate, 0, N_("relocate via URL-rewriting")},
+ {"config-dir", opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+ {"config-option", opt_config_options, 1,
+ N_("set user configuration option in the format:\n"
+ " "
+ " FILE:SECTION:OPTION=[VALUE]\n"
+ " "
+ "For example:\n"
+ " "
+ " servers:global:http-library=serf")},
+ {"auto-props", opt_autoprops, 0, N_("enable automatic properties")},
+ {"no-auto-props", opt_no_autoprops, 0, N_("disable automatic properties")},
+ {"native-eol", opt_native_eol, 1,
+ N_("use a different EOL marker than the standard\n"
+ " "
+ "system marker for files with the svn:eol-style\n"
+ " "
+ "property set to 'native'.\n"
+ " "
+ "ARG may be one of 'LF', 'CR', 'CRLF'")},
+ {"limit", 'l', 1, N_("maximum number of log entries")},
+ {"no-unlock", opt_no_unlock, 0, N_("don't unlock the targets")},
+ {"remove", opt_remove, 0, N_("remove changelist association")},
+ {"changelist", opt_changelist, 1,
+ N_("operate only on members of changelist ARG")},
+ {"keep-changelists", opt_keep_changelists, 0,
+ N_("don't delete changelists after commit")},
+ {"keep-local", opt_keep_local, 0, N_("keep path in working copy")},
+ {"with-all-revprops", opt_with_all_revprops, 0,
+ N_("retrieve all revision properties")},
+ {"with-no-revprops", opt_with_no_revprops, 0,
+ N_("retrieve no revision properties")},
+ {"with-revprop", opt_with_revprop, 1,
+ N_("set revision property ARG in new revision\n"
+ " "
+ "using the name[=value] format")},
+ {"parents", opt_parents, 0, N_("make intermediate directories")},
+ {"use-merge-history", 'g', 0,
+ N_("use/display additional information from merge\n"
+ " "
+ "history")},
+ {"accept", opt_accept, 1,
+ N_("specify automatic conflict resolution action\n"
+ " "
+ "('postpone', 'working', 'base', 'mine-conflict',\n"
+ " "
+ "'theirs-conflict', 'mine-full', 'theirs-full',\n"
+ " "
+ "'edit', 'launch')\n"
+ " "
+ "(shorthand: 'p', 'mc', 'tc', 'mf', 'tf', 'e', 'l')"
+ )},
+ {"show-revs", opt_show_revs, 1,
+ N_("specify which collection of revisions to display\n"
+ " "
+ "('merged', 'eligible')")},
+ {"reintegrate", opt_reintegrate, 0,
+ N_("deprecated")},
+ {"strip", opt_strip, 1,
+ N_("number of leading path components to strip from\n"
+ " "
+ "paths parsed from the patch file. --strip 0\n"
+ " "
+ "is the default and leaves paths unmodified.\n"
+ " "
+ "--strip 1 would change the path\n"
+ " "
+ "'doc/fudge/crunchy.html' to 'fudge/crunchy.html'.\n"
+ " "
+ "--strip 2 would leave just 'crunchy.html'\n"
+ " "
+ "The expected component separator is '/' on all\n"
+ " "
+ "platforms. A leading '/' counts as one component.")},
+ {"ignore-keywords", opt_ignore_keywords, 0,
+ N_("don't expand keywords")},
+ {"reverse-diff", opt_reverse_diff, 0,
+ N_("apply the unidiff in reverse")},
+ {"ignore-whitespace", opt_ignore_whitespace, 0,
+ N_("ignore whitespace during pattern matching")},
+ {"diff", opt_diff, 0, N_("produce diff output")}, /* maps to show_diff */
+ /* diff options */
+ {"diff-cmd", opt_diff_cmd, 1, N_("use ARG as diff command")},
+ {"internal-diff", opt_internal_diff, 0,
+ N_("override diff-cmd specified in config file")},
+ {"no-diff-added", opt_no_diff_added, 0,
+ N_("do not print differences for added files")},
+ {"no-diff-deleted", opt_no_diff_deleted, 0,
+ N_("do not print differences for deleted files")},
+ {"show-copies-as-adds", opt_show_copies_as_adds, 0,
+ N_("don't diff copied or moved files with their source")},
+ {"notice-ancestry", opt_notice_ancestry, 0,
+ N_("diff unrelated nodes as delete and add")},
+ {"summarize", opt_summarize, 0, N_("show a summary of the results")},
+ {"git", opt_use_git_diff_format, 0,
+ N_("use git's extended diff format")},
+ {"ignore-properties", opt_ignore_properties, 0,
+ N_("ignore properties during the operation")},
+ {"properties-only", opt_properties_only, 0,
+ N_("show only properties during the operation")},
+ {"patch-compatible", opt_patch_compatible, 0,
+ N_("generate diff suitable for generic third-party\n"
+ " "
+ "patch tools; currently the same as\n"
+ " "
+ "--show-copies-as-adds --ignore-properties"
+ )},
+ /* end of diff options */
+ {"allow-mixed-revisions", opt_allow_mixed_revisions, 0,
+ N_("Allow operation on mixed-revision working copy.\n"
+ " "
+ "Use of this option is not recommended!\n"
+ " "
+ "Please run 'svn update' instead.")},
+ {"include-externals", opt_include_externals, 0,
+ N_("Also commit file and dir externals reached by\n"
+ " "
+ "recursion. This does not include externals with a\n"
+ " "
+ "fixed revision. (See the svn:externals property)")},
+ {"show-inherited-props", opt_show_inherited_props, 0,
+ N_("retrieve target's inherited properties")},
+ {"search", opt_search, 1,
+ N_("use ARG as search pattern (glob syntax)")},
+ {"search-and", opt_search_and, 1,
+ N_("combine ARG with the previous search pattern")},
+
+ /* Long-opt Aliases
+ *
+ * These have NULL desriptions, but an option code that matches some
+ * other option (whose description should probably mention its aliases).
+ */
+
+ {"cl", opt_changelist, 1, NULL},
+
+ {0, 0, 0, 0},
+};
+
+
+
+/*** Command dispatch. ***/
+
+/* Our array of available subcommands.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ *
+ * In most of the help text "PATH" is used where a working copy path is
+ * required, "URL" where a repository URL is required and "TARGET" when
+ * either a path or a url can be used. Hmm, should this be part of the
+ * help text?
+ */
+
+/* Options that apply to all commands. (While not every command may
+ currently require authentication or be interactive, allowing every
+ command to take these arguments allows scripts to just pass them
+ willy-nilly to every invocation of 'svn') . */
+const int svn_cl__global_options[] =
+{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive,
+ opt_force_interactive, opt_trust_server_cert, opt_config_dir,
+ opt_config_options, 0
+};
+
+/* Options for giving a log message. (Some of these also have other uses.)
+ */
+#define SVN_CL__LOG_MSG_OPTIONS 'm', 'F', \
+ opt_force_log, \
+ opt_editor_cmd, \
+ opt_encoding, \
+ opt_with_revprop
+
+const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
+{
+ { "add", svn_cl__add, {0}, N_
+ ("Put files and directories under version control, scheduling\n"
+ "them for addition to repository. They will be added in next commit.\n"
+ "usage: add PATH...\n"),
+ {opt_targets, 'N', opt_depth, 'q', opt_force, opt_no_ignore, opt_autoprops,
+ opt_no_autoprops, opt_parents },
+ {{opt_parents, N_("add intermediate parents")}} },
+
+ { "blame", svn_cl__blame, {"praise", "annotate", "ann"}, N_
+ ("Output the content of specified files or\n"
+ "URLs with revision and author information in-line.\n"
+ "usage: blame TARGET[@REV]...\n"
+ "\n"
+ " If specified, REV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r', 'v', 'g', opt_incremental, opt_xml, 'x', opt_force} },
+
+ { "cat", svn_cl__cat, {0}, N_
+ ("Output the content of specified files or URLs.\n"
+ "usage: cat TARGET[@REV]...\n"
+ "\n"
+ " If specified, REV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r'} },
+
+ { "changelist", svn_cl__changelist, {"cl"}, N_
+ ("Associate (or dissociate) changelist CLNAME with the named files.\n"
+ "usage: 1. changelist CLNAME PATH...\n"
+ " 2. changelist --remove PATH...\n"),
+ { 'q', 'R', opt_depth, opt_remove, opt_targets, opt_changelist} },
+
+ { "checkout", svn_cl__checkout, {"co"}, N_
+ ("Check out a working copy from a repository.\n"
+ "usage: checkout URL[@REV]... [PATH]\n"
+ "\n"
+ " If specified, REV determines in which revision the URL is first\n"
+ " looked up.\n"
+ "\n"
+ " If PATH is omitted, the basename of the URL will be used as\n"
+ " the destination. If multiple URLs are given each will be checked\n"
+ " out into a sub-directory of PATH, with the name of the sub-directory\n"
+ " being the basename of the URL.\n"
+ "\n"
+ " If --force is used, unversioned obstructing paths in the working\n"
+ " copy destination do not automatically cause the check out to fail.\n"
+ " If the obstructing path is the same type (file or directory) as the\n"
+ " corresponding path in the repository it becomes versioned but its\n"
+ " contents are left 'as-is' in the working copy. This means that an\n"
+ " obstructing directory's unversioned children may also obstruct and\n"
+ " become versioned. For files, any content differences between the\n"
+ " obstruction and the repository are treated like a local modification\n"
+ " to the working copy. All properties from the repository are applied\n"
+ " to the obstructing path.\n"
+ "\n"
+ " See also 'svn help update' for a list of possible characters\n"
+ " reporting the action taken.\n"),
+ {'r', 'q', 'N', opt_depth, opt_force, opt_ignore_externals} },
+
+ { "cleanup", svn_cl__cleanup, {0}, N_
+ ("Recursively clean up the working copy, removing locks, resuming\n"
+ "unfinished operations, etc.\n"
+ "usage: cleanup [WCPATH...]\n"),
+ {opt_merge_cmd} },
+
+ { "commit", svn_cl__commit, {"ci"},
+ N_("Send changes from your working copy to the repository.\n"
+ "usage: commit [PATH...]\n"
+ "\n"
+ " A log message must be provided, but it can be empty. If it is not\n"
+ " given by a --message or --file option, an editor will be started.\n"
+ " If any targets are (or contain) locked items, those will be\n"
+ " unlocked after a successful commit.\n"),
+ {'q', 'N', opt_depth, opt_targets, opt_no_unlock, SVN_CL__LOG_MSG_OPTIONS,
+ opt_changelist, opt_keep_changelists, opt_include_externals} },
+
+ { "copy", svn_cl__copy, {"cp"}, N_
+ ("Copy files and directories in a working copy or repository.\n"
+ "usage: copy SRC[@REV]... DST\n"
+ "\n"
+ " SRC and DST can each be either a working copy (WC) path or URL:\n"
+ " WC -> WC: copy and schedule for addition (with history)\n"
+ " WC -> URL: immediately commit a copy of WC to URL\n"
+ " URL -> WC: check out URL into WC, schedule for addition\n"
+ " URL -> URL: complete server-side copy; used to branch and tag\n"
+ " All the SRCs must be of the same type. When copying multiple sources,\n"
+ " they will be added as children of DST, which must be a directory.\n"
+ "\n"
+ " WARNING: For compatibility with previous versions of Subversion,\n"
+ " copies performed using two working copy paths (WC -> WC) will not\n"
+ " contact the repository. As such, they may not, by default, be able\n"
+ " to propagate merge tracking information from the source of the copy\n"
+ " to the destination.\n"),
+ {'r', 'q', opt_ignore_externals, opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
+
+ { "delete", svn_cl__delete, {"del", "remove", "rm"}, N_
+ ("Remove files and directories from version control.\n"
+ "usage: 1. delete PATH...\n"
+ " 2. delete URL...\n"
+ "\n"
+ " 1. Each item specified by a PATH is scheduled for deletion upon\n"
+ " the next commit. Files, and directories that have not been\n"
+ " committed, are immediately removed from the working copy\n"
+ " unless the --keep-local option is given.\n"
+ " PATHs that are, or contain, unversioned or modified items will\n"
+ " not be removed unless the --force or --keep-local option is given.\n"
+ "\n"
+ " 2. Each item specified by a URL is deleted from the repository\n"
+ " via an immediate commit.\n"),
+ {opt_force, 'q', opt_targets, SVN_CL__LOG_MSG_OPTIONS, opt_keep_local} },
+
+ { "diff", svn_cl__diff, {"di"}, N_
+ ("Display local changes or differences between two revisions or paths.\n"
+ "usage: 1. diff\n"
+ " 2. diff [-c M | -r N[:M]] [TARGET[@REV]...]\n"
+ " 3. diff [-r N[:M]] --old=OLD-TGT[@OLDREV] [--new=NEW-TGT[@NEWREV]] \\\n"
+ " [PATH...]\n"
+ " 4. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]\n"
+ " 5. diff OLD-URL[@OLDREV] NEW-PATH[@NEWREV]\n"
+ " 6. diff OLD-PATH[@OLDREV] NEW-URL[@NEWREV]\n"
+ "\n"
+ " 1. Use just 'svn diff' to display local modifications in a working copy.\n"
+ "\n"
+ " 2. Display the changes made to TARGETs as they are seen in REV between\n"
+ " two revisions. TARGETs may be all working copy paths or all URLs.\n"
+ " If TARGETs are working copy paths, N defaults to BASE and M to the\n"
+ " working copy; if URLs, N must be specified and M defaults to HEAD.\n"
+ " The '-c M' option is equivalent to '-r N:M' where N = M-1.\n"
+ " Using '-c -M' does the reverse: '-r M:N' where N = M-1.\n"
+ "\n"
+ " 3. Display the differences between OLD-TGT as it was seen in OLDREV and\n"
+ " NEW-TGT as it was seen in NEWREV. PATHs, if given, are relative to\n"
+ " OLD-TGT and NEW-TGT and restrict the output to differences for those\n"
+ " paths. OLD-TGT and NEW-TGT may be working copy paths or URL[@REV].\n"
+ " NEW-TGT defaults to OLD-TGT if not specified. -r N makes OLDREV default\n"
+ " to N, -r N:M makes OLDREV default to N and NEWREV default to M.\n"
+ " If OLDREV or NEWREV are not specified, they default to WORKING for\n"
+ " working copy targets and to HEAD for URL targets.\n"
+ "\n"
+ " Either or both OLD-TGT and NEW-TGT may also be paths to unversioned\n"
+ " targets. Revisions cannot be specified for unversioned targets.\n"
+ " Both targets must be of the same node kind (file or directory).\n"
+ " Diffing unversioned targets against URL targets is not supported.\n"
+ "\n"
+ " 4. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\n"
+ " 5. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-PATH[@NEWREV]'\n"
+ " 6. Shorthand for 'svn diff --old=OLD-PATH[@OLDREV] --new=NEW-URL[@NEWREV]'\n"),
+ {'r', 'c', opt_old_cmd, opt_new_cmd, 'N', opt_depth, opt_diff_cmd,
+ opt_internal_diff, 'x', opt_no_diff_added, opt_no_diff_deleted,
+ opt_ignore_properties, opt_properties_only,
+ opt_show_copies_as_adds, opt_notice_ancestry, opt_summarize, opt_changelist,
+ opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible} },
+ { "export", svn_cl__export, {0}, N_
+ ("Create an unversioned copy of a tree.\n"
+ "usage: 1. export [-r REV] URL[@PEGREV] [PATH]\n"
+ " 2. export [-r REV] PATH1[@PEGREV] [PATH2]\n"
+ "\n"
+ " 1. Exports a clean directory tree from the repository specified by\n"
+ " URL, at revision REV if it is given, otherwise at HEAD, into\n"
+ " PATH. If PATH is omitted, the last component of the URL is used\n"
+ " for the local directory name.\n"
+ "\n"
+ " 2. Exports a clean directory tree from the working copy specified by\n"
+ " PATH1, at revision REV if it is given, otherwise at WORKING, into\n"
+ " PATH2. If PATH2 is omitted, the last component of the PATH1 is used\n"
+ " for the local directory name. If REV is not specified, all local\n"
+ " changes will be preserved. Files not under version control will\n"
+ " not be copied.\n"
+ "\n"
+ " If specified, PEGREV determines in which revision the target is first\n"
+ " looked up.\n"),
+ {'r', 'q', 'N', opt_depth, opt_force, opt_native_eol, opt_ignore_externals,
+ opt_ignore_keywords} },
+
+ { "help", svn_cl__help, {"?", "h"}, N_
+ ("Describe the usage of this program or its subcommands.\n"
+ "usage: help [SUBCOMMAND...]\n"),
+ {0} },
+ /* This command is also invoked if we see option "--help", "-h" or "-?". */
+
+ { "import", svn_cl__import, {0}, N_
+ ("Commit an unversioned file or tree into the repository.\n"
+ "usage: import [PATH] URL\n"
+ "\n"
+ " Recursively commit a copy of PATH to URL.\n"
+ " If PATH is omitted '.' is assumed.\n"
+ " Parent directories are created as necessary in the repository.\n"
+ " If PATH is a directory, the contents of the directory are added\n"
+ " directly under URL.\n"
+ " Unversionable items such as device files and pipes are ignored\n"
+ " if --force is specified.\n"),
+ {'q', 'N', opt_depth, opt_autoprops, opt_force, opt_no_autoprops,
+ SVN_CL__LOG_MSG_OPTIONS, opt_no_ignore} },
+
+ { "info", svn_cl__info, {0}, N_
+ ("Display information about a local or remote item.\n"
+ "usage: info [TARGET[@REV]...]\n"
+ "\n"
+ " Print information about each TARGET (default: '.').\n"
+ " TARGET may be either a working-copy path or URL. If specified, REV\n"
+ " determines in which revision the target is first looked up.\n"),
+ {'r', 'R', opt_depth, opt_targets, opt_incremental, opt_xml, opt_changelist}
+ },
+
+ { "list", svn_cl__list, {"ls"}, N_
+ ("List directory entries in the repository.\n"
+ "usage: list [TARGET[@REV]...]\n"
+ "\n"
+ " List each TARGET file and the contents of each TARGET directory as\n"
+ " they exist in the repository. If TARGET is a working copy path, the\n"
+ " corresponding repository URL will be used. If specified, REV determines\n"
+ " in which revision the target is first looked up.\n"
+ "\n"
+ " The default TARGET is '.', meaning the repository URL of the current\n"
+ " working directory.\n"
+ "\n"
+ " With --verbose, the following fields will be shown for each item:\n"
+ "\n"
+ " Revision number of the last commit\n"
+ " Author of the last commit\n"
+ " If locked, the letter 'O'. (Use 'svn info URL' to see details)\n"
+ " Size (in bytes)\n"
+ " Date and time of the last commit\n"),
+ {'r', 'v', 'R', opt_depth, opt_incremental, opt_xml,
+ opt_include_externals },
+ {{opt_include_externals, N_("include externals definitions")}} },
+
+ { "lock", svn_cl__lock, {0}, N_
+ ("Lock working copy paths or URLs in the repository, so that\n"
+ "no other user can commit changes to them.\n"
+ "usage: lock TARGET...\n"
+ "\n"
+ " Use --force to steal the lock from another user or working copy.\n"),
+ { opt_targets, 'm', 'F', opt_force_log, opt_encoding, opt_force },
+ {{'F', N_("read lock comment from file ARG")},
+ {'m', N_("specify lock comment ARG")},
+ {opt_force_log, N_("force validity of lock comment source")}} },
+
+ { "log", svn_cl__log, {0}, N_
+ ("Show the log messages for a set of revision(s) and/or path(s).\n"
+ "usage: 1. log [PATH][@REV]\n"
+ " 2. log URL[@REV] [PATH...]\n"
+ "\n"
+ " 1. Print the log messages for the URL corresponding to PATH\n"
+ " (default: '.'). If specified, REV is the revision in which the\n"
+ " URL is first looked up, and the default revision range is REV:1.\n"
+ " If REV is not specified, the default revision range is BASE:1,\n"
+ " since the URL might not exist in the HEAD revision.\n"
+ "\n"
+ " 2. Print the log messages for the PATHs (default: '.') under URL.\n"
+ " If specified, REV is the revision in which the URL is first\n"
+ " looked up, and the default revision range is REV:1; otherwise,\n"
+ " the URL is looked up in HEAD, and the default revision range is\n"
+ " HEAD:1.\n"
+ "\n"
+ " Multiple '-c' or '-r' options may be specified (but not a\n"
+ " combination of '-c' and '-r' options), and mixing of forward and\n"
+ " reverse ranges is allowed.\n"
+ "\n"
+ " With -v, also print all affected paths with each log message.\n"
+ " With -q, don't print the log message body itself (note that this is\n"
+ " compatible with -v).\n"
+ "\n"
+ " Each log message is printed just once, even if more than one of the\n"
+ " affected paths for that revision were explicitly requested. Logs\n"
+ " follow copy history by default. Use --stop-on-copy to disable this\n"
+ " behavior, which can be useful for determining branchpoints.\n"
+ "\n"
+ " The --depth option is only valid in combination with the --diff option\n"
+ " and limits the scope of the displayed diff to the specified depth.\n"
+ "\n"
+ " If the --search option is used, log messages are displayed only if the\n"
+ " provided search pattern matches any of the author, date, log message\n"
+ " text (unless --quiet is used), or, if the --verbose option is also\n"
+ " provided, a changed path.\n"
+ " The search pattern may include \"glob syntax\" wildcards:\n"
+ " ? matches any single character\n"
+ " * matches a sequence of arbitrary characters\n"
+ " [abc] matches any of the characters listed inside the brackets\n"
+ " If multiple --search options are provided, a log message is shown if\n"
+ " it matches any of the provided search patterns. If the --search-and\n"
+ " option is used, that option's argument is combined with the pattern\n"
+ " from the previous --search or --search-and option, and a log message\n"
+ " is shown only if it matches the combined search pattern.\n"
+ " If --limit is used in combination with --search, --limit restricts the\n"
+ " number of log messages searched, rather than restricting the output\n"
+ " to a particular number of matching log messages.\n"
+ "\n"
+ " Examples:\n"
+ "\n"
+ " Show the latest 5 log messages for the current working copy\n"
+ " directory and display paths changed in each commit:\n"
+ " svn log -l 5 -v\n"
+ "\n"
+ " Show the log for bar.c as of revision 42:\n"
+ " svn log bar.c@42\n"
+ "\n"
+ " Show log messages and diffs for each commit to foo.c:\n"
+ " svn log --diff http://www.example.com/repo/project/foo.c\n"
+ " (Because the above command uses a full URL it does not require\n"
+ " a working copy.)\n"
+ "\n"
+ " Show log messages for the children foo.c and bar.c of the directory\n"
+ " '/trunk' as it appeared in revision 50, using the ^/ URL shortcut:\n"
+ " svn log ^/trunk@50 foo.c bar.c\n"
+ "\n"
+ " Show the log messages for any incoming changes to foo.c during the\n"
+ " next 'svn update':\n"
+ " svn log -r BASE:HEAD foo.c\n"
+ "\n"
+ " Show the log message for the revision in which /branches/foo\n"
+ " was created:\n"
+ " svn log --stop-on-copy --limit 1 -r0:HEAD ^/branches/foo\n"),
+ {'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy, opt_incremental,
+ opt_xml, 'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop,
+ opt_depth, opt_diff, opt_diff_cmd, opt_internal_diff, 'x', opt_search,
+ opt_search_and, },
+ {{opt_with_revprop, N_("retrieve revision property ARG")},
+ {'c', N_("the change made in revision ARG")}} },
+
+ { "merge", svn_cl__merge, {0}, N_
+ ( /* For this large section, let's keep it unindented for easier
+ * viewing/editing. It has been vim-treated with a textwidth=75 and 'gw'
+ * (with quotes and newlines removed). */
+"Merge changes into a working copy.\n"
+"usage: 1. merge SOURCE[@REV] [TARGET_WCPATH]\n"
+" (the 'automatic' merge)\n"
+" 2. merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
+" (the 'cherry-pick' merge)\n"
+" 3. merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n"
+" (the '2-URL' merge)\n"
+"\n"
+" 1. This form, with one source path and no revision range, is called\n"
+" an 'automatic' merge:\n"
+"\n"
+" svn merge SOURCE[@REV] [TARGET_WCPATH]\n"
+"\n"
+" The automatic merge is used for the 'sync' and 'reintegrate' merges\n"
+" in the 'feature branch' pattern described below. It finds all the\n"
+" changes on the source branch that have not already been merged to the\n"
+" target branch, and merges them into the working copy. Merge tracking\n"
+" is used to know which changes have already been merged.\n"
+"\n"
+" SOURCE specifies the branch from where the changes will be pulled, and\n"
+" TARGET_WCPATH specifies a working copy of the target branch to which\n"
+" the changes will be applied. Normally SOURCE and TARGET_WCPATH should\n"
+" each correspond to the root of a branch. (If you want to merge only a\n"
+" subtree, then the subtree path must be included in both SOURCE and\n"
+" TARGET_WCPATH; this is discouraged, to avoid subtree mergeinfo.)\n"
+"\n"
+" SOURCE is usually a URL. The optional '@REV' specifies both the peg\n"
+" revision of the URL and the latest revision that will be considered\n"
+" for merging; if REV is not specified, the HEAD revision is assumed. If\n"
+" SOURCE is a working copy path, the corresponding URL of the path is\n"
+" used, and the default value of 'REV' is the base revision (usually the\n"
+" revision last updated to).\n"
+"\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. There are some special cases:\n"
+"\n"
+" - If SOURCE is a URL:\n"
+"\n"
+" - If the basename of the URL and the basename of '.' are the\n"
+" same, then the differences are applied to '.'. Otherwise,\n"
+" if a file with the same basename as that of the URL is found\n"
+" within '.', then the differences are applied to that file.\n"
+" In all other cases, the target defaults to '.'.\n"
+"\n"
+" - If SOURCE is a working copy path:\n"
+"\n"
+" - If the source is a file, then differences are applied to that\n"
+" file (useful for reverse-merging earlier changes). Otherwise,\n"
+" if the source is a directory, then the target defaults to '.'.\n"
+"\n"
+" In normal usage the working copy should be up to date, at a single\n"
+" revision, with no local modifications and no switched subtrees.\n"
+"\n"
+" - The 'Feature Branch' Merging Pattern -\n"
+"\n"
+" In this commonly used work flow, known also as the 'development\n"
+" branch' pattern, a developer creates a branch and commits a series of\n"
+" changes that implement a new feature. The developer periodically\n"
+" merges all the latest changes from the parent branch so as to keep the\n"
+" development branch up to date with those changes. When the feature is\n"
+" complete, the developer performs a merge from the feature branch to\n"
+" the parent branch to re-integrate the changes.\n"
+"\n"
+" parent --+----------o------o-o-------------o--\n"
+" \\ \\ \\ /\n"
+" \\ merge merge merge\n"
+" \\ \\ \\ /\n"
+" feature +--o-o-------o----o-o----o-------\n"
+"\n"
+" A merge from the parent branch to the feature branch is called a\n"
+" 'sync' or 'catch-up' merge, and a merge from the feature branch to the\n"
+" parent branch is called a 'reintegrate' merge.\n"
+"\n"
+" - Sync Merge Example -\n"
+" ............\n"
+" . .\n"
+" trunk --+------------L--------------R------\n"
+" \\ \\\n"
+" \\ |\n"
+" \\ v\n"
+" feature +------------------------o-----\n"
+" r100 r200\n"
+"\n"
+" Subversion will locate all the changes on 'trunk' that have not yet\n"
+" been merged into the 'feature' branch. In this case that is a single\n"
+" range, r100:200. In the diagram above, L marks the left side (trunk@100)\n"
+" and R marks the right side (trunk@200) of the merge source. The\n"
+" difference between L and R will be applied to the target working copy\n"
+" path. In this case, the working copy is a clean checkout of the entire\n"
+" 'feature' branch.\n"
+"\n"
+" To perform this sync merge, have a clean working copy of the feature\n"
+" branch and run the following command in its top-level directory:\n"
+"\n"
+" svn merge ^/trunk\n"
+"\n"
+" Note that the merge is now only in your local working copy and still\n"
+" needs to be committed to the repository so that it can be seen by\n"
+" others. You can review the changes and you may have to resolve\n"
+" conflicts before you commit the merge.\n"
+"\n"
+" - Reintegrate Merge Example -\n"
+"\n"
+" The feature branch was last synced with trunk up to revision X. So the\n"
+" difference between trunk@X and feature@HEAD contains the complete set\n"
+" of changes that implement the feature, and no other changes. These\n"
+" changes are applied to trunk.\n"
+"\n"
+" rW rX\n"
+" trunk ------+--------------------L------------------o\n"
+" \\ . ^\n"
+" \\ ............. /\n"
+" \\ . /\n"
+" feature +--------------------------------R\n"
+"\n"
+" In the diagram above, L marks the left side (trunk@X) and R marks the\n"
+" right side (feature@HEAD) of the merge. The difference between the\n"
+" left and right side is merged into trunk, the target.\n"
+"\n"
+" To perform the merge, have a clean working copy of trunk and run the\n"
+" following command in its top-level directory:\n"
+"\n"
+" svn merge ^/feature\n"
+"\n"
+" To prevent unnecessary merge conflicts, a reintegrate merge requires\n"
+" that TARGET_WCPATH is not a mixed-revision working copy, has no local\n"
+" modifications, and has no switched subtrees.\n"
+"\n"
+" A reintegrate merge also requires that the source branch is coherently\n"
+" synced with the target -- in the above example, this means that all\n"
+" revisions between the branch point W and the last merged revision X\n"
+" are merged to the feature branch, so that there are no unmerged\n"
+" revisions in-between.\n"
+"\n"
+"\n"
+" 2. This form is called a 'cherry-pick' merge:\n"
+"\n"
+" svn merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
+"\n"
+" A cherry-pick merge is used to merge specific revisions (or revision\n"
+" ranges) from one branch to another. By default, this uses merge\n"
+" tracking to automatically skip any revisions that have already been\n"
+" merged to the target; you can use the --ignore-ancestry option to\n"
+" disable such skipping.\n"
+"\n"
+" SOURCE is usually a URL. The optional '@REV' specifies only the peg\n"
+" revision of the URL and does not affect the merge range; if REV is not\n"
+" specified, the HEAD revision is assumed. If SOURCE is a working copy\n"
+" path, the corresponding URL of the path is used, and the default value\n"
+" of 'REV' is the base revision (usually the revision last updated to).\n"
+"\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. The special cases noted above in the 'automatic' merge form\n"
+" also apply here.\n"
+"\n"
+" The revision ranges to be merged are specified by the '-r' and/or '-c'\n"
+" options. '-r N:M' refers to the difference in the history of the\n"
+" source branch between revisions N and M. You can use '-c M' to merge\n"
+" single revisions: '-c M' is equivalent to '-r <M-1>:M'. Each such\n"
+" difference is applied to TARGET_WCPATH.\n"
+"\n"
+" If the mergeinfo in TARGET_WCPATH indicates that revisions within the\n"
+" range were already merged, changes made in those revisions are not\n"
+" merged again. If needed, the range is broken into multiple sub-ranges,\n"
+" and each sub-range is merged separately.\n"
+"\n"
+" A 'reverse range' can be used to undo changes. For example, when\n"
+" source and target refer to the same branch, a previously committed\n"
+" revision can be 'undone'. In a reverse range, N is greater than M in\n"
+" '-r N:M', or the '-c' option is used with a negative number: '-c -M'\n"
+" is equivalent to '-r M:<M-1>'. Undoing changes like this is also known\n"
+" as performing a 'reverse merge'.\n"
+"\n"
+" Multiple '-c' and/or '-r' options may be specified and mixing of\n"
+" forward and reverse ranges is allowed.\n"
+"\n"
+" - Cherry-pick Merge Example -\n"
+"\n"
+" A bug has been fixed on trunk in revision 50. This fix needs to\n"
+" be merged from trunk onto the release branch.\n"
+"\n"
+" 1.x-release +-----------------------o-----\n"
+" / ^\n"
+" / |\n"
+" / |\n"
+" trunk ------+--------------------------LR-----\n"
+" r50\n"
+"\n"
+" In the above diagram, L marks the left side (trunk@49) and R marks the\n"
+" right side (trunk@50) of the merge. The difference between the left\n"
+" and right side is applied to the target working copy path.\n"
+"\n"
+" Note that the difference between revision 49 and 50 is exactly those\n"
+" changes that were committed in revision 50, not including changes\n"
+" committed in revision 49.\n"
+"\n"
+" To perform the merge, have a clean working copy of the release branch\n"
+" and run the following command in its top-level directory; remember\n"
+" that the default target is '.':\n"
+"\n"
+" svn merge -c50 ^/trunk\n"
+"\n"
+" You can also cherry-pick several revisions and/or revision ranges:\n"
+"\n"
+" svn merge -c50,54,60 -r65:68 ^/trunk\n"
+"\n"
+"\n"
+" 3. This form is called a '2-URL merge':\n"
+"\n"
+" svn merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n"
+"\n"
+" You should use this merge variant only if the other variants do not\n"
+" apply to your situation, as this variant can be quite complex to\n"
+" master.\n"
+"\n"
+" Two source URLs are specified, identifying two trees on the same\n"
+" branch or on different branches. The trees are compared and the\n"
+" difference from SOURCE1@REV1 to SOURCE2@REV2 is applied to the\n"
+" working copy of the target branch at TARGET_WCPATH. The target\n"
+" branch may be the same as one or both sources, or different again.\n"
+" The three branches involved can be completely unrelated.\n"
+"\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. The special cases noted above in the 'automatic' merge form\n"
+" also apply here.\n"
+"\n"
+" SOURCE1 and/or SOURCE2 can also be specified as a working copy path,\n"
+" in which case the merge source URL is derived from the working copy.\n"
+"\n"
+" - 2-URL Merge Example -\n"
+"\n"
+" Two features have been developed on separate branches called 'foo' and\n"
+" 'bar'. It has since become clear that 'bar' should be combined with\n"
+" the 'foo' branch for further development before reintegration.\n"
+"\n"
+" Although both feature branches originate from trunk, they are not\n"
+" directly related -- one is not a direct copy of the other. A 2-URL\n"
+" merge is necessary.\n"
+"\n"
+" The 'bar' branch has been synced with trunk up to revision 500.\n"
+" (If this revision number is not known, it can be located using the\n"
+" 'svn log' and/or 'svn mergeinfo' commands.)\n"
+" The difference between trunk@500 and bar@HEAD contains the complete\n"
+" set of changes related to feature 'bar', and no other changes. These\n"
+" changes are applied to the 'foo' branch.\n"
+"\n"
+" foo +-----------------------------------o\n"
+" / ^\n"
+" / /\n"
+" / r500 /\n"
+" trunk ------+------+-----------------L---------> /\n"
+" \\ . /\n"
+" \\ ............ /\n"
+" \\ . /\n"
+" bar +-----------------------------------R\n"
+"\n"
+" In the diagram above, L marks the left side (trunk@500) and R marks\n"
+" the right side (bar@HEAD) of the merge. The difference between the\n"
+" left and right side is applied to the target working copy path, in\n"
+" this case a working copy of the 'foo' branch.\n"
+"\n"
+" To perform the merge, have a clean working copy of the 'foo' branch\n"
+" and run the following command in its top-level directory:\n"
+"\n"
+" svn merge ^/trunk@500 ^/bar\n"
+"\n"
+" The exact changes applied by a 2-URL merge can be previewed with svn's\n"
+" diff command, which is a good idea to verify if you do not have the\n"
+" luxury of a clean working copy to merge to. In this case:\n"
+"\n"
+" svn diff ^/trunk@500 ^/bar@HEAD\n"
+"\n"
+"\n"
+" The following applies to all types of merges:\n"
+"\n"
+" To prevent unnecessary merge conflicts, svn merge requires that\n"
+" TARGET_WCPATH is not a mixed-revision working copy. Running 'svn update'\n"
+" before starting a merge ensures that all items in the working copy are\n"
+" based on the same revision.\n"
+"\n"
+" If possible, you should have no local modifications in the merge's target\n"
+" working copy prior to the merge, to keep things simpler. It will be\n"
+" easier to revert the merge and to understand the branch's history.\n"
+"\n"
+" Switched sub-paths should also be avoided during merging, as they may\n"
+" cause incomplete merges and create subtree mergeinfo.\n"
+"\n"
+" For each merged item a line will be printed with characters reporting the\n"
+" action taken. These characters have the following meaning:\n"
+"\n"
+" A Added\n"
+" D Deleted\n"
+" U Updated\n"
+" C Conflict\n"
+" G Merged\n"
+" E Existed\n"
+" R Replaced\n"
+"\n"
+" Characters in the first column report about the item itself.\n"
+" Characters in the second column report about properties of the item.\n"
+" A 'C' in the third column indicates a tree conflict, while a 'C' in\n"
+" the first and second columns indicate textual conflicts in files\n"
+" and in property values, respectively.\n"
+"\n"
+" - Merge Tracking -\n"
+"\n"
+" Subversion uses the svn:mergeinfo property to track merge history. This\n"
+" property is considered at the start of a merge to determine what to merge\n"
+" and it is updated at the conclusion of the merge to describe the merge\n"
+" that took place. Mergeinfo is used only if the two sources are on the\n"
+" same line of history -- if the first source is an ancestor of the second,\n"
+" or vice-versa (i.e. if one has originally been created by copying the\n"
+" other). This is verified and enforced when using sync merges and\n"
+" reintegrate merges.\n"
+"\n"
+" The --ignore-ancestry option prevents merge tracking and thus ignores\n"
+" mergeinfo, neither considering it nor recording it.\n"
+"\n"
+" - Merging from foreign repositories -\n"
+"\n"
+" Subversion does support merging from foreign repositories.\n"
+" While all merge source URLs must point to the same repository, the merge\n"
+" target working copy may come from a different repository than the source.\n"
+" However, there are some caveats. Most notably, copies made in the\n"
+" merge source will be transformed into plain additions in the merge\n"
+" target. Also, merge-tracking is not supported for merges from foreign\n"
+" repositories.\n"),
+ {'r', 'c', 'N', opt_depth, 'q', opt_force, opt_dry_run, opt_merge_cmd,
+ opt_record_only, 'x', opt_ignore_ancestry, opt_accept, opt_reintegrate,
+ opt_allow_mixed_revisions, 'v'} },
+
+ { "mergeinfo", svn_cl__mergeinfo, {0}, N_
+ ("Display merge-related information.\n"
+ "usage: 1. mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
+ " 2. mergeinfo --show-revs=WHICH SOURCE[@REV] [TARGET[@REV]]\n"
+ "\n"
+ " 1. Summarize the history of merging between SOURCE and TARGET. The graph\n"
+ " shows, from left to right:\n"
+ " the youngest common ancestor of the branches;\n"
+ " the latest full merge in either direction, and thus the common base\n"
+ " that will be used for the next automatic merge;\n"
+ " the repository path and revision number of the tip of each branch.\n"
+ "\n"
+ " 2. Print the revision numbers on SOURCE that have been merged to TARGET\n"
+ " (with --show-revs=merged), or that have not been merged to TARGET\n"
+ " (with --show-revs=eligible). Print only revisions in which there was\n"
+ " at least one change in SOURCE.\n"
+ "\n"
+ " If --revision (-r) is provided, filter the displayed information to\n"
+ " show only that which is associated with the revisions within the\n"
+ " specified range. Revision numbers, dates, and the 'HEAD' keyword are\n"
+ " valid range values.\n"
+ "\n"
+ " SOURCE and TARGET are the source and target branch URLs, respectively.\n"
+ " (If a WC path is given, the corresponding base URL is used.) The default\n"
+ " TARGET is the current working directory ('.'). REV specifies the revision\n"
+ " to be considered the tip of the branch; the default for SOURCE is HEAD,\n"
+ " and the default for TARGET is HEAD for a URL or BASE for a WC path.\n"
+ "\n"
+ " The depth can be 'empty' or 'infinity'; the default is 'empty'.\n"),
+ {'r', 'R', opt_depth, opt_show_revs} },
+
+ { "mkdir", svn_cl__mkdir, {0}, N_
+ ("Create a new directory under version control.\n"
+ "usage: 1. mkdir PATH...\n"
+ " 2. mkdir URL...\n"
+ "\n"
+ " Create version controlled directories.\n"
+ "\n"
+ " 1. Each directory specified by a working copy PATH is created locally\n"
+ " and scheduled for addition upon the next commit.\n"
+ "\n"
+ " 2. Each directory specified by a URL is created in the repository via\n"
+ " an immediate commit.\n"
+ "\n"
+ " In both cases, all the intermediate directories must already exist,\n"
+ " unless the --parents option is given.\n"),
+ {'q', opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
+
+ { "move", svn_cl__move, {"mv", "rename", "ren"}, N_
+ ("Move (rename) an item in a working copy or repository.\n"
+ "usage: move SRC... DST\n"
+ "\n"
+ " SRC and DST can both be working copy (WC) paths or URLs:\n"
+ " WC -> WC: move an item in a working copy, as a local change to\n"
+ " be committed later (with or without further changes)\n"
+ " URL -> URL: move an item in the repository directly, immediately\n"
+ " creating a new revision in the repository\n"
+ " All the SRCs must be of the same type. When moving multiple sources,\n"
+ " they will be added as children of DST, which must be a directory.\n"
+ "\n"
+ " SRC and DST of WC -> WC moves must be committed in the same revision.\n"
+ " Furthermore, WC -> WC moves will refuse to move a mixed-revision subtree.\n"
+ " To avoid unnecessary conflicts, it is recommended to run 'svn update'\n"
+ " to update the subtree to a single revision before moving it.\n"
+ " The --allow-mixed-revisions option is provided for backward compatibility.\n"
+ "\n"
+ " The --revision option has no use and is deprecated.\n"),
+ {'r', 'q', opt_force, opt_parents, opt_allow_mixed_revisions,
+ SVN_CL__LOG_MSG_OPTIONS} },
+
+ { "patch", svn_cl__patch, {0}, N_
+ ("Apply a patch to a working copy.\n"
+ "usage: patch PATCHFILE [WCPATH]\n"
+ "\n"
+ " Apply a unidiff patch in PATCHFILE to the working copy WCPATH.\n"
+ " If WCPATH is omitted, '.' is assumed.\n"
+ "\n"
+ " A unidiff patch suitable for application to a working copy can be\n"
+ " produced with the 'svn diff' command or third-party diffing tools.\n"
+ " Any non-unidiff content of PATCHFILE is ignored, except for Subversion\n"
+ " property diffs as produced by 'svn diff'.\n"
+ "\n"
+ " Changes listed in the patch will either be applied or rejected.\n"
+ " If a change does not match at its exact line offset, it may be applied\n"
+ " earlier or later in the file if a match is found elsewhere for the\n"
+ " surrounding lines of context provided by the patch.\n"
+ " A change may also be applied with fuzz, which means that one\n"
+ " or more lines of context are ignored when matching the change.\n"
+ " If no matching context can be found for a change, the change conflicts\n"
+ " and will be written to a reject file with the extension .svnpatch.rej.\n"
+ "\n"
+ " For each patched file a line will be printed with characters reporting\n"
+ " the action taken. These characters have the following meaning:\n"
+ "\n"
+ " A Added\n"
+ " D Deleted\n"
+ " U Updated\n"
+ " C Conflict\n"
+ " G Merged (with local uncommitted changes)\n"
+ "\n"
+ " Changes applied with an offset or fuzz are reported on lines starting\n"
+ " with the '>' symbol. You should review such changes carefully.\n"
+ "\n"
+ " If the patch removes all content from a file, that file is scheduled\n"
+ " for deletion. If the patch creates a new file, that file is scheduled\n"
+ " for addition. Use 'svn revert' to undo deletions and additions you\n"
+ " do not agree with.\n"
+ "\n"
+ " Hint: If the patch file was created with Subversion, it will contain\n"
+ " the number of a revision N the patch will cleanly apply to\n"
+ " (look for lines like '--- foo/bar.txt (revision N)').\n"
+ " To avoid rejects, first update to the revision N using\n"
+ " 'svn update -r N', apply the patch, and then update back to the\n"
+ " HEAD revision. This way, conflicts can be resolved interactively.\n"
+ ),
+ {'q', opt_dry_run, opt_strip, opt_reverse_diff,
+ opt_ignore_whitespace} },
+
+ { "propdel", svn_cl__propdel, {"pdel", "pd"}, N_
+ ("Remove a property from files, dirs, or revisions.\n"
+ "usage: 1. propdel PROPNAME [PATH...]\n"
+ " 2. propdel PROPNAME --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Removes versioned props in working copy.\n"
+ " 2. Removes unversioned remote prop on repos revision.\n"
+ " TARGET only determines which repository to access.\n"),
+ {'q', 'R', opt_depth, 'r', opt_revprop, opt_changelist} },
+
+ { "propedit", svn_cl__propedit, {"pedit", "pe"}, N_
+ ("Edit a property with an external editor.\n"
+ "usage: 1. propedit PROPNAME TARGET...\n"
+ " 2. propedit PROPNAME --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Edits versioned prop in working copy or repository.\n"
+ " 2. Edits unversioned remote prop on repos revision.\n"
+ " TARGET only determines which repository to access.\n"
+ "\n"
+ " See 'svn help propset' for more on setting properties.\n"),
+ {'r', opt_revprop, SVN_CL__LOG_MSG_OPTIONS, opt_force} },
+
+ { "propget", svn_cl__propget, {"pget", "pg"}, N_
+ ("Print the value of a property on files, dirs, or revisions.\n"
+ "usage: 1. propget PROPNAME [TARGET[@REV]...]\n"
+ " 2. propget PROPNAME --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Prints versioned props. If specified, REV determines in which\n"
+ " revision the target is first looked up.\n"
+ " 2. Prints unversioned remote prop on repos revision.\n"
+ " TARGET only determines which repository to access.\n"
+ "\n"
+ " With --verbose, the target path and the property name are printed on\n"
+ " separate lines before each value, like 'svn proplist --verbose'.\n"
+ " Otherwise, if there is more than one TARGET or a depth other than\n"
+ " 'empty', the target path is printed on the same line before each value.\n"
+ "\n"
+ " By default, an extra newline is printed after the property value so that\n"
+ " the output looks pretty. With a single TARGET and depth 'empty', you can\n"
+ " use the --strict option to disable this (useful when redirecting a binary\n"
+ " property value to a file, for example).\n"),
+ {'v', 'R', opt_depth, 'r', opt_revprop, opt_strict, opt_xml,
+ opt_changelist, opt_show_inherited_props },
+ {{'v', N_("print path, name and value on separate lines")},
+ {opt_strict, N_("don't print an extra newline")}} },
+
+ { "proplist", svn_cl__proplist, {"plist", "pl"}, N_
+ ("List all properties on files, dirs, or revisions.\n"
+ "usage: 1. proplist [TARGET[@REV]...]\n"
+ " 2. proplist --revprop -r REV [TARGET]\n"
+ "\n"
+ " 1. Lists versioned props. If specified, REV determines in which\n"
+ " revision the target is first looked up.\n"
+ " 2. Lists unversioned remote props on repos revision.\n"
+ " TARGET only determines which repository to access.\n"
+ "\n"
+ " With --verbose, the property values are printed as well, like 'svn propget\n"
+ " --verbose'. With --quiet, the paths are not printed.\n"),
+ {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist,
+ opt_show_inherited_props },
+ {{'v', N_("print path, name and value on separate lines")},
+ {'q', N_("don't print the path")}} },
+
+ { "propset", svn_cl__propset, {"pset", "ps"}, N_
+ ("Set the value of a property on files, dirs, or revisions.\n"
+ "usage: 1. propset PROPNAME PROPVAL PATH...\n"
+ " 2. propset PROPNAME --revprop -r REV PROPVAL [TARGET]\n"
+ "\n"
+ " 1. Changes a versioned file or directory property in a working copy.\n"
+ " 2. Changes an unversioned property on a repository revision.\n"
+ " (TARGET only determines which repository to access.)\n"
+ "\n"
+ " The value may be provided with the --file option instead of PROPVAL.\n"
+ "\n"
+ " Property names starting with 'svn:' are reserved. Subversion recognizes\n"
+ " the following special versioned properties on a file:\n"
+ " svn:keywords - Keywords to be expanded. Valid keywords are:\n"
+ " URL, HeadURL - The URL for the head version of the file.\n"
+ " Author, LastChangedBy - The last person to modify the file.\n"
+ " Date, LastChangedDate - The date/time the file was last modified.\n"
+ " Rev, Revision, - The last revision the file changed.\n"
+ " LastChangedRevision\n"
+ " Id - A compressed summary of the previous four.\n"
+ " Header - Similar to Id but includes the full URL.\n"
+ "\n"
+ " Custom keywords can be defined with a format string separated from\n"
+ " the keyword name with '='. Valid format substitutions are:\n"
+ " %a - The author of the revision given by %r.\n"
+ " %b - The basename of the URL of the file.\n"
+ " %d - Short format of the date of the revision given by %r.\n"
+ " %D - Long format of the date of the revision given by %r.\n"
+ " %P - The file's path, relative to the repository root.\n"
+ " %r - The number of the revision which last changed the file.\n"
+ " %R - The URL to the root of the repository.\n"
+ " %u - The URL of the file.\n"
+ " %_ - A space (keyword definitions cannot contain a literal space).\n"
+ " %% - A literal '%'.\n"
+ " %H - Equivalent to %P%_%r%_%d%_%a.\n"
+ " %I - Equivalent to %b%_%r%_%d%_%a.\n"
+ " Example custom keyword definition: MyKeyword=%r%_%a%_%P\n"
+ " Once a custom keyword has been defined for a file, it can be used\n"
+ " within the file like any other keyword: $MyKeyword$\n"
+ "\n"
+ " svn:executable - If present, make the file executable. Use\n"
+ " 'svn propdel svn:executable PATH...' to clear.\n"
+ " svn:eol-style - One of 'native', 'LF', 'CR', 'CRLF'.\n"
+ " svn:mime-type - The mimetype of the file. Used to determine\n"
+ " whether to merge the file, and how to serve it from Apache.\n"
+ " A mimetype beginning with 'text/' (or an absent mimetype) is\n"
+ " treated as text. Anything else is treated as binary.\n"
+ " svn:needs-lock - If present, indicates that the file should be locked\n"
+ " before it is modified. Makes the working copy file read-only\n"
+ " when it is not locked. Use 'svn propdel svn:needs-lock PATH...'\n"
+ " to clear.\n"
+ "\n"
+ " Subversion recognizes the following special versioned properties on a\n"
+ " directory:\n"
+ " svn:ignore - A list of file glob patterns to ignore, one per line.\n"
+ " svn:global-ignores - Like svn:ignore, but inheritable.\n"
+ " svn:externals - A list of module specifiers, one per line, in the\n"
+ " following format similar to the syntax of 'svn checkout':\n"
+ " [-r REV] URL[@PEG] LOCALPATH\n"
+ " Example:\n"
+ " http://example.com/repos/zig foo/bar\n"
+ " The LOCALPATH is relative to the directory having this property.\n"
+ " To pin the external to a known revision, specify the optional REV:\n"
+ " -r25 http://example.com/repos/zig foo/bar\n"
+ " To unambiguously identify an element at a path which may have been\n"
+ " subsequently deleted or renamed, specify the optional PEG revision:\n"
+ " -r25 http://example.com/repos/zig@42 foo/bar\n"
+ " The URL may be a full URL or a relative URL starting with one of:\n"
+ " ../ to the parent directory of the extracted external\n"
+ " ^/ to the repository root\n"
+ " / to the server root\n"
+ " // to the URL scheme\n"
+ " Use of the following format is discouraged but is supported for\n"
+ " interoperability with Subversion 1.4 and earlier clients:\n"
+ " LOCALPATH [-r PEG] URL\n"
+ " The ambiguous format 'relative_path relative_path' is taken as\n"
+ " 'relative_url relative_path' with peg revision support.\n"
+ " Lines starting with a '#' character are ignored.\n"),
+ {'F', opt_encoding, 'q', 'r', opt_targets, 'R', opt_depth, opt_revprop,
+ opt_force, opt_changelist },
+ {{'F', N_("read property value from file ARG")}} },
+
+ { "relocate", svn_cl__relocate, {0}, N_
+ ("Relocate the working copy to point to a different repository root URL.\n"
+ "usage: 1. relocate FROM-PREFIX TO-PREFIX [PATH...]\n"
+ " 2. relocate TO-URL [PATH]\n"
+ "\n"
+ " Rewrite working copy URL metadata to reflect a syntactic change only.\n"
+ " This is used when a repository's root URL changes (such as a scheme\n"
+ " or hostname change) but your working copy still reflects the same\n"
+ " directory within the same repository.\n"
+ "\n"
+ " 1. FROM-PREFIX and TO-PREFIX are initial substrings of the working\n"
+ " copy's current and new URLs, respectively. (You may specify the\n"
+ " complete old and new URLs if you wish.) Use 'svn info' to determine\n"
+ " the current working copy URL.\n"
+ "\n"
+ " 2. TO-URL is the (complete) new repository URL to use for PATH.\n"
+ "\n"
+ " Examples:\n"
+ " svn relocate http:// svn:// project1 project2\n"
+ " svn relocate http://www.example.com/repo/project \\\n"
+ " svn://svn.example.com/repo/project\n"),
+ {opt_ignore_externals} },
+
+ { "resolve", svn_cl__resolve, {0}, N_
+ ("Resolve conflicts on working copy files or directories.\n"
+ "usage: resolve [PATH...]\n"
+ "\n"
+ " By default, perform interactive conflict resolution on PATH.\n"
+ " In this mode, the command is recursive by default (depth 'infinity').\n"
+ "\n"
+ " The --accept=ARG option prevents interactive prompting and forces\n"
+ " conflicts on PATH to be resolved in the manner specified by ARG.\n"
+ " In this mode, the command is not recursive by default (depth 'empty').\n"),
+ {opt_targets, 'R', opt_depth, 'q', opt_accept},
+ {{opt_accept, N_("specify automatic conflict resolution source\n"
+ " "
+ "('base', 'working', 'mine-conflict',\n"
+ " "
+ "'theirs-conflict', 'mine-full', 'theirs-full')")}} },
+
+ { "resolved", svn_cl__resolved, {0}, N_
+ ("Remove 'conflicted' state on working copy files or directories.\n"
+ "usage: resolved PATH...\n"
+ "\n"
+ " Note: this subcommand does not semantically resolve conflicts or\n"
+ " remove conflict markers; it merely removes the conflict-related\n"
+ " artifact files and allows PATH to be committed again. It has been\n"
+ " deprecated in favor of running 'svn resolve --accept working'.\n"),
+ {opt_targets, 'R', opt_depth, 'q'} },
+
+ { "revert", svn_cl__revert, {0}, N_
+ ("Restore pristine working copy state (undo local changes).\n"
+ "usage: revert PATH...\n"
+ "\n"
+ " Revert changes in the working copy at or within PATH, and remove\n"
+ " conflict markers as well, if any.\n"
+ "\n"
+ " This subcommand does not revert already committed changes.\n"
+ " For information about undoing already committed changes, search\n"
+ " the output of 'svn help merge' for 'undo'.\n"),
+ {opt_targets, 'R', opt_depth, 'q', opt_changelist} },
+
+ { "status", svn_cl__status, {"stat", "st"}, N_
+ ("Print the status of working copy files and directories.\n"
+ "usage: status [PATH...]\n"
+ "\n"
+ " With no args, print only locally modified items (no network access).\n"
+ " With -q, print only summary information about locally modified items.\n"
+ " With -u, add working revision and server out-of-date information.\n"
+ " With -v, print full revision information on every item.\n"
+ "\n"
+ " The first seven columns in the output are each one character wide:\n"
+ " First column: Says if item was added, deleted, or otherwise changed\n"
+ " ' ' no modifications\n"
+ " 'A' Added\n"
+ " 'C' Conflicted\n"
+ " 'D' Deleted\n"
+ " 'I' Ignored\n"
+ " 'M' Modified\n"
+ " 'R' Replaced\n"
+ " 'X' an unversioned directory created by an externals definition\n"
+ " '?' item is not under version control\n"
+ " '!' item is missing (removed by non-svn command) or incomplete\n"
+ " '~' versioned item obstructed by some item of a different kind\n"
+ " Second column: Modifications of a file's or directory's properties\n"
+ " ' ' no modifications\n"
+ " 'C' Conflicted\n"
+ " 'M' Modified\n"
+ " Third column: Whether the working copy directory is locked\n"
+ " ' ' not locked\n"
+ " 'L' locked\n"
+ " Fourth column: Scheduled commit will contain addition-with-history\n"
+ " ' ' no history scheduled with commit\n"
+ " '+' history scheduled with commit\n"
+ " Fifth column: Whether the item is switched or a file external\n"
+ " ' ' normal\n"
+ " 'S' the item has a Switched URL relative to the parent\n"
+ " 'X' a versioned file created by an eXternals definition\n"
+ " Sixth column: Repository lock token\n"
+ " (without -u)\n"
+ " ' ' no lock token\n"
+ " 'K' lock token present\n"
+ " (with -u)\n"
+ " ' ' not locked in repository, no lock token\n"
+ " 'K' locked in repository, lock toKen present\n"
+ " 'O' locked in repository, lock token in some Other working copy\n"
+ " 'T' locked in repository, lock token present but sTolen\n"
+ " 'B' not locked in repository, lock token present but Broken\n"
+ " Seventh column: Whether the item is the victim of a tree conflict\n"
+ " ' ' normal\n"
+ " 'C' tree-Conflicted\n"
+ " If the item is a tree conflict victim, an additional line is printed\n"
+ " after the item's status line, explaining the nature of the conflict.\n"
+ "\n"
+ " The out-of-date information appears in the ninth column (with -u):\n"
+ " '*' a newer revision exists on the server\n"
+ " ' ' the working copy is up to date\n"
+ "\n"
+ " Remaining fields are variable width and delimited by spaces:\n"
+ " The working revision (with -u or -v; '-' if the item is copied)\n"
+ " The last committed revision and last committed author (with -v)\n"
+ " The working copy path is always the final field, so it can\n"
+ " include spaces.\n"
+ "\n"
+ " The presence of a question mark ('?') where a working revision, last\n"
+ " committed revision, or last committed author was expected indicates\n"
+ " that the information is unknown or irrelevant given the state of the\n"
+ " item (for example, when the item is the result of a copy operation).\n"
+ " The question mark serves as a visual placeholder to facilitate parsing.\n"
+ "\n"
+ " Example output:\n"
+ " svn status wc\n"
+ " M wc/bar.c\n"
+ " A + wc/qax.c\n"
+ "\n"
+ " svn status -u wc\n"