summaryrefslogtreecommitdiff
path: root/subversion/svn
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2015-03-18 13:33:26 +0000
committer <>2015-07-08 14:41:01 +0000
commitbb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch)
tree98bae10dde41c746c51ae97ec4f879e330415aa7 /subversion/svn
parent239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff)
downloadsubversion-tarball-bb0ef45f7c46b0ae221b26265ef98a768c33f820.tar.gz
Imported from /home/lorry/working-area/delta_subversion-tarball/subversion-1.8.13.tar.gz.subversion-1.8.13
Diffstat (limited to 'subversion/svn')
-rw-r--r--subversion/svn/add-cmd.c5
-rw-r--r--subversion/svn/blame-cmd.c11
-rw-r--r--subversion/svn/cat-cmd.c4
-rw-r--r--subversion/svn/cl-conflicts.c454
-rw-r--r--subversion/svn/cl-conflicts.h (renamed from subversion/svn/tree-conflicts.h)24
-rw-r--r--subversion/svn/cl.h357
-rw-r--r--subversion/svn/client_errors.h2
-rw-r--r--subversion/svn/commit-cmd.c8
-rw-r--r--subversion/svn/conflict-callbacks.c1380
-rw-r--r--subversion/svn/copy-cmd.c52
-rw-r--r--subversion/svn/deprecated.c41
-rw-r--r--subversion/svn/diff-cmd.c161
-rw-r--r--subversion/svn/export-cmd.c12
-rw-r--r--subversion/svn/file-merge.c979
-rw-r--r--subversion/svn/help-cmd.c76
-rw-r--r--subversion/svn/import-cmd.c4
-rw-r--r--subversion/svn/info-cmd.c198
-rw-r--r--subversion/svn/list-cmd.c184
-rw-r--r--subversion/svn/log-cmd.c311
-rw-r--r--subversion/svn/merge-cmd.c311
-rw-r--r--subversion/svn/mergeinfo-cmd.c255
-rw-r--r--subversion/svn/move-cmd.c8
-rw-r--r--subversion/svn/notify.c413
-rw-r--r--subversion/svn/patch-cmd.c2
-rw-r--r--subversion/svn/propedit-cmd.c61
-rw-r--r--subversion/svn/propget-cmd.c281
-rw-r--r--subversion/svn/proplist-cmd.c133
-rw-r--r--subversion/svn/props.c364
-rw-r--r--subversion/svn/propset-cmd.c8
-rw-r--r--subversion/svn/relocate-cmd.c2
-rw-r--r--subversion/svn/resolve-cmd.c26
-rw-r--r--subversion/svn/resolved-cmd.c3
-rw-r--r--subversion/svn/schema/diff.rnc2
-rw-r--r--subversion/svn/schema/info.rnc20
-rw-r--r--subversion/svn/schema/status.rnc6
-rw-r--r--subversion/svn/status-cmd.c40
-rw-r--r--subversion/svn/status.c237
-rw-r--r--subversion/svn/svn.c (renamed from subversion/svn/main.c)1558
-rw-r--r--subversion/svn/switch-cmd.c28
-rw-r--r--subversion/svn/tree-conflicts.c196
-rw-r--r--subversion/svn/update-cmd.c33
-rw-r--r--subversion/svn/util.c514
42 files changed, 6022 insertions, 2742 deletions
diff --git a/subversion/svn/add-cmd.c b/subversion/svn/add-cmd.c
index 7b79ee1..44f73c7 100644
--- a/subversion/svn/add-cmd.c
+++ b/subversion/svn/add-cmd.c
@@ -75,10 +75,11 @@ svn_cl__add(apr_getopt_t *os,
svn_pool_clear(iterpool);
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
SVN_ERR(svn_cl__try
- (svn_client_add4(target,
+ (svn_client_add5(target,
opt_state->depth,
opt_state->force, opt_state->no_ignore,
- opt_state->parents, ctx, iterpool),
+ opt_state->no_autoprops, opt_state->parents,
+ ctx, iterpool),
errors, opt_state->quiet,
SVN_ERR_ENTRY_EXISTS,
SVN_ERR_WC_PATH_NOT_FOUND,
diff --git a/subversion/svn/blame-cmd.c b/subversion/svn/blame-cmd.c
index e198178..174a199 100644
--- a/subversion/svn/blame-cmd.c
+++ b/subversion/svn/blame-cmd.c
@@ -203,11 +203,11 @@ blame_receiver(void *baton,
we may need to adjust this. */
if (merged_revision < revision)
{
- SVN_ERR(svn_stream_printf(out, pool, "G "));
+ SVN_ERR(svn_stream_puts(out, "G "));
use_merged = TRUE;
}
else
- SVN_ERR(svn_stream_printf(out, pool, " "));
+ SVN_ERR(svn_stream_puts(out, " "));
}
if (use_merged)
@@ -283,7 +283,7 @@ svn_cl__blame(apr_getopt_t *os,
if (! opt_state->xml)
SVN_ERR(svn_stream_for_stdout(&bl.out, pool));
else
- bl.sbuf = svn_stringbuf_create("", pool);
+ bl.sbuf = svn_stringbuf_create_empty(pool);
bl.opt_state = opt_state;
@@ -375,10 +375,13 @@ svn_cl__blame(apr_getopt_t *os,
{
svn_error_clear(err);
SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Skipping binary file: '%s'\n"),
+ _("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)
{
diff --git a/subversion/svn/cat-cmd.c b/subversion/svn/cat-cmd.c
index 7e28a81..551420e 100644
--- a/subversion/svn/cat-cmd.c
+++ b/subversion/svn/cat-cmd.c
@@ -98,7 +98,7 @@ svn_cl__cat(apr_getopt_t *os,
if (status == SVN_ERR_ENTRY_NOT_FOUND ||
status == SVN_ERR_FS_NOT_FOUND)
- err = svn_error_quick_wrap(err,
+ err = svn_error_quick_wrap(err,
_("Could not cat all targets because "
"some targets don't exist"));
else if (status == SVN_ERR_UNVERSIONED_RESOURCE)
@@ -113,6 +113,6 @@ svn_cl__cat(apr_getopt_t *os,
return svn_error_trace(err);
}
-
+
return SVN_NO_ERROR;
}
diff --git a/subversion/svn/cl-conflicts.c b/subversion/svn/cl-conflicts.c
new file mode 100644
index 0000000..440c9d7
--- /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/tree-conflicts.h b/subversion/svn/cl-conflicts.h
index 7a01604..07591a0 100644
--- a/subversion/svn/tree-conflicts.h
+++ b/subversion/svn/cl-conflicts.h
@@ -1,5 +1,5 @@
/*
- * tree-conflicts.h: Tree conflicts.
+ * conflicts.h: Conflicts handling
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -23,8 +23,8 @@
-#ifndef SVN_TREE_CONFLICTS_H
-#define SVN_TREE_CONFLICTS_H
+#ifndef SVN_CONFLICTS_H
+#define SVN_CONFLICTS_H
/*** Includes. ***/
#include <apr_pools.h>
@@ -41,6 +41,18 @@ extern "C" {
/**
* 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.
@@ -52,11 +64,11 @@ svn_cl__get_human_readable_tree_conflict_description(
apr_pool_t *pool);
/**
- * Append to @a str an XML representation of the tree conflict data
+ * 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_tree_conflict_info_xml(
+svn_cl__append_conflict_info_xml(
svn_stringbuf_t *str,
const svn_wc_conflict_description2_t *conflict,
apr_pool_t *pool);
@@ -65,4 +77,4 @@ svn_cl__append_tree_conflict_info_xml(
}
#endif /* __cplusplus */
-#endif /* SVN_TREE_CONFLICTS_H */
+#endif /* SVN_CONFLICTS_H */
diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h
index 2d4d341..8a732c7 100644
--- a/subversion/svn/cl.h
+++ b/subversion/svn/cl.h
@@ -65,15 +65,11 @@ typedef enum svn_cl__accept_t
svn_cl__accept_working,
/* Resolve the conflicted hunks by choosing the corresponding text
- from the pre-conflict working copy file.
-
- Note: this is a placeholder, not actually implemented in 1.5. */
+ 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.
-
- Note: this is a placeholder, not actually implemented in 1.5. */
+ from the post-conflict base copy file. */
svn_cl__accept_theirs_conflict,
/* Resolve the conflict by taking the entire pre-conflict working
@@ -163,7 +159,6 @@ typedef struct svn_cl__opt_state_t
svn_boolean_t no_unlock;
const char *message; /* log message */
- const char *ancestor_path; /* ### todo: who sets this? */
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 */
@@ -183,15 +178,25 @@ typedef struct svn_cl__opt_state_t
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 */
- svn_boolean_t no_diff_deleted; /* do not show diffs for deleted files */
+ 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 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 *diff_cmd; /* the external diff command to use */
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 */
@@ -203,7 +208,6 @@ typedef struct svn_cl__opt_state_t
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 summarize; /* create a summary of a diff */
svn_boolean_t remove; /* deassociate a changelist */
apr_array_header_t *changelists; /* changelist filters */
const char *changelist; /* operate on this changelist
@@ -215,21 +219,22 @@ typedef struct svn_cl__opt_state_t
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_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_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 internal_diff; /* override diff_cmd in config file */
- svn_boolean_t use_git_diff_format; /* Use git's extended diff format */
- svn_boolean_t allow_mixed_rev; /* Allow operation on mixed-revision WC */
+ 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;
@@ -280,13 +285,13 @@ svn_opt_subcommand_t
svn_cl__upgrade;
-/* See definition in main.c for documentation. */
+/* See definition in svn.c for documentation. */
extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[];
-/* See definition in main.c for documentation. */
+/* See definition in svn.c for documentation. */
extern const int svn_cl__global_options[];
-/* See definition in main.c for documentation. */
+/* See definition in svn.c for documentation. */
extern const apr_getopt_option_t svn_cl__options[];
@@ -322,34 +327,57 @@ svn_cl__check_cancel(void *baton);
/* Various conflict-resolution callbacks. */
-typedef struct svn_cl__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;
-} svn_cl__conflict_baton_t;
-
-/* Create and return a conflict baton, allocated from POOL, with the values
- ACCEPT_WHICH, CONFIG, EDITOR_CMD and PB placed in the same-named fields
- of the baton, and its 'external_failed' field initialised to FALSE. */
-svn_cl__conflict_baton_t *
-svn_cl__conflict_baton_make(svn_cl__accept_t accept_which,
- apr_hash_t *config,
- const char *editor_cmd,
- svn_cmdline_prompt_baton_t *pb,
- apr_pool_t *pool);
-
-/* A conflict-resolution callback which prompts the user to choose
- one of the 3 fulltexts, edit the merged file on the spot, or just
- skip the conflict (to be resolved later).
- Implements @c svn_wc_conflict_resolver_func_t. */
-svn_error_t *
-svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description_t *desc,
- void *baton,
- apr_pool_t *pool);
+/* 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. ***/
@@ -381,6 +409,9 @@ svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
/* 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.
@@ -392,10 +423,16 @@ svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
Increment *TEXT_CONFLICTS, *PROP_CONFLICTS, or *TREE_CONFLICTS if
a conflict was encountered.
- */
+
+ Use TARGET_ABSPATH and TARGET_PATH to shorten PATH into something
+ relative to the target as necessary.
+*/
svn_error_t *
-svn_cl__print_status(const char *path,
+svn_cl__print_status(const char *target_abspath,
+ const char *target_path,
+ 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,
@@ -408,37 +445,19 @@ svn_cl__print_status(const char *path,
/* Print STATUS for PATH in XML to stdout. Use POOL for temporary
- allocations. */
+ allocations.
+
+ Use TARGET_ABSPATH and TARGET_PATH to shorten PATH into something
+ relative to the target as necessary.
+ */
svn_error_t *
-svn_cl__print_status_xml(const char *path,
+svn_cl__print_status_xml(const char *target_abspath,
+ const char *target_path,
+ const char *path,
const svn_client_status_t *status,
svn_client_ctx_t *ctx,
apr_pool_t *pool);
-
-/* Print to stdout a hash that maps property names (char *) to property
- values (svn_string_t *). The names are assumed to be in UTF-8 format;
- the values are either in UTF-8 (the special Subversion props) or
- plain binary values.
-
- If OUT is not NULL, then write to it rather than stdout.
-
- If NAMES_ONLY is true, print just names, else print names and
- values. */
-svn_error_t *
-svn_cl__print_prop_hash(svn_stream_t *out,
- apr_hash_t *prop_hash,
- svn_boolean_t names_only,
- apr_pool_t *pool);
-
-/* Same as svn_cl__print_prop_hash(), only output xml to *OUTSTR. If *OUTSTR is
- NULL, allocate it first from POOL, otherwise append to it. */
-svn_error_t *
-svn_cl__print_xml_prop_hash(svn_stringbuf_t **outstr,
- apr_hash_t *prop_hash,
- svn_boolean_t names_only,
- 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. */
@@ -470,60 +489,6 @@ svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
svn_client_ctx_t *ctx,
apr_pool_t *pool);
-/* Search for a text editor command in standard environment variables,
- and invoke it to edit CONTENTS (using a temporary file created in
- directory BASE_DIR). Return the new contents in *EDITED_CONTENTS,
- or set *EDITED_CONTENTS to NULL if no edit was performed.
-
- If EDITOR_CMD is not NULL, it is the name of the external editor
- command to use, overriding anything else that might determine the
- editor.
-
- If TMPFILE_LEFT is NULL, the temporary file will be destroyed.
- Else, the file will be left on disk, and its path returned in
- *TMPFILE_LEFT.
-
- CONFIG is a hash of svn_config_t * items keyed on a configuration
- category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL.
-
- If AS_TEXT is TRUE, recode CONTENTS and convert to native eol-style before
- editing and back again afterwards. In this case, ENCODING determines the
- encoding used during editing. If non-NULL, use the named encoding, else
- use the system encoding. If AS_TEXT is FALSE, don't do any translation.
- In that case, ENCODING is ignored.
-
- Use POOL for all allocations. Use PREFIX as the prefix for the
- temporary file used by the editor.
-
- If return error, *EDITED_CONTENTS is not touched. */
-svn_error_t *
-svn_cl__edit_string_externally(svn_string_t **edited_contents,
- const char **tmpfile_left,
- const char *editor_cmd,
- const char *base_dir,
- const svn_string_t *contents,
- const char *prefix,
- apr_hash_t *config,
- svn_boolean_t as_text,
- const char *encoding,
- apr_pool_t *pool);
-
-
-/* Search for a text editor command in standard environment variables,
- and invoke it to edit PATH. Use POOL for all allocations.
-
- If EDITOR_CMD is not NULL, it is the name of the external editor
- command to use, overriding anything else that might determine the
- editor.
-
- CONFIG is a hash of svn_config_t * items keyed on a configuration
- category (SVN_CONFIG_CATEGORY_CONFIG et al), and may be NULL. */
-svn_error_t *
-svn_cl__edit_file_externally(const char *path,
- const char *editor_cmd,
- apr_hash_t *config,
- 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
@@ -546,20 +511,30 @@ svn_cl__merge_file_externally(const char *base_path,
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.
- *
- * If don't want a summary line at the end of notifications, set
- * SUPPRESS_FINAL_LINE.
*/
svn_error_t *
svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
void **notify_baton_p,
- svn_boolean_t suppress_final_line,
+ svn_cl__conflict_stats_t *conflict_stats,
apr_pool_t *pool);
/* Make the notifier for use with BATON print the appropriate summary
@@ -595,14 +570,15 @@ svn_cl__check_externals_failed_notify_wrapper(void *baton,
const svn_wc_notify_t *n,
apr_pool_t *pool);
-/* Print conflict stats accumulated in NOTIFY_BATON.
+/* Print the conflict stats accumulated in BATON, which is the
+ * notifier baton from svn_cl__get_notifier().
* Return any error encountered during printing.
- * Do all allocations in POOL.*/
+ */
svn_error_t *
-svn_cl__print_conflict_stats(void *notify_baton, apr_pool_t *pool);
+svn_cl__notifier_print_conflict_stats(void *baton, apr_pool_t *scratch_pool);
-
-/*** Log message callback stuffs. ***/
+
+ /*** Log message callback stuffs. ***/
/* Allocate in POOL a baton for use with svn_cl__get_log_message().
@@ -709,6 +685,35 @@ 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
@@ -745,15 +750,6 @@ svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets_p,
svn_boolean_t keep_dest_origpath_on_truepath_collision,
apr_pool_t *pool);
-/* Return a string allocated in POOL that is a copy of STR but with each
- * line prefixed with INDENT. A line is all characters up to the first
- * CR-LF, LF-CR, CR or LF, or the end of STR if sooner. */
-const char *
-svn_cl__indent_string(const char *str,
- const char *indent,
- 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.
@@ -766,42 +762,19 @@ 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 copy of @a targets with peg revision
- * specifiers snipped off the end of each element.
- *
- * ### JAF TODO: This function is not good because it does not allow the
- * ### caller to detect if an invalid peg revision was specified.
- * ###
- * ### Callers should never have a need to silently *discard* all peg
- * ### revisions, even if they are doing this *after* saving any peg
- * ### revisions that might be of interest on certain arguments: I don't
- * ### think it can ever be correct to silently ignore a peg revision that
- * ### is specified, whether it makes semantic sense or not.
- * ###
- * ### Instead, callers should parse all the arguments and silently
- * ### ignore an *empty* peg revision part (just an "@", which can be
- * ### used to escape an earlier "@" in the argument) on any argument,
- * ### even an argument on which a peg revision does not make sense,
- * ### but should not silently ignore a non-empty peg when it does not
- * ### make sense.
- * ###
- * ### Something like:
- * ### For each (URL-like?) argument that doesn't accept a peg rev:
- * ### Parse into peg-rev and true-path parts;
- * ### If (peg rev != unspecified)
- * ### Error("This arg doesn't accept a peg rev.").
- * ### Use the true-path part.
+/* 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 peg
- * revisions to be specified on the command line so that users of
+ * 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.
*
- * If a peg revision is present but cannot be parsed, an error is thrown.
- * The user has likely forgotten to escape an '@' character in a filename.
- *
* It is safe to pass the address of @a targets as @a true_targets_p.
*
* Do all allocations in @a pool. */
@@ -833,6 +806,28 @@ svn_cl__local_style_skip_ancestor(const char *parent_path,
const char *path,
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 */
diff --git a/subversion/svn/client_errors.h b/subversion/svn/client_errors.h
index 5041e25..19f0bdf 100644
--- a/subversion/svn/client_errors.h
+++ b/subversion/svn/client_errors.h
@@ -43,6 +43,8 @@ extern "C" {
#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" },
diff --git a/subversion/svn/commit-cmd.c b/subversion/svn/commit-cmd.c
index e227f04..2d04c69 100644
--- a/subversion/svn/commit-cmd.c
+++ b/subversion/svn/commit-cmd.c
@@ -29,6 +29,7 @@
#include <apr_general.h>
+#include "svn_hash.h"
#include "svn_error.h"
#include "svn_error_codes.h"
#include "svn_wc.h"
@@ -136,8 +137,7 @@ svn_cl__commit(apr_getopt_t *os,
if (opt_state->depth == svn_depth_unknown)
opt_state->depth = svn_depth_infinity;
- cfg = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING);
+ 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,
@@ -166,11 +166,13 @@ svn_cl__commit(apr_getopt_t *os,
}
/* Commit. */
- err = svn_client_commit5(targets,
+ 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
diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c
index a158576..0f12413 100644
--- a/subversion/svn/conflict-callbacks.c
+++ b/subversion/svn/conflict-callbacks.c
@@ -24,36 +24,66 @@
#include <apr_xlate.h> /* for APR_LOCALE_CHARSET */
-#define APR_WANT_STDIO
#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_cl__conflict_baton_t *
-svn_cl__conflict_baton_make(svn_cl__accept_t accept_which,
- apr_hash_t *config,
- const char *editor_cmd,
- svn_cmdline_prompt_baton_t *pb,
- apr_pool_t *pool)
+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_cl__conflict_baton_t *b = apr_palloc(pool, sizeof(*b));
- b->accept_which = accept_which;
- b->config = config;
- b->editor_cmd = editor_cmd;
- b->external_failed = FALSE;
- b->pb = pb;
- return b;
+ 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
@@ -92,31 +122,63 @@ svn_cl__accept_from_word(const char *word)
}
-/* Print on stdout a diff between the 'base' and 'merged' files, if both of
- * those are available, else between 'their' and 'my' files, of DESC. */
+/* 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_description_t *desc,
+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 && desc->base_file)
+ if (desc->merged_file)
{
- /* Show the conflict markers to the user */
- path1 = desc->base_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 base file, but we can show the
+ /* There's no merged file, but we can show the
difference between mine and theirs. */
- path1 = desc->their_file;
- path2 = desc->my_file;
+ 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));
@@ -124,7 +186,7 @@ show_diff(const svn_wc_conflict_description_t *desc,
options, pool));
return svn_diff_file_output_unified3(output, diff,
path1, path2,
- NULL, NULL,
+ label1, label2,
APR_LOCALE_CHARSET,
NULL, FALSE,
pool);
@@ -134,7 +196,7 @@ show_diff(const svn_wc_conflict_description_t *desc,
/* 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_description_t *desc,
+show_conflicts(const svn_wc_conflict_description2_t *desc,
apr_pool_t *pool)
{
svn_diff_t *diff;
@@ -145,16 +207,16 @@ show_conflicts(const svn_wc_conflict_description_t *desc,
options->ignore_eol_style = TRUE;
SVN_ERR(svn_stream_for_stdout(&output, pool));
SVN_ERR(svn_diff_file_diff3_2(&diff,
- desc->base_file,
- desc->my_file,
- desc->their_file,
+ 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_file,
- desc->my_file,
- desc->their_file,
+ desc->base_abspath,
+ desc->my_abspath,
+ desc->their_abspath,
_("||||||| ORIGINAL"),
_("<<<<<<< MINE (select with 'mc')"),
_(">>>>>>> THEIRS (select with 'tc')"),
@@ -163,8 +225,85 @@ show_conflicts(const svn_wc_conflict_description_t *desc,
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));
-/* Run an external editor, passing it the 'merged' file in DESC, or, if the
+ 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.
@@ -175,27 +314,31 @@ show_conflicts(const svn_wc_conflict_description_t *desc,
* return that error. */
static svn_error_t *
open_editor(svn_boolean_t *performed_edit,
- const svn_wc_conflict_description_t *desc,
- svn_cl__conflict_baton_t *b,
+ const char *merged_file,
+ svn_cl__interactive_conflict_baton_t *b,
apr_pool_t *pool)
{
svn_error_t *err;
- if (desc->merged_file)
+ if (merged_file)
{
- err = svn_cl__edit_file_externally(desc->merged_file, b->editor_cmd,
- b->config, pool);
+ 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",
- err->message ? err->message :
+ 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",
- err->message ? err->message :
+ root_err->message ? root_err->message :
_("Error running editor.")));
svn_error_clear(err);
}
@@ -212,6 +355,34 @@ open_editor(svn_boolean_t *performed_edit,
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
@@ -223,27 +394,30 @@ open_editor(svn_boolean_t *performed_edit,
* return that error. */
static svn_error_t *
launch_resolver(svn_boolean_t *performed_edit,
- const svn_wc_conflict_description_t *desc,
- svn_cl__conflict_baton_t *b,
+ 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_file, desc->their_file,
- desc->my_file, desc->merged_file,
- desc->path, b->config, NULL, pool);
+ 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.\n")));
+ _("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.")));
+ _("Error running merge tool, "
+ "try '(m) merge' instead.")));
svn_error_clear(err);
}
else if (err)
@@ -255,21 +429,705 @@ launch_resolver(svn_boolean_t *performed_edit,
}
-/* Implement svn_wc_conflict_resolver_func_t; resolves based on
- --accept option if given, else by prompting. */
-svn_error_t *
-svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description_t *desc,
- void *baton,
- apr_pool_t *pool)
+/* 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_("mark 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 },
+ { "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 },
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "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[] =
+{
+ { "mf", N_("my version"), N_("accept my version of entire property (even "
+ "non-conflicts) [mine-full]"),
+ svn_wc_conflict_choose_mine_full },
+ { "tf", N_("their version"), N_("accept their version of entire property "
+ "(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_("mark resolved"), N_("accept edited version of property"),
+ svn_wc_conflict_choose_merged },
+ { "p", N_("postpone"), N_("mark the conflict to be resolved later"
+ " [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "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[] =
+{
+ { "r", N_("mark resolved"), N_("accept current working copy state"),
+ svn_wc_conflict_choose_merged },
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "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[] =
+{
+ { "mc", N_("apply update (recommended)"),
+ N_("apply update to the move destination"
+ " [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("discard update (breaks move)"), N_("discard update, mark "
+ "resolved, the move will "
+ "will become a copy"),
+ svn_wc_conflict_choose_merged },
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "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_edit_moved_away[] =
+{
+ { "mc", N_("apply update to move destination"),
+ N_("apply incoming update to move destination"
+ " [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "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[] =
+{
+ { "mc", N_("keep affected local moves"), N_("keep any local moves affected "
+ "by this deletion [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("mark resolved (breaks moves)"), N_("mark resolved, any affected "
+ "moves will become copies"),
+ svn_wc_conflict_choose_merged },
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "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[] =
+{
+ { "mc", N_("keep affected local moves"), N_("keep any moves affected by this "
+ "replacement [mine-conflict]"),
+ svn_wc_conflict_choose_mine_conflict },
+ { "r", N_("mark resolved (breaks moves)"), N_("mark resolved (any affected "
+ "moves will become copies)"),
+ svn_wc_conflict_choose_merged },
+ { "p", N_("postpone"), N_("resolve the conflict later [postpone]"),
+ svn_wc_conflict_choose_postpone },
+ { "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 (opt->choice == svn_wc_conflict_choose_merged
+ && ! knows_something)
+ {
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, iterpool,
+ _("Invalid option; use diff/edit/merge/launch "
+ "before choosing 'mark 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)
+ {
+ if (desc->action == svn_wc_conflict_action_edit)
+ tc_opts = tree_conflict_options_update_edit_moved_away;
+ else
+ 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;
+}
+
+/* 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__conflict_baton_t *b = baton;
+ svn_cl__interactive_conflict_baton_t *b = baton;
svn_error_t *err;
- apr_pool_t *subpool;
/* Start out assuming we're going to postpone the conflict. */
*result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
- NULL, pool);
+ NULL, result_pool);
switch (b->accept_which)
{
@@ -284,6 +1142,10 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
(*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:
@@ -307,11 +1169,12 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
return SVN_NO_ERROR;
}
- err = svn_cl__edit_file_externally(desc->merged_file,
- b->editor_cmd, b->config, pool);
+ 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, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("No editor found;"
" leaving all conflicts.")));
@@ -320,7 +1183,7 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
}
else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM))
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("Error running editor;"
" leaving all conflicts.")));
@@ -335,8 +1198,8 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
/* else, fall through to prompting. */
break;
case svn_cl__accept_launch:
- if (desc->base_file && desc->their_file
- && desc->my_file && desc->merged_file)
+ if (desc->base_abspath && desc->their_abspath
+ && desc->my_abspath && desc->merged_file)
{
svn_boolean_t remains_in_conflict;
@@ -346,17 +1209,17 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
return SVN_NO_ERROR;
}
- err = svn_cl__merge_file_externally(desc->base_file,
- desc->their_file,
- desc->my_file,
+ err = svn_cl__merge_file_externally(desc->base_abspath,
+ desc->their_abspath,
+ desc->my_abspath,
desc->merged_file,
- desc->path,
+ desc->local_abspath,
b->config,
&remains_in_conflict,
- pool);
+ scratch_pool);
if (err && err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL)
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("No merge tool found;"
" leaving all conflicts.")));
@@ -365,7 +1228,7 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
}
else if (err && err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)
{
- SVN_ERR(svn_cmdline_fprintf(stderr, pool, "%s\n",
+ SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n",
err->message ? err->message :
_("Error running merge tool;"
" leaving all conflicts.")));
@@ -387,371 +1250,50 @@ svn_cl__conflict_handler(svn_wc_conflict_result_t **result,
/* We're in interactive mode and either the user gave no --accept
option or the option did not apply; let's prompt. */
- subpool = svn_pool_create(pool);
/* Handle the most common cases, which is either:
Conflicting edits on a file's text, or
Conflicting edits on a property.
*/
- if (((desc->node_kind == svn_node_file)
+ if (((desc->kind == svn_wc_conflict_kind_text)
&& (desc->action == svn_wc_conflict_action_edit)
- && (desc->reason == svn_wc_conflict_reason_edited))
- || (desc->kind == svn_wc_conflict_kind_property))
- {
- const char *answer;
- char *prompt;
- 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;
-
- if (desc->kind == svn_wc_conflict_kind_text)
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Conflict discovered in '%s'.\n"),
- desc->path));
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Conflict for property '%s' discovered"
- " on '%s'.\n"),
- desc->property_name, desc->path));
-
- if ((!desc->my_file && desc->their_file)
- || (desc->my_file && !desc->their_file))
- {
- /* One agent wants to change the property, one wants to
- delete it. This is not something we can diff, so we
- just tell the user. */
- svn_stringbuf_t *myval = NULL, *theirval = NULL;
-
- if (desc->my_file)
- {
- SVN_ERR(svn_stringbuf_from_file2(&myval, desc->my_file,
- subpool));
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("They want to delete the property, "
- "you want to change the value to '%s'.\n"),
- myval->data));
- }
- else
- {
- SVN_ERR(svn_stringbuf_from_file2(&theirval, desc->their_file,
- subpool));
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("They want to change the property value to '%s', "
- "you want to delete the property.\n"),
- theirval->data));
- }
- }
- }
- else
- /* We don't recognize any other sort of conflict yet */
- return SVN_NO_ERROR;
-
- /* 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_file)
- || (!desc->base_file && desc->my_file && desc->their_file))
- diff_allowed = TRUE;
-
- while (TRUE)
- {
- svn_pool_clear(subpool);
-
- prompt = apr_pstrdup(subpool, _("Select: (p) postpone"));
-
- if (diff_allowed)
- {
- prompt = apr_pstrcat(subpool, prompt,
- _(", (df) diff-full, (e) edit"),
- (char *)NULL);
-
- if (knows_something)
- prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"),
- (char *)NULL);
-
- if (! desc->is_binary &&
- desc->kind != svn_wc_conflict_kind_property)
- prompt = apr_pstrcat(subpool, prompt,
- _(",\n (mc) mine-conflict, "
- "(tc) theirs-conflict"),
- (char *)NULL);
- }
- else
- {
- if (knows_something)
- prompt = apr_pstrcat(subpool, prompt, _(", (r) resolved"),
- (char *)NULL);
- prompt = apr_pstrcat(subpool, prompt,
- _(",\n "
- "(mf) mine-full, (tf) theirs-full"),
- (char *)NULL);
- }
-
- prompt = apr_pstrcat(subpool, prompt, ",\n ", (char *)NULL);
- prompt = apr_pstrcat(subpool, prompt,
- _("(s) show all options: "),
- (char *)NULL);
+ && (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));
+ else if (desc->kind == svn_wc_conflict_kind_tree)
+ SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool));
- SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
-
- if (strcmp(answer, "s") == 0)
- {
- /* These are used in svn_cl__accept_from_word(). */
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("\n"
- " (e) edit - change merged file in an editor\n"
- " (df) diff-full - show all changes made to merged "
- "file\n"
- " (r) resolved - accept merged version of file\n"
- "\n"
- " (dc) display-conflict - show all conflicts "
- "(ignoring merged version)\n"
- " (mc) mine-conflict - accept my version for all "
- "conflicts (same)\n"
- " (tc) theirs-conflict - accept their version for all "
- "conflicts (same)\n"
- "\n"
- " (mf) mine-full - accept my version of entire file "
- "(even non-conflicts)\n"
- " (tf) theirs-full - accept their version of entire "
- "file (same)\n"
- "\n"
- " (p) postpone - mark the conflict to be "
- "resolved later\n"
- " (l) launch - launch external tool to "
- "resolve conflict\n"
- " (s) show all - show this list\n\n")));
- }
- else if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
- {
- /* Do nothing, let file be marked conflicted. */
- (*result)->choice = svn_wc_conflict_choose_postpone;
- break;
- }
- else if (strcmp(answer, "mc") == 0 || strcmp(answer, "X-)") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts in a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts for "
- "properties.\n\n")));
- continue;
- }
-
- (*result)->choice = svn_wc_conflict_choose_mine_conflict;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "tc") == 0 || strcmp(answer, "X-(") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts in a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot choose "
- "based on conflicts for "
- "properties.\n\n")));
- continue;
- }
- (*result)->choice = svn_wc_conflict_choose_theirs_conflict;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_mine_full;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_theirs_full;
- if (performed_edit)
- (*result)->save_merged = TRUE;
- break;
- }
- else if (strcmp(answer, "dc") == 0)
- {
- if (desc->is_binary)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "display conflicts for a "
- "binary file.\n\n")));
- continue;
- }
- else if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "display conflicts for "
- "properties.\n\n")));
- continue;
- }
- else if (! (desc->my_file && desc->base_file && desc->their_file))
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; original "
- "files not available.\n\n")));
- continue;
- }
- SVN_ERR(show_conflicts(desc, subpool));
- knows_something = TRUE;
- }
- else if (strcmp(answer, "df") == 0)
- {
- if (! diff_allowed)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; there's no "
- "merged version to diff.\n\n")));
- continue;
- }
-
- SVN_ERR(show_diff(desc, subpool));
- knows_something = TRUE;
- }
- else if (strcmp(answer, "e") == 0 || strcmp(answer, ":-E") == 0)
- {
- SVN_ERR(open_editor(&performed_edit, desc, b, subpool));
- if (performed_edit)
- knows_something = TRUE;
- }
- else if (strcmp(answer, "l") == 0 || strcmp(answer, ":-l") == 0)
- {
- if (desc->kind == svn_wc_conflict_kind_property)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option; cannot "
- "resolve property conflicts "
- "with an external merge tool."
- "\n\n")));
- continue;
- }
- if (desc->base_file && desc->their_file && desc->my_file
- && desc->merged_file)
- {
- SVN_ERR(launch_resolver(&performed_edit, desc, b, subpool));
- if (performed_edit)
- knows_something = TRUE;
- }
- else
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option.\n\n")));
- }
- else if (strcmp(answer, "r") == 0)
- {
- /* 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 (knows_something)
- {
- (*result)->choice = svn_wc_conflict_choose_merged;
- break;
- }
- else
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _("Invalid option.\n\n")));
- }
- }
+ else /* other types of conflicts -- do nothing about them. */
+ {
+ (*result)->choice = svn_wc_conflict_choose_postpone;
}
- /*
- 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
+ return SVN_NO_ERROR;
+}
- choose_theirs destroy file, schedule-delete, revert add,
- add new item. add new item. rm file,
- add new item
+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;
- postpone [ bail out ]
+ SVN_ERR(conflict_func_interactive(result, desc, baton,
+ result_pool, scratch_pool));
- */
- else if ((desc->action == svn_wc_conflict_action_add)
- && (desc->reason == svn_wc_conflict_reason_obstructed))
+ /* If we are resolving a conflict, adjust the summary of conflicts. */
+ if ((*result)->choice != svn_wc_conflict_choose_postpone)
{
- const char *answer;
- const char *prompt;
-
- SVN_ERR(svn_cmdline_fprintf(
- stderr, subpool,
- _("Conflict discovered when trying to add '%s'.\n"
- "An object of the same name already exists.\n"),
- desc->path));
- prompt = _("Select: (p) postpone, (mf) mine-full, "
- "(tf) theirs-full, (h) help:");
-
- while (1)
- {
- svn_pool_clear(subpool);
-
- SVN_ERR(svn_cmdline_prompt_user2(&answer, prompt, b->pb, subpool));
+ const char *local_path
+ = svn_cl__local_style_skip_ancestor(
+ b->path_prefix, desc->local_abspath, scratch_pool);
- if (strcmp(answer, "h") == 0 || strcmp(answer, "?") == 0)
- {
- SVN_ERR(svn_cmdline_fprintf(stderr, subpool,
- _(" (p) postpone - resolve the conflict later\n"
- " (mf) mine-full - accept pre-existing item "
- "(ignore upstream addition)\n"
- " (tf) theirs-full - accept incoming item "
- "(overwrite pre-existing item)\n"
- " (h) help - show this help\n\n")));
- }
- if (strcmp(answer, "p") == 0 || strcmp(answer, ":-P") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_postpone;
- break;
- }
- if (strcmp(answer, "mf") == 0 || strcmp(answer, ":-)") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_mine_full;
- break;
- }
- if (strcmp(answer, "tf") == 0 || strcmp(answer, ":-(") == 0)
- {
- (*result)->choice = svn_wc_conflict_choose_theirs_full;
- break;
- }
- }
+ svn_cl__conflict_stats_resolved(b->conflict_stats, local_path,
+ desc->kind);
}
-
- else /* other types of conflicts -- do nothing about them. */
- {
- (*result)->choice = svn_wc_conflict_choose_postpone;
- }
-
- svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
diff --git a/subversion/svn/copy-cmd.c b/subversion/svn/copy-cmd.c
index 99b8703..e6fbd4b 100644
--- a/subversion/svn/copy-cmd.c
+++ b/subversion/svn/copy-cmd.c
@@ -68,7 +68,42 @@ svn_cl__copy(apr_getopt_t *os,
svn_opt_revision_t *peg_revision = apr_palloc(pool,
sizeof(*peg_revision));
- SVN_ERR(svn_opt_parse_path(peg_revision, &src, target, pool));
+ 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;
@@ -76,7 +111,18 @@ svn_cl__copy(apr_getopt_t *os,
APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = source;
}
- SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
+ /* 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;
@@ -84,8 +130,6 @@ svn_cl__copy(apr_getopt_t *os,
error if they are not.) */
src_path = APR_ARRAY_IDX(targets, 0, const char *);
srcs_are_urls = svn_path_is_url(src_path);
- dst_path = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *);
- apr_array_pop(targets);
dst_is_url = svn_path_is_url(dst_path);
if ((! srcs_are_urls) && (! dst_is_url))
diff --git a/subversion/svn/deprecated.c b/subversion/svn/deprecated.c
new file mode 100644
index 0000000..6115573
--- /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
index 3c62523..2cbd202 100644
--- a/subversion/svn/diff-cmd.c
+++ b/subversion/svn/diff-cmd.c
@@ -77,6 +77,12 @@ kind_to_word(svn_client_diff_summarize_kind_t kind)
}
}
+/* 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
@@ -86,10 +92,11 @@ 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 = *(const char **)baton;
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ 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. */
@@ -125,7 +132,8 @@ summarize_regular(const svn_client_diff_summarize_t *summary,
void *baton,
apr_pool_t *pool)
{
- const char *path = *(const char **)baton;
+ 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. */
@@ -166,12 +174,17 @@ svn_cl__diff(apr_getopt_t *os,
svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
apr_array_header_t *options;
apr_array_header_t *targets;
- apr_file_t *outfile, *errfile;
- apr_status_t status;
+ 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);
@@ -180,26 +193,24 @@ svn_cl__diff(apr_getopt_t *os,
else
options = NULL;
- /* Get an apr_file_t representing stdout and stderr, which is where
+ /* Get streams representing stdout and stderr, which is where
we'll have the external 'diff' program print to. */
- if ((status = apr_file_open_stdout(&outfile, pool)))
- return svn_error_wrap_apr(status, _("Can't open stdout"));
- if ((status = apr_file_open_stderr(&errfile, pool)))
- return svn_error_wrap_apr(status, _("Can't open stderr"));
+ 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->summarize)
+ 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("", 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));
}
@@ -210,12 +221,13 @@ svn_cl__diff(apr_getopt_t *os,
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 *))
+ && (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)
{
- /* The 'svn diff OLD_URL[@OLDREV] NEW_URL[@NEWREV]' case matches. */
+ /* 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 *),
@@ -225,10 +237,16 @@ svn_cl__diff(apr_getopt_t *os,
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_opt_revision_head;
+ 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_opt_revision_head;
+ 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)
{
@@ -241,9 +259,8 @@ svn_cl__diff(apr_getopt_t *os,
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
- : APR_ARRAY_IDX(tmp, 0,
- const char *));
+ ? opt_state->new_target
+ : opt_state->old_target);
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp,
ctx, FALSE, pool));
@@ -264,10 +281,14 @@ svn_cl__diff(apr_getopt_t *os,
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 : svn_opt_revision_base;
-
+ ? 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;
@@ -292,7 +313,10 @@ svn_cl__diff(apr_getopt_t *os,
old_target = "";
new_target = "";
- SVN_ERR(svn_cl__assert_homogeneous_target_type(targets));
+ 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 *));
@@ -347,34 +371,41 @@ svn_cl__diff(apr_getopt_t *os,
else
target2 = svn_dirent_join(new_target, path, iterpool);
- if (opt_state->summarize)
- SVN_ERR(svn_client_diff_summarize2
- (target1,
- &opt_state->start_revision,
- target2,
- &opt_state->end_revision,
- opt_state->depth,
- ! opt_state->notice_ancestry,
- opt_state->changelists,
- summarize_func, &target1,
- ctx, 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_diff5
- (options,
+ SVN_ERR(svn_client_diff6(
+ options,
target1,
&(opt_state->start_revision),
target2,
&(opt_state->end_revision),
NULL,
opt_state->depth,
- ! opt_state->notice_ancestry,
- opt_state->no_diff_deleted,
- opt_state->show_copies_as_adds,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->diff.no_diff_added,
+ opt_state->diff.no_diff_deleted,
+ show_copies_as_adds,
opt_state->force,
- opt_state->use_git_diff_format,
+ ignore_properties,
+ opt_state->diff.properties_only,
+ opt_state->diff.use_git_diff_format,
svn_cmdline_output_encoding(pool),
- outfile,
- errfile,
+ outstream,
+ errstream,
opt_state->changelists,
ctx, iterpool));
}
@@ -392,34 +423,40 @@ svn_cl__diff(apr_getopt_t *os,
peg_revision.kind = svn_path_is_url(path)
? svn_opt_revision_head : svn_opt_revision_working;
- if (opt_state->summarize)
- SVN_ERR(svn_client_diff_summarize_peg2
- (truepath,
- &peg_revision,
- &opt_state->start_revision,
- &opt_state->end_revision,
- opt_state->depth,
- ! opt_state->notice_ancestry,
- opt_state->changelists,
- summarize_func, &truepath,
- ctx, iterpool));
+ 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_peg5
- (options,
+ SVN_ERR(svn_client_diff_peg6(
+ options,
truepath,
&peg_revision,
&opt_state->start_revision,
&opt_state->end_revision,
NULL,
opt_state->depth,
- ! opt_state->notice_ancestry,
- opt_state->no_diff_deleted,
- opt_state->show_copies_as_adds,
+ ! opt_state->diff.notice_ancestry,
+ opt_state->diff.no_diff_added,
+ opt_state->diff.no_diff_deleted,
+ show_copies_as_adds,
opt_state->force,
- opt_state->use_git_diff_format,
+ ignore_properties,
+ opt_state->diff.properties_only,
+ opt_state->diff.use_git_diff_format,
svn_cmdline_output_encoding(pool),
- outfile,
- errfile,
+ outstream,
+ errstream,
opt_state->changelists,
ctx, iterpool));
}
@@ -427,7 +464,7 @@ svn_cl__diff(apr_getopt_t *os,
if (opt_state->xml)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ 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));
diff --git a/subversion/svn/export-cmd.c b/subversion/svn/export-cmd.c
index c4c4aa4..75b6723 100644
--- a/subversion/svn/export-cmd.c
+++ b/subversion/svn/export-cmd.c
@@ -114,9 +114,15 @@ svn_cl__export(apr_getopt_t *os,
"the directory or use --force to overwrite"));
if (nwb.had_externals_error)
- return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL,
- _("Failure occurred processing one or more "
- "externals definitions"));
+ {
+ 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 0000000..c64f577
--- /dev/null
+++ b/subversion/svn/file-merge.c
@@ -0,0 +1,979 @@
+/*
+ * 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"
+ " (12) their version first, then yours,\n"
+ " (21) your version first, then theirs,\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;
+ }
+ if (strcmp(answer, "12") == 0)
+ {
+ *merged_chunk = apr_array_make(result_pool,
+ chunk1->nelts + chunk2->nelts,
+ sizeof(svn_stringbuf_t *));
+ apr_array_cat(*merged_chunk, chunk1);
+ apr_array_cat(*merged_chunk, chunk2);
+ break;
+ }
+ if (strcmp(answer, "21") == 0)
+ {
+ *merged_chunk = apr_array_make(result_pool,
+ chunk1->nelts + chunk2->nelts,
+ sizeof(svn_stringbuf_t *));
+ apr_array_cat(*merged_chunk, chunk2);
+ apr_array_cat(*merged_chunk, chunk1);
+ 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
index cd0373a..93fecc3 100644
--- a/subversion/svn/help-cmd.c
+++ b/subversion/svn/help-cmd.c
@@ -27,7 +27,9 @@
/*** Includes. ***/
+#include "svn_hash.h"
#include "svn_string.h"
+#include "svn_config.h"
#include "svn_error.h"
#include "svn_version.h"
#include "cl.h"
@@ -43,7 +45,8 @@ svn_cl__help(apr_getopt_t *os,
void *baton,
apr_pool_t *pool)
{
- svn_cl__opt_state_t *opt_state;
+ 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[] =
@@ -69,20 +72,77 @@ svn_cl__help(apr_getopt_t *os,
const char *ra_desc_start
= _("The following repository access (RA) modules are available:\n\n");
- svn_stringbuf_t *version_footer;
-
if (baton)
- opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
- else
- opt_state = NULL;
+ {
+ 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;
+ }
- version_footer = svn_stringbuf_create(ra_desc_start, pool);
+ 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_help3(os,
+ 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,
diff --git a/subversion/svn/import-cmd.c b/subversion/svn/import-cmd.c
index f795092..6fe5af6 100644
--- a/subversion/svn/import-cmd.c
+++ b/subversion/svn/import-cmd.c
@@ -114,12 +114,14 @@ svn_cl__import(apr_getopt_t *os,
SVN_ERR(svn_cl__cleanup_log_msg
(ctx->log_msg_baton3,
- svn_client_import4(path,
+ 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,
diff --git a/subversion/svn/info-cmd.c b/subversion/svn/info-cmd.c
index 14d0db6..56833f6 100644
--- a/subversion/svn/info-cmd.c
+++ b/subversion/svn/info-cmd.c
@@ -40,7 +40,7 @@
#include "cl.h"
#include "svn_private_config.h"
-#include "tree-conflicts.h"
+#include "cl-conflicts.h"
/*** Code. ***/
@@ -85,7 +85,7 @@ print_info_xml(void *baton,
const svn_client_info2_t *info,
apr_pool_t *pool)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
const char *rev_str;
const char *path_prefix = baton;
@@ -102,8 +102,22 @@ print_info_xml(void *baton,
"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>" */
@@ -172,6 +186,35 @@ print_info_xml(void *baton,
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");
}
@@ -197,47 +240,7 @@ print_info_xml(void *baton,
APR_ARRAY_IDX(info->wc_info->conflicts, i,
const svn_wc_conflict_description2_t *);
- switch (conflict->kind)
- {
- case svn_wc_conflict_kind_text:
- /* "<conflict>" */
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "conflict",
- NULL);
-
- /* "<prev-base-file> xx </prev-base-file>" */
- svn_cl__xml_tagged_cdata(&sb, pool, "prev-base-file",
- conflict->base_abspath);
-
- /* "<prev-wc-file> xx </prev-wc-file>" */
- svn_cl__xml_tagged_cdata(&sb, pool, "prev-wc-file",
- conflict->my_abspath);
-
- /* "<cur-base-file> xx </cur-base-file>" */
- svn_cl__xml_tagged_cdata(&sb, pool, "cur-base-file",
- conflict->their_abspath);
-
- /* "</conflict>" */
- svn_xml_make_close_tag(&sb, pool, "conflict");
- break;
-
- case svn_wc_conflict_kind_property:
- /* "<conflict>" */
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "conflict",
- NULL);
-
- /* "<prop-file> xx </prop-file>" */
- svn_cl__xml_tagged_cdata(&sb, pool, "prop-file",
- conflict->their_abspath);
-
- /* "</conflict>" */
- svn_xml_make_close_tag(&sb, pool, "conflict");
- break;
-
- case svn_wc_conflict_kind_tree:
- SVN_ERR(svn_cl__append_tree_conflict_info_xml(sb, conflict,
- pool));
- break;
- }
+ SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool));
}
}
@@ -279,6 +282,13 @@ print_info(void *baton,
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));
@@ -376,6 +386,31 @@ print_info(void *baton,
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)
@@ -403,16 +438,15 @@ print_info(void *baton,
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;
- const char *src_left_version;
- const char *src_right_version;
+ 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)
{
@@ -440,10 +474,12 @@ print_info(void *baton,
break;
case svn_wc_conflict_kind_property:
- SVN_ERR(svn_cmdline_printf(pool,
- _("Conflict Properties File: %s\n"),
- svn_dirent_local_style(conflict->their_abspath,
- pool)));
+ 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:
@@ -451,33 +487,45 @@ print_info(void *baton,
svn_cl__get_human_readable_tree_conflict_description(
&desc, conflict, pool));
- 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);
-
SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n",
_("Tree conflict"), desc));
-
- 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));
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));
+ }
}
}
diff --git a/subversion/svn/list-cmd.c b/subversion/svn/list-cmd.c
index 7bc5183..efe4279 100644
--- a/subversion/svn/list-cmd.c
+++ b/subversion/svn/list-cmd.c
@@ -42,9 +42,14 @@
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_func_t API, printing a single
+/* 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,
@@ -52,10 +57,22 @@ print_dirent(void *baton,
const svn_dirent_t *dirent,
const svn_lock_t *lock,
const char *abs_path,
- apr_pool_t *pool)
+ 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));
@@ -63,7 +80,7 @@ print_dirent(void *baton,
if (strcmp(path, "") == 0)
{
if (dirent->kind == svn_node_file)
- entryname = svn_dirent_basename(abs_path, pool);
+ entryname = svn_dirent_basename(abs_path, scratch_pool);
else if (pb->verbose)
entryname = ".";
else
@@ -73,6 +90,24 @@ print_dirent(void *baton,
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();
@@ -90,12 +125,12 @@ print_dirent(void *baton,
&& apr_time_sec(dirent->time - now) < (365 * 86400 / 2))
{
apr_err = apr_strftime(timestr, &size, sizeof(timestr),
- _("%b %d %H:%M"), &exp_time);
+ time_format_long, &exp_time);
}
else
{
apr_err = apr_strftime(timestr, &size, sizeof(timestr),
- _("%b %d %Y"), &exp_time);
+ time_format_short, &exp_time);
}
/* if that failed, just zero out the string and print nothing */
@@ -103,12 +138,13 @@ print_dirent(void *baton,
timestr[0] = '\0';
/* we need it in UTF-8. */
- SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, pool));
+ SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool));
- sizestr = apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, dirent->size);
+ sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT,
+ dirent->size);
return svn_cmdline_printf
- (pool, "%7ld %-8.8s %c %10s %12s %s%s\n",
+ (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n",
dirent->created_rev,
dirent->last_author ? dirent->last_author : " ? ",
lock ? 'O' : ' ',
@@ -119,14 +155,14 @@ print_dirent(void *baton,
}
else
{
- return svn_cmdline_printf(pool, "%s%s\n", entryname,
+ return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname,
(dirent->kind == svn_node_dir)
? "/" : "");
}
}
-/* This implements the svn_client_list_func_t API, printing a single dirent
+/* 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,
@@ -134,18 +170,21 @@ print_dirent_xml(void *baton,
const svn_dirent_t *dirent,
const svn_lock_t *lock,
const char *abs_path,
- apr_pool_t *pool)
+ 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_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, pool);
- else if (pb->verbose)
- entryname = ".";
+ 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;
@@ -156,48 +195,72 @@ print_dirent_xml(void *baton,
if (pb->ctx->cancel_func)
SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
- sb = svn_stringbuf_create("", pool);
+ 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);
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry",
+ 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, pool, "name", entryname);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname);
if (dirent->kind == svn_node_file)
{
svn_cl__xml_tagged_cdata
- (&sb, pool, "size",
- apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, dirent->size));
+ (&sb, scratch_pool, "size",
+ apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size));
}
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "commit",
+ svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit",
"revision",
- apr_psprintf(pool, "%ld", dirent->created_rev),
+ apr_psprintf(scratch_pool, "%ld", dirent->created_rev),
NULL);
- svn_cl__xml_tagged_cdata(&sb, pool, "author", dirent->last_author);
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author);
if (dirent->time)
- svn_cl__xml_tagged_cdata(&sb, pool, "date",
- svn_time_to_cstring(dirent->time, pool));
- svn_xml_make_close_tag(&sb, pool, "commit");
+ 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, pool, svn_xml_normal, "lock", NULL);
- svn_cl__xml_tagged_cdata(&sb, pool, "token", lock->token);
- svn_cl__xml_tagged_cdata(&sb, pool, "owner", lock->owner);
- svn_cl__xml_tagged_cdata(&sb, pool, "comment", lock->comment);
- svn_cl__xml_tagged_cdata(&sb, pool, "created",
+ 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,
- pool));
+ scratch_pool));
if (lock->expiration_date != 0)
- svn_cl__xml_tagged_cdata(&sb, pool, "expires",
+ svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
svn_time_to_cstring
- (lock->expiration_date, pool));
- svn_xml_make_close_tag(&sb, pool, "lock");
+ (lock->expiration_date, scratch_pool));
+ svn_xml_make_close_tag(&sb, scratch_pool, "lock");
}
- svn_xml_make_close_tag(&sb, pool, "entry");
+ svn_xml_make_close_tag(&sb, scratch_pool, "entry");
return svn_cl__error_checked_fputs(sb->data, stdout);
}
@@ -218,6 +281,8 @@ svn_cl__list(apr_getopt_t *os,
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,
@@ -259,6 +324,15 @@ svn_cl__list(apr_getopt_t *os,
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++)
{
@@ -266,6 +340,12 @@ svn_cl__list(apr_getopt_t *os,
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));
@@ -276,18 +356,19 @@ svn_cl__list(apr_getopt_t *os,
if (opt_state->xml)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ 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_list2(truepath, &peg_revision,
+ 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);
@@ -308,7 +389,15 @@ svn_cl__list(apr_getopt_t *os,
if (opt_state->xml)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ 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));
}
@@ -316,13 +405,20 @@ svn_cl__list(apr_getopt_t *os,
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)
- return svn_error_create(
- SVN_ERR_ILLEGAL_TARGET, NULL,
- _("Could not list all targets because some targets don't exist"));
- else
- return SVN_NO_ERROR;
+ 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/log-cmd.c b/subversion/svn/log-cmd.c
index 4057b0a..af57cf4 100644
--- a/subversion/svn/log-cmd.c
+++ b/subversion/svn/log-cmd.c
@@ -21,9 +21,7 @@
* ====================================================================
*/
-#define APR_WANT_STRFUNC
-#define APR_WANT_STDIO
-#include <apr_want.h>
+#include <apr_fnmatch.h>
#include "svn_client.h"
#include "svn_compat.h"
@@ -38,6 +36,8 @@
#include "svn_props.h"
#include "svn_pools.h"
+#include "private/svn_cmdline_private.h"
+
#include "cl.h"
#include "svn_private_config.h"
@@ -70,6 +70,10 @@ struct log_receiver_baton
/* 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;
};
@@ -80,6 +84,168 @@ struct log_receiver_baton
"------------------------------------------------------------------------\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.
*
@@ -196,6 +362,16 @@ log_entry_receiver(void *baton,
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));
@@ -228,8 +404,7 @@ log_entry_receiver(void *baton,
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
- = apr_hash_get(log_entry->changed_paths2, item->key, item->klen);
+ svn_log_changed_path2_t *log_item = item->value;
const char *copy_data = "";
if (lb->ctx->cancel_func)
@@ -275,56 +450,27 @@ log_entry_receiver(void *baton,
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)
{
- apr_file_t *outfile;
- apr_file_t *errfile;
- apr_array_header_t *diff_options;
- apr_status_t status;
- svn_opt_revision_t start_revision;
- svn_opt_revision_t end_revision;
-
- if ((status = apr_file_open_stdout(&outfile, pool)))
- return svn_error_wrap_apr(status, _("Can't open stdout"));
- if ((status = apr_file_open_stderr(&errfile, pool)))
- return svn_error_wrap_apr(status, _("Can't open stderr"));
-
- /* Fall back to "" to get options initialized either way. */
- if (lb->diff_extensions)
- diff_options = svn_cstring_split(lb->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_cmdline_printf(pool, _("\n")));
- SVN_ERR(svn_cmdline_fflush(stdout));
- SVN_ERR(svn_client_diff_peg5(diff_options,
- lb->target_path_or_url,
- &lb->target_peg_revision,
- &start_revision, &end_revision,
- NULL,
- lb->depth,
- FALSE, /* ignore ancestry */
- TRUE, /* no diff deleted */
- FALSE, /* show copies as adds */
- FALSE, /* ignore content type */
- FALSE, /* use git diff format */
- svn_cmdline_output_encoding(pool),
- outfile,
- errfile,
- NULL,
- lb->ctx, pool));
- SVN_ERR(svn_cmdline_printf(pool, _("\n")));
- }
+ svn_stream_t *outstream;
+ svn_stream_t *errstream;
- SVN_ERR(svn_cmdline_fflush(stdout));
- SVN_ERR(svn_cmdline_fflush(stderr));
+ 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;
@@ -376,7 +522,7 @@ log_entry_receiver_xml(void *baton,
{
struct log_receiver_baton *lb = baton;
/* Collate whole log message into sb before printing. */
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
char *revstr;
const char *author;
const char *date;
@@ -387,13 +533,6 @@ log_entry_receiver_xml(void *baton,
svn_compat_log_revprops_out(&author, &date, &message, log_entry->revprops);
- 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);
-
if (log_entry->revision == 0 && message == NULL)
return SVN_NO_ERROR;
@@ -406,6 +545,24 @@ log_entry_receiver_xml(void *baton,
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",
@@ -425,18 +582,23 @@ log_entry_receiver_xml(void *baton,
if (log_entry->changed_paths2)
{
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_paths;
+ int i;
/* <paths> */
svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths",
NULL);
- for (hi = apr_hash_first(pool, log_entry->changed_paths2);
- hi != NULL;
- hi = apr_hash_next(hi))
+ /* 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++)
{
- const char *path = svn__apr_hash_index_key(hi);
- svn_log_changed_path2_t *log_item = svn__apr_hash_index_val(hi);
+ 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;
@@ -464,7 +626,13 @@ log_entry_receiver_xml(void *baton,
/* <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), NULL);
+ "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);
@@ -485,9 +653,9 @@ log_entry_receiver_xml(void *baton,
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_cl__print_xml_prop_hash(&sb, log_entry->revprops,
- FALSE, /* name_only */
- pool));
+ 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");
}
@@ -541,11 +709,11 @@ svn_cl__log(apr_getopt_t *os,
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'quiet' and 'diff' options are "
"mutually exclusive"));
- if (opt_state->diff_cmd && (! opt_state->show_diff))
+ 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->internal_diff && (! opt_state->show_diff))
+ 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"));
@@ -618,6 +786,7 @@ svn_cl__log(apr_getopt_t *os,
: 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)
diff --git a/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c
index 57aad9f..17507a2 100644
--- a/subversion/svn/merge-cmd.c
+++ b/subversion/svn/merge-cmd.c
@@ -33,12 +33,121 @@
#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;
+ }
+
+ 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 *
@@ -51,10 +160,11 @@ svn_cl__merge(apr_getopt_t *os,
apr_array_header_t *targets;
const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = "";
svn_boolean_t two_sources_specified = TRUE;
- svn_error_t *err;
+ 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. */
@@ -175,28 +285,22 @@ svn_cl__merge(apr_getopt_t *os,
_("Too many arguments given"));
/* Set the default value for unspecified paths and peg revision. */
- if (targets->nelts == 0)
- {
- peg_revision1.kind = svn_opt_revision_head;
- }
- else
- {
- /* targets->nelts is 1 ("svn merge SOURCE") or 2 ("svn merge
- SOURCE WCPATH") here. */
- sourcepath2 = sourcepath1;
+ /* 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 (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 *);
- 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"));
- }
+ 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 */
@@ -212,16 +316,13 @@ svn_cl__merge(apr_getopt_t *os,
/* Catch 'svn merge wc_path1 wc_path2 [target]' without explicit
revisions--since it ignores local modifications it may not do what
- the user expects. Forcing the user to specify a repository
+ 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. */
- if ((first_range_start.kind == svn_opt_revision_unspecified
- && ! svn_path_is_url(sourcepath1))
- ||
- (first_range_end.kind == svn_opt_revision_unspecified
- && ! svn_path_is_url(sourcepath2)))
- return svn_error_create
- (SVN_ERR_CLIENT_BAD_REVISION, 0,
- _("A working copy merge source needs an explicit revision"));
+ 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)
@@ -231,17 +332,22 @@ svn_cl__merge(apr_getopt_t *os,
/* Decide where to apply the delta (defaulting to "."). */
if (targets->nelts == 3)
- targetpath = APR_ARRAY_IDX(targets, 2, const char *);
+ {
+ 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 (sourcepath1 && sourcepath2 && strcmp(targetpath, "") == 0)
+ 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. However, if the sourcepath is
- a local path, it can refer to a target somewhere deeper in
- the directory structure. */
+ 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);
@@ -249,23 +355,40 @@ svn_cl__merge(apr_getopt_t *os,
if (strcmp(sp1_basename, sp2_basename) == 0)
{
- svn_node_kind_t kind;
+ const char *target_url;
+ const char *target_base;
- SVN_ERR(svn_io_check_path(sp1_basename, &kind, pool));
- if (kind == svn_node_file)
+ 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)
{
- targetpath = sp1_basename;
+ 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;
- const char *decoded_path = svn_path_uri_decode(sourcepath1, pool);
- SVN_ERR(svn_io_check_path(decoded_path, &kind, pool));
+
+ SVN_ERR(svn_io_check_path(sourcepath1, &kind, pool));
if (kind == svn_node_file)
{
- targetpath = decoded_path;
+ targetpath = sourcepath1;
}
}
}
@@ -278,6 +401,16 @@ svn_cl__merge(apr_getopt_t *os,
/* 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 "
@@ -298,86 +431,28 @@ svn_cl__merge(apr_getopt_t *os,
"with --reintegrate"));
}
- if (! two_sources_specified) /* TODO: Switch order of if */
+ 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)
{
- /* If we don't have a source, use the target as the source. */
- if (! sourcepath1)
- sourcepath1 = targetpath;
-
- /* 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))
- {
- svn_opt_revision_range_t *range = apr_pcalloc(pool, sizeof(*range));
- ranges_to_merge = apr_array_make(pool, 1, sizeof(range));
- range->start.kind = svn_opt_revision_number;
- range->start.value.number = 1;
- range->end = peg_revision1;
- APR_ARRAY_PUSH(ranges_to_merge, svn_opt_revision_range_t *) = range;
- }
-
- if (opt_state->reintegrate)
- err = svn_client_merge_reintegrate(sourcepath1,
- &peg_revision1,
- targetpath,
- opt_state->dry_run,
- options, ctx, pool);
- else
- err = svn_client_merge_peg4(sourcepath1,
- ranges_to_merge,
- &peg_revision1,
- targetpath,
- opt_state->depth,
- opt_state->ignore_ancestry,
- opt_state->force,
- opt_state->record_only,
- opt_state->dry_run,
- opt_state->allow_mixed_rev,
- options,
- ctx,
- pool);
+ return svn_error_quick_wrap(
+ merge_err,
+ _("Merge tracking not possible, use --ignore-ancestry or\n"
+ "fix invalid mergeinfo in target with 'svn propset'"));
}
- 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"));
- err = svn_client_merge4(sourcepath1,
- &first_range_start,
- sourcepath2,
- &first_range_end,
- targetpath,
- opt_state->depth,
- opt_state->ignore_ancestry,
- opt_state->force,
- opt_state->record_only,
- opt_state->dry_run,
- opt_state->allow_mixed_rev,
- options,
- ctx,
- pool);
- }
-
- if (! opt_state->quiet)
- SVN_ERR(svn_cl__print_conflict_stats(ctx->notify_baton2, pool));
- if (err)
+ if (!opt_state->quiet)
{
- if(err->apr_err == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
- {
- err = svn_error_quick_wrap(
- err,
- _("Merge tracking not possible, use --ignore-ancestry or\n"
- "fix invalid mergeinfo in target with 'svn propset'"));
- }
- else if (! opt_state->reintegrate)
- {
- return svn_cl__may_need_force(err);
- }
+ 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_error_trace(err);
+ return svn_cl__may_need_force(merge_err);
}
diff --git a/subversion/svn/mergeinfo-cmd.c b/subversion/svn/mergeinfo-cmd.c
index 5875e2d..41edcda 100644
--- a/subversion/svn/mergeinfo-cmd.c
+++ b/subversion/svn/mergeinfo-cmd.c
@@ -55,6 +55,189 @@ print_log_rev(void *baton,
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,
@@ -66,28 +249,24 @@ svn_cl__mergeinfo(apr_getopt_t *os,
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_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));
- /* We expect a single source URL followed by a single target --
- nothing more, nothing less. */
+ /* 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"));
-
- /* Parse the SOURCE-URL[@REV] argument. */
SVN_ERR(svn_opt_parse_path(&src_peg_revision, &source,
APR_ARRAY_IDX(targets, 0, const char *), pool));
-
- /* Parse the TARGET[@REV] argument (if provided). */
if (targets->nelts == 2)
{
SVN_ERR(svn_opt_parse_path(&tgt_peg_revision, &target,
@@ -101,11 +280,15 @@ svn_cl__mergeinfo(apr_getopt_t *os,
}
/* 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))
@@ -114,22 +297,58 @@ svn_cl__mergeinfo(apr_getopt_t *os,
tgt_peg_revision.kind = svn_opt_revision_base;
}
+ 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_log(TRUE, target, &tgt_peg_revision,
- source, &src_peg_revision,
- print_log_rev, NULL,
- TRUE, depth, NULL, ctx,
- pool));
+ apr_array_header_t *revprops;
+
+ /* We need only revisions number, not revision properties. */
+ revprops = apr_array_make(pool, 0, sizeof(const char *));
+
+ 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, revprops, ctx,
+ pool));
}
else if (opt_state->show_revs == svn_cl__show_revs_eligible)
{
- SVN_ERR(svn_client_mergeinfo_log(FALSE, target, &tgt_peg_revision,
- source, &src_peg_revision,
- print_log_rev, NULL,
- TRUE, depth, NULL, ctx,
- pool));
+ apr_array_header_t *revprops;
+
+ /* We need only revisions number, not revision properties. */
+ revprops = apr_array_make(pool, 0, sizeof(const char *));
+
+ 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, revprops, 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/move-cmd.c b/subversion/svn/move-cmd.c
index 795870a..bb71043 100644
--- a/subversion/svn/move-cmd.c
+++ b/subversion/svn/move-cmd.c
@@ -84,8 +84,12 @@ svn_cl__move(apr_getopt_t *os,
SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool));
- err = svn_client_move6(targets, dst_path,
- TRUE, opt_state->parents, opt_state->revprop_table,
+ 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);
diff --git a/subversion/svn/notify.c b/subversion/svn/notify.c
index a785e53..6498fb1 100644
--- a/subversion/svn/notify.c
+++ b/subversion/svn/notify.c
@@ -35,7 +35,11 @@
#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"
@@ -47,56 +51,154 @@ struct notify_baton
svn_boolean_t is_checkout;
svn_boolean_t is_export;
svn_boolean_t is_wc_to_repos_copy;
- svn_boolean_t suppress_summary_lines;
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. */
- /* Conflict stats for update and merge. */
- unsigned int text_conflicts;
- unsigned int prop_conflicts;
- unsigned int tree_conflicts;
- unsigned int skipped_paths;
+ svn_cl__conflict_stats_t *conflict_stats;
/* The cwd, for use in decomposing absolute paths. */
const char *path_prefix;
};
-
-svn_error_t *
-svn_cl__print_conflict_stats(void *notify_baton, apr_pool_t *pool)
+/* Conflict stats for operations such as update and merge. */
+struct svn_cl__conflict_stats_t
{
- struct notify_baton *nb = notify_baton;
- unsigned int text_conflicts;
- unsigned int prop_conflicts;
- unsigned int tree_conflicts;
- unsigned int skipped_paths;
+ 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;
+};
- text_conflicts = nb->text_conflicts;
- prop_conflicts = nb->prop_conflicts;
- tree_conflicts = nb->tree_conflicts;
- skipped_paths = nb->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;
+}
- if (text_conflicts > 0 || prop_conflicts > 0
- || tree_conflicts > 0 || skipped_paths > 0)
- SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n")));
+/* 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), "");
+}
- if (text_conflicts > 0)
- SVN_ERR(svn_cmdline_printf
- (pool, _(" Text conflicts: %u\n"), text_conflicts));
+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;
+ }
+}
- if (prop_conflicts > 0)
- SVN_ERR(svn_cmdline_printf
- (pool, _(" Property conflicts: %u\n"), prop_conflicts));
+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);
+}
- if (tree_conflicts > 0)
- SVN_ERR(svn_cmdline_printf
- (pool, _(" Tree conflicts: %u\n"), tree_conflicts));
+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);
+}
- if (skipped_paths > 0)
- SVN_ERR(svn_cmdline_printf
- (pool, _(" Skipped paths: %u\n"), skipped_paths));
+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;
}
@@ -126,7 +228,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
switch (n->action)
{
case svn_wc_notify_skip:
- nb->skipped_paths++;
+ nb->conflict_stats->skipped_paths++;
if (n->content_state == svn_wc_notify_state_missing)
{
if ((err = svn_cmdline_printf
@@ -149,28 +251,28 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
}
break;
case svn_wc_notify_update_skip_obstruction:
- nb->skipped_paths++;
+ 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->skipped_paths++;
+ 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->skipped_paths++;
+ 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->skipped_paths++;
+ nb->conflict_stats->skipped_paths++;
if ((err = svn_cmdline_printf(
pool, _("Skipped '%s' -- Node remains in conflict\n"),
path_local)))
@@ -182,6 +284,10 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
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;
@@ -199,6 +305,12 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
}
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)))
@@ -209,7 +321,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
nb->received_some_change = TRUE;
if (n->content_state == svn_wc_notify_state_conflicted)
{
- nb->text_conflicts++;
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
if ((err = svn_cmdline_printf(pool, "C %s\n", path_local)))
goto print_error;
}
@@ -224,7 +336,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
nb->received_some_change = TRUE;
if (n->content_state == svn_wc_notify_state_conflicted)
{
- nb->text_conflicts++;
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
statchar_buf[0] = 'C';
}
else
@@ -232,7 +344,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
if (n->prop_state == svn_wc_notify_state_conflicted)
{
- nb->prop_conflicts++;
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
statchar_buf[1] = 'C';
}
else if (n->prop_state == svn_wc_notify_state_merged)
@@ -298,7 +410,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
nb->received_some_change = TRUE;
if (n->content_state == svn_wc_notify_state_conflicted)
{
- nb->text_conflicts++;
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
statchar_buf[0] = 'C';
}
else if (n->kind == svn_node_file)
@@ -311,7 +423,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
if (n->prop_state == svn_wc_notify_state_conflicted)
{
- nb->prop_conflicts++;
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
statchar_buf[1] = 'C';
}
else if (n->prop_state == svn_wc_notify_state_changed)
@@ -336,7 +448,13 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
if (n->hunk_matched_line > n->hunk_original_start)
{
- off = 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
@@ -504,7 +622,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
{
if (n->content_state == svn_wc_notify_state_conflicted)
{
- nb->text_conflicts++;
+ store_path(nb, nb->conflict_stats->text_conflicts, path_local);
statchar_buf[0] = 'C';
}
else if (n->kind == svn_node_file)
@@ -517,7 +635,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
if (n->prop_state == svn_wc_notify_state_conflicted)
{
- nb->prop_conflicts++;
+ store_path(nb, nb->conflict_stats->prop_conflicts, path_local);
statchar_buf[1] = 'C';
}
else if (n->prop_state == svn_wc_notify_state_merged)
@@ -583,8 +701,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
break;
case svn_wc_notify_update_started:
- if (! (nb->suppress_summary_lines ||
- nb->in_external ||
+ if (! (nb->in_external ||
nb->is_checkout ||
nb->is_export))
{
@@ -596,77 +713,74 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
case svn_wc_notify_update_completed:
{
- if (! nb->suppress_summary_lines)
+ if (SVN_IS_VALID_REVNUM(n->revision))
{
- 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->is_export)
+ if (nb->received_some_change)
{
+ nb->received_some_change = FALSE;
if ((err = svn_cmdline_printf
(pool, nb->in_external
- ? _("Exported external at revision %ld.\n")
- : _("Exported revision %ld.\n"),
+ ? _("Updated external to revision %ld.\n")
+ : _("Updated to revision %ld.\n"),
n->revision)))
goto print_error;
}
- else if (nb->is_checkout)
+ else
{
if ((err = svn_cmdline_printf
(pool, nb->in_external
- ? _("Checked out external at revision %ld.\n")
- : _("Checked out revision %ld.\n"),
+ ? _("External at revision %ld.\n")
+ : _("At 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 */
+ }
+ else /* no revision */
+ {
+ if (nb->is_export)
{
- 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 ((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;
}
}
}
@@ -898,7 +1012,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
break;
case svn_wc_notify_tree_conflict:
- nb->tree_conflicts++;
+ 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;
@@ -923,50 +1037,50 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
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;
+ 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;
+ 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;
+ 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);
+ 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,
+ err = svn_cmdline_printf(pool,
_("property '%s' deleted from repository revision %ld\n"),
n->prop_name, n->revision);
- if (err)
- goto print_error;
+ 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;
+ err = svn_cmdline_printf(pool, _("Upgraded '%s'\n"), path_local);
+ if (err)
+ goto print_error;
break;
case svn_wc_notify_url_redirect:
@@ -977,7 +1091,37 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
break;
case svn_wc_notify_path_nonexistent:
- err = svn_cmdline_printf(pool, _("'%s' is not under version control"),
+ 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;
@@ -1016,7 +1160,7 @@ notify(void *baton, const svn_wc_notify_t *n, apr_pool_t *pool)
svn_error_t *
svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
void **notify_baton_p,
- svn_boolean_t suppress_summary_lines,
+ svn_cl__conflict_stats_t *conflict_stats,
apr_pool_t *pool)
{
struct notify_baton *nb = apr_pcalloc(pool, sizeof(*nb));
@@ -1026,13 +1170,9 @@ svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p,
nb->is_checkout = FALSE;
nb->is_export = FALSE;
nb->is_wc_to_repos_copy = FALSE;
- nb->suppress_summary_lines = suppress_summary_lines;
nb->in_external = FALSE;
nb->had_print_error = FALSE;
- nb->text_conflicts = 0;
- nb->prop_conflicts = 0;
- nb->tree_conflicts = 0;
- nb->skipped_paths = 0;
+ nb->conflict_stats = conflict_stats;
SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool));
*notify_func_p = notify;
@@ -1080,4 +1220,3 @@ svn_cl__check_externals_failed_notify_wrapper(void *baton,
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
index 24abb47..83707c6 100644
--- a/subversion/svn/patch-cmd.c
+++ b/subversion/svn/patch-cmd.c
@@ -92,7 +92,7 @@ svn_cl__patch(apr_getopt_t *os,
if (! opt_state->quiet)
- SVN_ERR(svn_cl__print_conflict_stats(ctx->notify_baton2, pool));
+ SVN_ERR(svn_cl__notifier_print_conflict_stats(ctx->notify_baton2, pool));
return SVN_NO_ERROR;
}
diff --git a/subversion/svn/propedit-cmd.c b/subversion/svn/propedit-cmd.c
index 0e36a34..520fe6c 100644
--- a/subversion/svn/propedit-cmd.c
+++ b/subversion/svn/propedit-cmd.c
@@ -27,6 +27,7 @@
/*** Includes. ***/
+#include "svn_hash.h"
#include "svn_cmdline.h"
#include "svn_wc.h"
#include "svn_pools.h"
@@ -39,8 +40,7 @@
#include "svn_props.h"
#include "cl.h"
-#include "private/svn_wc_private.h"
-
+#include "private/svn_cmdline_private.h"
#include "svn_private_config.h"
@@ -86,6 +86,10 @@ svn_cl__propedit(apr_getopt_t *os,
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,
@@ -122,7 +126,7 @@ svn_cl__propedit(apr_getopt_t *os,
if (! propval)
{
- propval = svn_string_create("", pool);
+ 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: */
@@ -136,8 +140,8 @@ svn_cl__propedit(apr_getopt_t *os,
/* 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_cl__edit_string_externally
- (&propval, NULL,
+ SVN_ERR(svn_cmdline__edit_string_externally(
+ &propval, NULL,
opt_state->editor_cmd, temp_dir,
propval, "svn-prop",
ctx->config,
@@ -212,7 +216,7 @@ svn_cl__propedit(apr_getopt_t *os,
svn_string_t *propval, *edited_propval;
const char *base_dir = target;
const char *target_local;
- const char *local_abspath;
+ const char *abspath_or_url;
svn_node_kind_t kind;
svn_opt_revision_t peg_revision;
svn_revnum_t base_rev = SVN_INVALID_REVNUM;
@@ -221,28 +225,25 @@ svn_cl__propedit(apr_getopt_t *os,
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
if (!svn_path_is_url(target))
- SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, subpool));
+ 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_propget4(&props, pname_utf8,
- svn_path_is_url(target)
- ? target : local_abspath,
+ 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 = apr_hash_get(props,
- svn_path_is_url(target)
- ? target : local_abspath,
- APR_HASH_KEY_STRING);
+ propval = svn_hash_gets(props, abspath_or_url);
if (! propval)
- propval = svn_string_create("", subpool);
+ propval = svn_string_create_empty(subpool);
if (svn_path_is_url(target))
{
@@ -261,8 +262,8 @@ svn_cl__propedit(apr_getopt_t *os,
}
/* Split the path if it is a file path. */
- SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE,
- subpool));
+ 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(
@@ -274,16 +275,16 @@ svn_cl__propedit(apr_getopt_t *os,
/* Run the editor on a temporary file which contains the
original property value... */
- SVN_ERR(svn_cl__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));
+ 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);
@@ -316,6 +317,10 @@ svn_cl__propedit(apr_getopt_t *os,
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,
@@ -329,7 +334,7 @@ svn_cl__propedit(apr_getopt_t *os,
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 an URL
+ 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(
diff --git a/subversion/svn/propget-cmd.c b/subversion/svn/propget-cmd.c
index fcb6503..e291911 100644
--- a/subversion/svn/propget-cmd.c
+++ b/subversion/svn/propget-cmd.c
@@ -27,6 +27,7 @@
/*** Includes. ***/
+#include "svn_hash.h"
#include "svn_cmdline.h"
#include "svn_pools.h"
#include "svn_client.h"
@@ -70,11 +71,46 @@ stream_write(svn_stream_t *out,
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 = svn_pool_create(pool);
+ 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++)
@@ -82,38 +118,145 @@ print_properties_xml(const char *pname,
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_stringbuf_t *sb = NULL;
+ 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, iterpool);
+ 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));
}
- svn_pool_destroy(iterpool);
+ 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.
-/* Print the properties in PROPS to the stream OUT. PROPS is a hash mapping
- * (const char *) path to (svn_string_t) property value.
- * If IS_URL is true, all paths 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.
- * */
+ 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,
- svn_boolean_t is_url,
+ 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,
@@ -126,6 +269,24 @@ print_properties(svn_stream_t *out,
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++)
{
@@ -135,53 +296,10 @@ print_properties(svn_stream_t *out,
svn_pool_clear(iterpool);
- if (print_filenames)
- {
- const char *header;
-
- /* Print the file name. */
-
- if (! is_url)
- filename = svn_cl__local_style_skip_ancestor(path_prefix, filename,
- iterpool);
-
- /* In verbose mode, print exactly same as "proplist" does;
- * otherwise, print a brief header. */
- header = apr_psprintf(iterpool, like_proplist
- ? _("Properties on '%s':\n")
- : "%s - ", filename);
- SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, iterpool));
- SVN_ERR(svn_subst_translate_cstring2(header, &header,
- APR_EOL_STR, /* 'native' eol */
- FALSE, /* no repair */
- NULL, /* no keywords */
- FALSE, /* no expansion */
- iterpool));
- 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(iterpool);
-
- apr_hash_set(hash, pname_utf8, APR_HASH_KEY_STRING, propval);
- SVN_ERR(svn_cl__print_prop_hash(out, hash, FALSE, iterpool));
- }
- 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, iterpool));
-
- SVN_ERR(stream_write(out, propval->data, propval->len));
-
- if (! omit_newline)
- SVN_ERR(stream_write(out, APR_EOL_STR,
- strlen(APR_EOL_STR)));
- }
+ 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);
@@ -234,6 +352,11 @@ svn_cl__propget(apr_getopt_t *os,
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));
@@ -255,7 +378,8 @@ svn_cl__propget(apr_getopt_t *os,
"revprops",
"rev", revstr, NULL);
- svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, pool);
+ svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, FALSE,
+ pool);
svn_xml_make_close_tag(&sb, pool, "revprops");
@@ -310,6 +434,7 @@ svn_cl__propget(apr_getopt_t *os,
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));
@@ -321,12 +446,15 @@ svn_cl__propget(apr_getopt_t *os,
if (!svn_path_is_url(truepath))
SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
- SVN_ERR(svn_client_propget4(&props, pname_utf8, truepath,
- &peg_revision,
- &(opt_state->start_revision),
- NULL, opt_state->depth,
- opt_state->changelists, ctx, subpool,
- 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,
@@ -335,17 +463,24 @@ svn_cl__propget(apr_getopt_t *os,
print_filenames = ((opt_state->depth > svn_depth_empty
|| targets->nelts > 1
|| apr_hash_count(props) > 1
- || opt_state->verbose)
+ || 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, subpool));
+ SVN_ERR(print_properties_xml(
+ pname_utf8, props,
+ opt_state->show_inherited_props ? inherited_props : NULL,
+ subpool));
else
- SVN_ERR(print_properties(out, svn_path_is_url(target), pname_utf8,
- props, print_filenames, omit_newline,
- like_proplist, subpool));
+ 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)
diff --git a/subversion/svn/proplist-cmd.c b/subversion/svn/proplist-cmd.c
index 64cb055..fe23a67 100644
--- a/subversion/svn/proplist-cmd.c
+++ b/subversion/svn/proplist-cmd.c
@@ -35,8 +35,11 @@
#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
@@ -48,43 +51,81 @@ typedef struct proplist_baton_t
/*** Code. ***/
-/* This implements the svn_proplist_receiver_t interface, printing XML to
+/* 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 = NULL;
+ 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;
- /* "<target ...>" */
- svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
- "path", name_local, NULL);
+ sb = NULL;
+
- SVN_ERR(svn_cl__print_xml_prop_hash(&sb, prop_hash, (! opt_state->verbose),
- pool));
+ if (prop_hash)
+ {
+ /* "<target ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", name_local, NULL);
- /* "</target>" */
- svn_xml_make_close_tag(&sb, pool, "target");
+ 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_cl__error_checked_fputs(sb->data, stdout);
+ return SVN_NO_ERROR;
}
-/* This implements the svn_proplist_receiver_t interface. */
+/* 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;
@@ -96,10 +137,48 @@ proplist_receiver(void *baton,
else
name_local = path;
- if (!opt_state->quiet)
- SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), name_local));
- return svn_cl__print_prop_hash(NULL, prop_hash, (! opt_state->verbose),
- pool);
+ 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;
}
@@ -129,6 +208,11 @@ svn_cl__proplist(apr_getopt_t *os,
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));
@@ -147,8 +231,9 @@ svn_cl__proplist(apr_getopt_t *os,
svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal,
"revprops",
"rev", revstr, NULL);
- SVN_ERR(svn_cl__print_xml_prop_hash
- (&sb, proplist, (! opt_state->verbose), scratch_pool));
+ 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));
@@ -161,15 +246,16 @@ svn_cl__proplist(apr_getopt_t *os,
_("Unversioned properties on revision %ld:\n"),
rev));
- SVN_ERR(svn_cl__print_prop_hash
- (NULL, proplist, (! opt_state->verbose), scratch_pool));
+ 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_receiver_t pl_receiver;
+ svn_proplist_receiver2_t pl_receiver;
if (opt_state->xml)
{
@@ -203,10 +289,11 @@ svn_cl__proplist(apr_getopt_t *os,
iterpool));
SVN_ERR(svn_cl__try(
- svn_client_proplist3(truepath, &peg_revision,
+ 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,
@@ -223,12 +310,12 @@ svn_cl__proplist(apr_getopt_t *os,
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 "
diff --git a/subversion/svn/props.c b/subversion/svn/props.c
index f08f18c..2a41ac8 100644
--- a/subversion/svn/props.c
+++ b/subversion/svn/props.c
@@ -27,7 +27,10 @@
/*** Includes. ***/
+#include <stdlib.h>
+
#include <apr_hash.h>
+#include "svn_hash.h"
#include "svn_cmdline.h"
#include "svn_string.h"
#include "svn_error.h"
@@ -40,10 +43,10 @@
#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 *
@@ -81,144 +84,273 @@ svn_cl__revprop_prepare(const svn_opt_revision_t *revision,
return SVN_NO_ERROR;
}
-
-svn_error_t *
-svn_cl__print_prop_hash(svn_stream_t *out,
- apr_hash_t *prop_hash,
- svn_boolean_t names_only,
- apr_pool_t *pool)
+void
+svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
+ apr_pool_t *pool)
{
- apr_array_header_t *sorted_props;
- int i;
+ svn_stringbuf_t *propbuf;
- sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
- 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 *pname = item.key;
- svn_string_t *propval = item.value;
- const char *pname_stdout;
- apr_size_t len;
+ if (!svn_prop_is_boolean(propname))
+ return;
- if (svn_prop_needs_translation(pname))
- SVN_ERR(svn_subst_detranslate_string(&propval, propval,
- TRUE, pool));
+ propbuf = svn_stringbuf_create(propval, pool);
+ svn_stringbuf_strip_whitespace(propbuf);
- SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
+ 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);
+ }
+}
- if (out)
- {
- pname_stdout = apr_psprintf(pool, " %s\n", pname_stdout);
- SVN_ERR(svn_subst_translate_cstring2(pname_stdout, &pname_stdout,
- APR_EOL_STR, /* 'native' eol */
- FALSE, /* no repair */
- NULL, /* no keywords */
- FALSE, /* no expansion */
- pool));
-
- len = strlen(pname_stdout);
- SVN_ERR(svn_stream_write(out, pname_stdout, &len));
- }
- else
- {
- /* ### We leave these printfs for now, since if propval wasn't
- translated above, we don't know anything about its encoding.
- In fact, it might be binary data... */
- printf(" %s\n", pname_stdout);
- }
- if (!names_only)
- {
- /* Add an extra newline to the value before indenting, so that
- * every line of output has the indentation whether the value
- * already ended in a newline or not. */
- const char *newval = apr_psprintf(pool, "%s\n", propval->data);
- const char *indented_newval = svn_cl__indent_string(newval,
- " ",
- pool);
- if (out)
- {
- len = strlen(indented_newval);
- SVN_ERR(svn_stream_write(out, indented_newval, &len));
- }
- else
- {
- printf("%s", indented_newval);
- }
- }
- }
+/* 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 */
+};
- return SVN_NO_ERROR;
+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;
}
-svn_error_t *
-svn_cl__print_xml_prop_hash(svn_stringbuf_t **outstr,
- apr_hash_t *prop_hash,
- svn_boolean_t names_only,
- apr_pool_t *pool)
+/* Key comparator for qsort for simprop_t */
+static int
+simprop_compare(const void *pkeya, const void *pkeyb)
{
- apr_array_header_t *sorted_props;
- int i;
+ 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))));
+}
- if (*outstr == NULL)
- *outstr = svn_stringbuf_create("", pool);
- sorted_props = svn_sort__hash(prop_hash, svn_sort_compare_items_lexically,
- pool);
- for (i = 0; i < sorted_props->nelts; i++)
+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)
{
- svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
- const char *pname = item.key;
- svn_string_t *propval = item.value;
-
- if (names_only)
- {
- svn_xml_make_open_tag(outstr, pool, svn_xml_self_closing, "property",
- "name", pname, NULL);
- }
- else
- {
- const char *pname_out;
+ 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);
+ }
+}
- if (svn_prop_needs_translation(pname))
- SVN_ERR(svn_subst_detranslate_string(&propval, propval,
- TRUE, pool));
+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_ERR(svn_cmdline_cstring_from_utf8(&pname_out, pname, pool));
+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);
- svn_cmdline__print_xml_prop(outstr, pname_out, propval, pool);
+ 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;
}
}
- 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;
+ }
-void
-svn_cl__check_boolean_prop_val(const char *propname, const char *propval,
- apr_pool_t *pool)
-{
- svn_stringbuf_t *propbuf;
+ qsort(propkeys, numprops, sizeof(*propkeys), simprop_compare);
- if (!svn_prop_is_boolean(propname))
- return;
+ if (0 == propkeys[0]->diff)
+ return SVN_NO_ERROR; /* We found an exact match. */
- propbuf = svn_stringbuf_create(propval, pool);
- svn_stringbuf_strip_whitespace(propbuf);
+ /* 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;
- if (propbuf->data[0] == '\0'
- || strcmp(propbuf->data, "no") == 0
- || strcmp(propbuf->data, "off") == 0
- || strcmp(propbuf->data, "false") == 0)
+ switch (i)
{
- 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);
+ 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
index 33b4b5d..07b9bbd 100644
--- a/subversion/svn/propset-cmd.c
+++ b/subversion/svn/propset-cmd.c
@@ -67,6 +67,9 @@ svn_cl__propset(apr_getopt_t *os,
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. */
@@ -170,6 +173,11 @@ svn_cl__propset(apr_getopt_t *os,
}
}
+ 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,
diff --git a/subversion/svn/relocate-cmd.c b/subversion/svn/relocate-cmd.c
index f07d4ae..fe50f66 100644
--- a/subversion/svn/relocate-cmd.c
+++ b/subversion/svn/relocate-cmd.c
@@ -50,7 +50,7 @@ svn_cl__relocate(apr_getopt_t *os,
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 = NULL, *to = NULL, *path;
+ const char *from, *to, *path;
/* We've got two different syntaxes to support:
diff --git a/subversion/svn/resolve-cmd.c b/subversion/svn/resolve-cmd.c
index 4ce57be..ce4818e 100644
--- a/subversion/svn/resolve-cmd.c
+++ b/subversion/svn/resolve-cmd.c
@@ -26,9 +26,6 @@
/*** Includes. ***/
-#define APR_WANT_STDIO
-#include <apr_want.h>
-
#include "svn_path.h"
#include "svn_client.h"
#include "svn_error.h"
@@ -54,6 +51,7 @@ svn_cl__resolve(apr_getopt_t *os,
apr_array_header_t *targets;
int i;
apr_pool_t *iterpool;
+ svn_boolean_t had_error = FALSE;
switch (opt_state->accept_which)
{
@@ -76,8 +74,11 @@ svn_cl__resolve(apr_getopt_t *os,
conflict_choice = svn_wc_conflict_choose_mine_full;
break;
case svn_cl__accept_unspecified:
- return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("missing --accept option"));
+ 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"));
@@ -88,10 +89,15 @@ svn_cl__resolve(apr_getopt_t *os,
ctx, FALSE,
scratch_pool));
if (! targets->nelts)
- return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
+ svn_opt_push_implicit_dot_target(targets, scratch_pool);
if (opt_state->depth == svn_depth_unknown)
- opt_state->depth = svn_depth_empty;
+ {
+ 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));
@@ -111,9 +117,15 @@ svn_cl__resolve(apr_getopt_t *os,
{
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
index 0db3b4b..51e2da1 100644
--- a/subversion/svn/resolved-cmd.c
+++ b/subversion/svn/resolved-cmd.c
@@ -26,9 +26,6 @@
/*** Includes. ***/
-#define APR_WANT_STDIO
-#include <apr_want.h>
-
#include "svn_path.h"
#include "svn_client.h"
#include "svn_error.h"
diff --git a/subversion/svn/schema/diff.rnc b/subversion/svn/schema/diff.rnc
index 7ac51da..ab89b81 100644
--- a/subversion/svn/schema/diff.rnc
+++ b/subversion/svn/schema/diff.rnc
@@ -35,5 +35,5 @@ attlist.path &=
## The kind of the entry.
attribute kind { "dir" | "file" },
## The action performed against this path. This terminology
- ## was chosen for consistencey from 'svn list'.
+ ## 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
index 6ed2643..3dc43f6 100644
--- a/subversion/svn/schema/info.rnc
+++ b/subversion/svn/schema/info.rnc
@@ -27,8 +27,8 @@ info = element info { entry* }
entry =
element entry {
- attlist.entry, url?, repository?, wc-info?, commit?, conflict?, lock?,
- tree-conflict?
+ attlist.entry, url?, relative-url?, repository?, wc-info?,
+ commit?, conflict?, lock?, tree-conflict?
}
attlist.entry &=
## Local path.
@@ -41,6 +41,9 @@ attlist.entry &=
## 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? }
@@ -61,7 +64,9 @@ wc-info =
depth?,
text-updated?,
prop-updated?,
- checksum?
+ checksum?,
+ moved-from?,
+ moved-to?
}
wcroot-abspath = element wcroot-abspath { string }
@@ -84,6 +89,10 @@ 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,
@@ -118,7 +127,8 @@ attlist.tree-conflict &=
## Operation causing the tree conflict.
attribute operation { "update" | "merge" | "switch" },
## Operation's action on the victim.
- attribute action { "edit" | "add" | "delete" },
+ attribute action { "edit" | "add" | "delete" | "replace" },
## Local reason for the conflict.
attribute reason { "edit" | "obstruction" | "delete" | "add" |
- "missing" | "unversioned" }
+ "missing" | "unversioned" | "replace" |
+ "moved-away" | "moved-here" }
diff --git a/subversion/svn/schema/status.rnc b/subversion/svn/schema/status.rnc
index 6e55fa1..73d0ca0 100644
--- a/subversion/svn/schema/status.rnc
+++ b/subversion/svn/schema/status.rnc
@@ -70,7 +70,11 @@ attlist.wc-status &=
attribute switched { "true" | "false" }?,
## Tree-conflict status of the item.
[ a:defaultValue = "false" ]
- attribute tree-conflicted { "true" | "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? }
diff --git a/subversion/svn/status-cmd.c b/subversion/svn/status-cmd.c
index 74d2847..9840cd2 100644
--- a/subversion/svn/status-cmd.c
+++ b/subversion/svn/status-cmd.c
@@ -27,6 +27,7 @@
/*** Includes. ***/
+#include "svn_hash.h"
#include "svn_string.h"
#include "svn_wc.h"
#include "svn_client.h"
@@ -50,6 +51,9 @@ struct status_baton
{
/* These fields all correspond to the ones in the
svn_cl__print_status() interface. */
+ const char *target_abspath;
+ const char *target_path;
+ svn_boolean_t suppress_externals_placeholders;
svn_boolean_t detailed;
svn_boolean_t show_last_committed;
svn_boolean_t skip_unrecognized;
@@ -74,6 +78,8 @@ struct status_baton
struct status_cache
{
const char *path;
+ const char *target_abspath;
+ const char *target_path;
svn_client_status_t *status;
};
@@ -106,7 +112,7 @@ print_conflict_stats(struct status_baton *sb, apr_pool_t *pool)
static svn_error_t *
print_start_target_xml(const char *target, apr_pool_t *pool)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
"path", target, NULL);
@@ -122,7 +128,7 @@ static svn_error_t *
print_finish_target_xml(svn_revnum_t repos_rev,
apr_pool_t *pool)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
if (SVN_IS_VALID_REVNUM(repos_rev))
{
@@ -149,9 +155,13 @@ print_status_normal_or_xml(void *baton,
struct status_baton *sb = baton;
if (sb->xml_mode)
- return svn_cl__print_status_xml(path, status, sb->ctx, pool);
+ return svn_cl__print_status_xml(sb->target_abspath, sb->target_path,
+ path, status, sb->ctx, pool);
else
- return svn_cl__print_status(path, status, sb->detailed,
+ return svn_cl__print_status(sb->target_abspath, sb->target_path,
+ path, status,
+ sb->suppress_externals_placeholders,
+ sb->detailed,
sb->show_last_committed,
sb->skip_unrecognized,
sb->repos_locks,
@@ -233,16 +243,17 @@ print_status(void *baton,
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->target_abspath = apr_pstrdup(sb->cl_pool, sb->target_abspath);
+ scache->target_path = apr_pstrdup(sb->cl_pool, sb->target_path);
scache->status = svn_client_status_dup(status, sb->cl_pool);
path_array =
- apr_hash_get(sb->cached_changelists, cl_key, APR_HASH_KEY_STRING);
+ 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 *));
- apr_hash_set(sb->cached_changelists, cl_key,
- APR_HASH_KEY_STRING, path_array);
+ svn_hash_sets(sb->cached_changelists, cl_key, path_array);
}
APR_ARRAY_PUSH(path_array, struct status_cache *) = scache;
@@ -298,6 +309,8 @@ svn_cl__status(apr_getopt_t *os,
"mode"));
}
+ 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;
@@ -320,6 +333,10 @@ svn_cl__status(apr_getopt_t *os,
svn_pool_clear(iterpool);
+ SVN_ERR(svn_dirent_get_absolute(&(sb.target_abspath), target,
+ scratch_pool));
+ sb.target_path = target;
+
SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
if (opt_state->xml)
@@ -341,6 +358,7 @@ svn_cl__status(apr_getopt_t *os,
NULL, opt_state->quiet,
/* not versioned: */
SVN_ERR_WC_NOT_WORKING_COPY,
+ SVN_ERR_WC_PATH_NOT_FOUND,
SVN_NO_ERROR));
if (opt_state->xml)
@@ -355,7 +373,7 @@ svn_cl__status(apr_getopt_t *os,
svn_stringbuf_t *buf;
if (opt_state->xml)
- buf = svn_stringbuf_create("", scratch_pool);
+ buf = svn_stringbuf_create_empty(scratch_pool);
for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi;
hi = apr_hash_next(hi))
@@ -369,7 +387,7 @@ svn_cl__status(apr_getopt_t *os,
### non-changelist entries. */
if (opt_state->xml)
{
- svn_stringbuf_set(buf, "");
+ svn_stringbuf_setempty(buf);
svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
"changelist", "name", changelist_name,
NULL);
@@ -384,13 +402,15 @@ svn_cl__status(apr_getopt_t *os,
{
struct status_cache *scache =
APR_ARRAY_IDX(path_array, j, struct status_cache *);
+ sb.target_abspath = scache->target_abspath;
+ sb.target_path = scache->target_path;
SVN_ERR(print_status_normal_or_xml(&sb, scache->path,
scache->status, scratch_pool));
}
if (opt_state->xml)
{
- svn_stringbuf_set(buf, "");
+ svn_stringbuf_setempty(buf);
svn_xml_make_close_tag(&buf, scratch_pool, "changelist");
SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout));
}
diff --git a/subversion/svn/status.c b/subversion/svn/status.c
index 0c5bddb..9f1ad34 100644
--- a/subversion/svn/status.c
+++ b/subversion/svn/status.c
@@ -26,6 +26,7 @@
/*** Includes. ***/
+#include "svn_hash.h"
#include "svn_cmdline.h"
#include "svn_wc.h"
#include "svn_dirent_uri.h"
@@ -33,7 +34,7 @@
#include "svn_time.h"
#include "cl.h"
#include "svn_private_config.h"
-#include "tree-conflicts.h"
+#include "cl-conflicts.h"
#include "private/svn_wc_private.h"
/* Return the single character representation of STATUS */
@@ -135,11 +136,85 @@ generate_status_desc(enum svn_wc_status_kind status)
}
}
+/* Make a relative path containing '..' elements as needed.
+ TARGET_ABSPATH shall be the absolute version of TARGET_PATH.
+ TARGET_ABSPATH, TARGET_PATH and PATH shall be canonical.
+
+ If above conditions are met, a relative path that leads to PATH
+ from TARGET_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 *target_abspath,
+ const char *target_path,
+ const char *path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *la;
+ const char *parent_dir_els = "";
+ const char *abspath, *relative;
+ svn_error_t *err = svn_dirent_get_absolute(&abspath, path, scratch_pool);
+
+ if (err)
+ {
+ /* We probably got passed some invalid path. */
+ svn_error_clear(err);
+ return apr_pstrdup(result_pool, path);
+ }
+
+ relative = svn_dirent_skip_ancestor(target_abspath, abspath);
+ if (relative)
+ {
+ return svn_dirent_join(target_path, relative, result_pool);
+ }
+
+ /* An example:
+ * relative_to_path = /a/b/c
+ * path = /a/x/y/z
+ * result = ../../x/y/z
+ *
+ * Another example (Windows specific):
+ * relative_to_path = F:/wc
+ * path = C:/wc
+ * result = C:/wc
+ */
+
+ /* Skip the common ancestor of both paths, here '/a'. */
+ la = svn_dirent_get_longest_ancestor(target_abspath, abspath,
+ scratch_pool);
+ if (*la == '\0')
+ {
+ /* Nothing in common: E.g. C:/ vs F:/ on Windows */
+ return apr_pstrdup(result_pool, path);
+ }
+ relative = svn_dirent_skip_ancestor(la, target_abspath);
+ path = svn_dirent_skip_ancestor(la, path);
+
+ /* In above example, we'd now have:
+ * relative_to_path = b/c
+ * path = x/y/z */
+
+ /* Count the elements of relative_to_path and prepend as many '..' elements
+ * to path. */
+ while (*relative)
+ {
+ svn_dirent_split(&relative, NULL, relative,
+ scratch_pool);
+ parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool);
+ }
+
+ return svn_dirent_join(parent_dir_els, 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 *path,
+print_status(const char *target_abspath,
+ const char *target_path,
+ const char *path,
svn_boolean_t detailed,
svn_boolean_t show_last_committed,
svn_boolean_t repos_locks,
@@ -154,6 +229,10 @@ print_status(const char *path,
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(target_abspath, target_path, 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.
@@ -222,6 +301,55 @@ print_status(const char *path,
(*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(target_abspath, target_path,
+ 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(target_abspath, target_path,
+ 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(target_abspath, target_path,
+ 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);
+ }
+ }
+
+ path = svn_dirent_local_style(path, pool);
+
if (detailed)
{
char ood_status, lock_status;
@@ -284,7 +412,7 @@ print_status(const char *path,
SVN_ERR
(svn_cmdline_printf(pool,
- "%c%c%c%c%c%c%c %c %6s %6s %-12s %s%s\n",
+ "%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' : ' ',
@@ -297,11 +425,13 @@ print_status(const char *path,
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 %6s %s%s\n",
+ 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' : ' ',
@@ -312,11 +442,13 @@ print_status(const char *path,
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\n",
+ 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' : ' ',
@@ -326,6 +458,8 @@ print_status(const char *path,
? 'K' : ' '),
tree_status_code,
path,
+ moved_to_line,
+ moved_from_line,
tree_desc_line));
return svn_cmdline_fflush(stdout);
@@ -333,12 +467,14 @@ print_status(const char *path,
svn_error_t *
-svn_cl__print_status_xml(const char *path,
+svn_cl__print_status_xml(const char *target_abspath,
+ const char *target_path,
+ 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("", 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;
@@ -351,32 +487,54 @@ svn_cl__print_status_xml(const char *path,
SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
ctx->wc_ctx, local_abspath, pool));
+ path = make_relpath(target_abspath, target_path, 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);
- apr_hash_set(att_hash, "item", APR_HASH_KEY_STRING,
- generate_status_desc(combined_status(status)));
-
- apr_hash_set(att_hash, "props", APR_HASH_KEY_STRING,
- generate_status_desc(
- (status->node_status != svn_wc_status_deleted)
- ? status->prop_status
- : svn_wc_status_none));
+ 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)
- apr_hash_set(att_hash, "wc-locked", APR_HASH_KEY_STRING, "true");
+ svn_hash_sets(att_hash, "wc-locked", "true");
if (status->copied)
- apr_hash_set(att_hash, "copied", APR_HASH_KEY_STRING, "true");
+ svn_hash_sets(att_hash, "copied", "true");
if (status->switched)
- apr_hash_set(att_hash, "switched", APR_HASH_KEY_STRING, "true");
+ svn_hash_sets(att_hash, "switched", "true");
if (status->file_external)
- apr_hash_set(att_hash, "file-external", APR_HASH_KEY_STRING, "true");
+ svn_hash_sets(att_hash, "file-external", "true");
if (status->versioned && ! status->copied)
- apr_hash_set(att_hash, "revision", APR_HASH_KEY_STRING,
- apr_psprintf(pool, "%ld", status->revision));
+ svn_hash_sets(att_hash, "revision",
+ apr_psprintf(pool, "%ld", status->revision));
if (tree_conflicted)
- apr_hash_set(att_hash, "tree-conflicted", APR_HASH_KEY_STRING,
- "true");
+ 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(target_abspath, target_path,
+ 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(target_abspath, target_path,
+ 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);
@@ -416,8 +574,11 @@ svn_cl__print_status_xml(const char *path,
/* Called by status-cmd.c */
svn_error_t *
-svn_cl__print_status(const char *path,
+svn_cl__print_status(const char *target_abspath,
+ const char *target_path,
+ 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,
@@ -437,7 +598,33 @@ svn_cl__print_status(const char *path,
&& status->repos_node_status == svn_wc_status_none))
return SVN_NO_ERROR;
- return print_status(svn_dirent_local_style(path, pool),
+ /* 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(target_abspath, target_path, path,
detailed, show_last_committed, repos_locks, status,
text_conflicts, prop_conflicts, tree_conflicts,
ctx, pool);
diff --git a/subversion/svn/main.c b/subversion/svn/svn.c
index 8f3a7c4..38d4ce1 100644
--- a/subversion/svn/main.c
+++ b/subversion/svn/svn.c
@@ -1,5 +1,5 @@
/*
- * main.c: Subversion command line client.
+ * svn.c: Subversion command line client main file.
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -54,8 +54,9 @@
#include "svn_version.h"
#include "cl.h"
-#include "private/svn_wc_private.h"
+#include "private/svn_opt_private.h"
#include "private/svn_cmdline_private.h"
+#include "private/svn_subr_private.h"
#include "svn_private_config.h"
@@ -66,14 +67,25 @@
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_ancestor_path = SVN_OPT_FIRST_LONGOPT_ID,
- opt_auth_password,
+ 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,
@@ -88,11 +100,10 @@ typedef enum svn_cl__longopt_t {
opt_new_cmd,
opt_no_auth_cache,
opt_no_autoprops,
- opt_no_diff_deleted,
opt_no_ignore,
opt_no_unlock,
opt_non_interactive,
- opt_notice_ancestry,
+ opt_force_interactive,
opt_old_cmd,
opt_record_only,
opt_relocate,
@@ -100,7 +111,6 @@ typedef enum svn_cl__longopt_t {
opt_revprop,
opt_stop_on_copy,
opt_strict,
- opt_summarize,
opt_targets,
opt_depth,
opt_set_depth,
@@ -116,14 +126,15 @@ typedef enum svn_cl__longopt_t {
opt_reintegrate,
opt_trust_server_cert,
opt_strip,
- opt_show_copies_as_adds,
opt_ignore_keywords,
opt_reverse_diff,
opt_ignore_whitespace,
opt_diff,
- opt_internal_diff,
- opt_use_git_diff_format,
- opt_allow_mixed_revisions
+ opt_allow_mixed_revisions,
+ opt_include_externals,
+ opt_show_inherited_props,
+ opt_search,
+ opt_search_and
} svn_cl__longopt_t;
@@ -145,7 +156,11 @@ const apr_getopt_option_t svn_cl__options[] =
{"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")},
+ "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"
" "
@@ -173,37 +188,23 @@ const apr_getopt_option_t svn_cl__options[] =
{"username", opt_auth_username, 1, N_("specify a username ARG")},
{"password", opt_auth_password, 1, N_("specify a password ARG")},
{"extensions", 'x', 1,
- N_("Default: '-u'. When Subversion is invoking an\n"
+ N_("Specify differencing options for external diff or\n"
" "
- "external diff program, ARG is simply passed along\n"
+ "internal diff or blame. Default: '-u'. Options are\n"
" "
- "to the program. But when Subversion is using its\n"
+ "separated by spaces. Internal diff and blame take:\n"
" "
- "default internal diff implementation, or when\n"
+ " -u, --unified: Show 3 lines of unified context\n"
" "
- "Subversion is displaying blame annotations, ARG\n"
+ " -b, --ignore-space-change: Ignore changes in\n"
" "
- "could be any of the following:\n"
+ " amount of white space\n"
" "
- " -u (--unified):\n"
+ " -w, --ignore-all-space: Ignore all white space\n"
" "
- " Output 3 lines of unified context.\n"
+ " --ignore-eol-style: Ignore changes in EOL style\n"
" "
- " -b (--ignore-space-change):\n"
- " "
- " Ignore changes in the amount of white space.\n"
- " "
- " -w (--ignore-all-space):\n"
- " "
- " Ignore all white space.\n"
- " "
- " --ignore-eol-style:\n"
- " "
- " Ignore changes in EOL style.\n"
- " "
- " -p (--show-c-function):\n"
- " "
- " Show C function name in diff output.")},
+ " -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,
@@ -219,7 +220,9 @@ const apr_getopt_option_t svn_cl__options[] =
{"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 property ignores")},
+ 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,
@@ -229,18 +232,19 @@ const apr_getopt_option_t svn_cl__options[] =
" "
"with '--non-interactive')") },
{"non-interactive", opt_non_interactive, 0,
- N_("do no interactive prompting")},
+ 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")},
- {"no-diff-deleted", opt_no_diff_deleted, 0,
- N_("do not print differences for deleted files")},
- {"notice-ancestry", opt_notice_ancestry, 0,
- N_("notice ancestry when calculating differences")},
{"ignore-ancestry", opt_ignore_ancestry, 0,
- N_("ignore ancestry when calculating merges")},
+ N_("disable merge tracking; diff nodes as if related")},
{"ignore-externals", opt_ignore_externals, 0,
N_("ignore externals definitions")},
- {"diff-cmd", opt_diff_cmd, 1, N_("use ARG as diff command")},
{"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,
@@ -272,7 +276,6 @@ const apr_getopt_option_t svn_cl__options[] =
"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")},
- {"summarize", opt_summarize, 0, N_("show a summary of the results")},
{"remove", opt_remove, 0, N_("remove changelist association")},
{"changelist", opt_changelist, 1,
N_("operate only on members of changelist ARG")},
@@ -308,7 +311,7 @@ const apr_getopt_option_t svn_cl__options[] =
" "
"('merged', 'eligible')")},
{"reintegrate", opt_reintegrate, 0,
- N_("merge a branch back into its parent branch")},
+ N_("deprecated")},
{"strip", opt_strip, 1,
N_("number of leading path components to strip from\n"
" "
@@ -325,8 +328,6 @@ const apr_getopt_option_t svn_cl__options[] =
"The expected component separator is '/' on all\n"
" "
"platforms. A leading '/' counts as one component.")},
- {"show-copies-as-adds", opt_show_copies_as_adds, 0,
- N_("don't diff copied or moved files with their source")},
{"ignore-keywords", opt_ignore_keywords, 0,
N_("don't expand keywords")},
{"reverse-diff", opt_reverse_diff, 0,
@@ -334,16 +335,51 @@ const apr_getopt_option_t svn_cl__options[] =
{"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 merge into mixed-revision working copy.\n"
+ 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
*
@@ -366,7 +402,7 @@ const apr_getopt_option_t svn_cl__options[] =
*
* 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 an url can be used. Hmm, should this be part of the
+ * either a path or a url can be used. Hmm, should this be part of the
* help text?
*/
@@ -376,7 +412,8 @@ const apr_getopt_option_t svn_cl__options[] =
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_trust_server_cert, opt_config_dir, opt_config_options, 0
+ 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.)
@@ -448,9 +485,19 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
{'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"
+ ("Recursively clean up the working copy, removing write locks, resuming\n"
"unfinished operations, etc.\n"
- "usage: cleanup [WCPATH...]\n"),
+ "usage: cleanup [WCPATH...]\n"
+ "\n"
+ " Finish any unfinished business in the working copy at WCPATH, and remove\n"
+ " write locks (shown as 'L' by the 'svn status' command) from the working\n"
+ " copy. Usually, this is only necessary if a Subversion client has crashed\n"
+ " while using the working copy, leaving it in an unusable state.\n"
+ "\n"
+ " WARNING: There is no mechanism that will protect write locks still\n"
+ " being used by other Subversion clients. Running this command\n"
+ " while another client is using the working copy can corrupt\n"
+ " the working copy beyond repair!\n"),
{opt_merge_cmd} },
{ "commit", svn_cl__commit, {"ci"},
@@ -462,28 +509,25 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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_changelist, opt_keep_changelists, opt_include_externals} },
{ "copy", svn_cl__copy, {"cp"}, N_
- ("Duplicate something in working copy or repository, remembering\n"
- "history.\n"
+ ("Copy files and directories in a working copy or repository.\n"
"usage: copy SRC[@REV]... DST\n"
"\n"
- "When copying multiple sources, they will be added as children of DST,\n"
- "which must be a directory.\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.\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"),
+ " 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_
@@ -503,33 +547,46 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
{opt_force, 'q', opt_targets, SVN_CL__LOG_MSG_OPTIONS, opt_keep_local} },
{ "diff", svn_cl__diff, {"di"}, N_
- ("Display the differences between two revisions or paths.\n"
- "usage: 1. diff [-c M | -r N[:M]] [TARGET[@REV]...]\n"
- " 2. diff [-r N[:M]] --old=OLD-TGT[@OLDREV] [--new=NEW-TGT[@NEWREV]] \\\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"
- " 3. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]\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"
- " 1. Display the changes made to TARGETs as they are seen in REV between\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"
- " 2. Display the differences between OLD-TGT as it was seen in OLDREV and\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"
- " 3. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\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"
- " Use just 'svn diff' to display local modifications in a working copy.\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_deleted, opt_show_copies_as_adds,
- opt_notice_ancestry, opt_summarize, opt_changelist, opt_force, opt_xml,
- opt_use_git_diff_format} },
+ 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"
@@ -601,7 +658,9 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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} },
+ {'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"
@@ -646,18 +705,53 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
"\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"
- " svn log\n"
- " svn log foo.c\n"
- " svn log bar.c@42\n"
- " svn log http://www.example.com/repo/project/foo.c\n"
- " svn log http://www.example.com/repo/project foo.c bar.c\n"
- " svn log http://www.example.com/repo/project@50 foo.c bar.c\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_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")}} },
@@ -667,25 +761,22 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
* (with quotes and newlines removed). */
"Merge changes into a working copy.\n"
"usage: 1. merge SOURCE[@REV] [TARGET_WCPATH]\n"
-" (the 'sync' merge)\n"
+" (the 'complete' merge)\n"
" 2. merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n"
" (the 'cherry-pick' merge)\n"
-" 3. merge --reintegrate SOURCE[@REV] [TARGET_WCPATH]\n"
-" (the 'reintegrate' merge)\n"
-" 4. merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n"
+" 3. merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n"
" (the '2-URL' merge)\n"
"\n"
-" 1. This form is called a 'sync' (or 'catch-up') merge:\n"
+" 1. This form, with one source path and no revision range, is called\n"
+" a 'complete' merge:\n"
"\n"
" svn merge SOURCE[@REV] [TARGET_WCPATH]\n"
"\n"
-" A sync merge is used to fetch all the latest changes made on a parent\n"
-" branch. In other words, the target branch has originally been created\n"
-" by copying the source branch, and any changes committed on the source\n"
-" branch since branching are applied to the target branch. This uses\n"
-" merge tracking to skip all those revisions that have already been\n"
-" merged, so a sync merge can be repeated periodically to stay up-to-\n"
-" date with the source branch.\n"
+" The complete 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"
@@ -701,27 +792,60 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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 assumed.\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. There are some special cases:\n"
"\n"
-" - Sync Merge Example -\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"
-" A feature is being developed on a branch called 'feature', which has\n"
-" originally been a copy of trunk. The feature branch has been regularly\n"
-" synced with trunk to keep up with the changes made there. The previous\n"
-" sync merges are not shown on this diagram, and the last of them was\n"
-" done when HEAD was r100. Currently, HEAD is r200.\n"
+" - The 'Feature Branch' Merging Pattern -\n"
"\n"
-" feature +------------------------o-----\n"
-" / ^\n"
-" / ............ |\n"
-" / . . /\n"
-" trunk ------+------------L--------------R------\n"
-" r100 r200\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\n"
-" (trunk@100) and R marks the right side (trunk@200) of the merge. The\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"
@@ -736,6 +860,39 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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"
@@ -753,7 +910,9 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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 assumed.\n"
+" TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"
+" assumed. The special cases noted above in the 'complete' 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"
@@ -770,7 +929,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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>'.\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"
@@ -806,87 +966,27 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" svn merge -c50,54,60 -r65:68 ^/trunk\n"
"\n"
"\n"
-" 3. This form is called a 'reintegrate merge':\n"
-"\n"
-" svn merge --reintegrate SOURCE[@REV] [TARGET_WCPATH]\n"
-"\n"
-" In a reintegrate merge, an (e.g. feature) branch is merged back to its\n"
-" originating branch. In other words, the source branch has originally\n"
-" been created by copying the target branch, development has concluded\n"
-" on the source branch and it should now be merged back into the target\n"
-" branch.\n"
-" \n"
-" SOURCE is the URL of a branch to be merged back. If REV is specified,\n"
-" it is used as the peg revision for SOURCE; if REV is not specified,\n"
-" the HEAD revision is assumed.\n"
-"\n"
-" TARGET_WCPATH is a working copy of the branch the changes will be\n"
-" applied to.\n"
-"\n"
-" - Reintegrate Merge Example -\n"
-"\n"
-" A feature has been developed on a branch called 'feature'. The feature\n"
-" branch started as a copy of trunk@W. Work on the feature has completed\n"
-" and it should be merged back into the trunk.\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"
-" feature +--------------------------------R\n"
-" / . \\\n"
-" / ............. \\\n"
-" / . v\n"
-" trunk ------+--------------------L------------------o\n"
-" rW rX\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 --reintegrate ^/feature\n"
+" 3. This form is called a '2-URL merge':\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"
-" After the reintegrate merge, the feature branch cannot be synced to\n"
-" the trunk again without merge conflicts. If further work must be done\n"
-" on the feature branch, it should be deleted and then re-created.\n"
-"\n"
-"\n"
-" 4. This form is called a '2-URL merge':\n"
-"\n"
-" svn merge SOURCE1[@N] SOURCE2[@M] [TARGET_WCPATH]\n"
-"\n"
-" Two source URLs are specified, together with two revisions N and M.\n"
-" The two sources are compared at the specified revisions, and the\n"
-" difference is applied to TARGET_WCPATH, which is a path to a working\n"
-" copy of another branch. The three branches involved can be completely\n"
-" unrelated.\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"
-" If TARGET_WCPATH is omitted, a default value of '.' is assumed.\n"
-" However, in the special case where both sources refer to a file node\n"
-" with the same basename and a similarly named file is also found within\n"
-" '.', the differences will be applied to that local file. The source\n"
-" revisions default to HEAD if omitted.\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 'complete' merge form\n"
+" also apply here.\n"
"\n"
-" The sources can also be specified as working copy paths, in which case\n"
-" the URLs of the merge sources are derived from the working copies.\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"
@@ -988,16 +1088,35 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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} },
+ opt_allow_mixed_revisions, 'v'} },
{ "mergeinfo", svn_cl__mergeinfo, {0}, N_
("Display merge-related information.\n"
- "usage: mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"
- "\n"
- " Display information related to merges (or potential merges) between\n"
- " SOURCE and TARGET (default: '.'). Display the type of information\n"
- " specified by the --show-revs option. If --show-revs isn't passed,\n"
- " it defaults to --show-revs='merged'.\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 complete 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} },
@@ -1020,20 +1139,26 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
{'q', opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
{ "move", svn_cl__move, {"mv", "rename", "ren"}, N_
- ("Move and/or rename something in working copy or repository.\n"
+ ("Move (rename) an item in a working copy or repository.\n"
"usage: move SRC... DST\n"
"\n"
- "When moving multiple sources, they will be added as children of DST,\n"
- "which must be a directory.\n"
- "\n"
- " Note: this subcommand is equivalent to a 'copy' and 'delete'.\n"
- " Note: the --revision option has no use and is deprecated.\n"
- "\n"
" SRC and DST can both be working copy (WC) paths or URLs:\n"
- " WC -> WC: move and schedule for addition (with history)\n"
- " URL -> URL: complete server-side rename.\n"
- " All the SRCs must be of the same type.\n"),
- {'r', 'q', opt_force, opt_parents, SVN_CL__LOG_MSG_OPTIONS} },
+ " 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"
@@ -1044,7 +1169,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
"\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.\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"
@@ -1074,8 +1200,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
"\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"
+ " (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"
),
@@ -1101,7 +1227,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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"),
+ " 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_
@@ -1114,15 +1240,19 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 2. Prints unversioned remote prop on repos revision.\n"
" TARGET only determines which repository to access.\n"
"\n"
- " By default, this subcommand will add an extra newline to the end\n"
- " of the property values so that the output looks pretty. Also,\n"
- " whenever there are multiple paths involved, each property value\n"
- " is prefixed with the path with which it is associated. Use the\n"
- " --strict option to disable these beautifications (useful when\n"
- " redirecting a binary property value to a file, but available only\n"
- " if you supply a single TARGET to a non-recursive propget operation).\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_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"
@@ -1132,8 +1262,14 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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"),
- {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist } },
+ " 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"
@@ -1146,18 +1282,35 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
"\n"
" The value may be provided with the --file option instead of PROPVAL.\n"
"\n"
- " Note: svn recognizes the following special versioned properties\n"
- " but will store any arbitrary properties set:\n"
- " svn:ignore - A newline separated list of file glob patterns to ignore.\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 object.\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 object was last modified.\n"
- " Rev, Revision, - The last revision the object changed.\n"
- " LastChangedRevision\n"
- " Id - A compressed summary of the previous\n"
- " 4 keywords.\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"
@@ -1165,43 +1318,37 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" 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:externals - A newline separated list of module specifiers,\n"
- " each of which consists of a URL and a relative directory path,\n"
- " similar to the syntax of the 'svn checkout' command:\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"
- " A revision to check out can optionally be specified to pin the\n"
- " external to a known revision:\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 has been\n"
- " deleted (possibly even deleted multiple times in its history),\n"
- " an optional peg revision can be appended to the URL:\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"
- " Relative URLs are indicated by starting the URL with one\n"
- " of the following strings:\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 scheme\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 in externals definitions starting with the '#' character\n"
- " are considered comments and are ignored.\n"
- " Subversion 1.4 and earlier only support the following formats\n"
- " where peg revisions can only be specified using a -r modifier\n"
- " and where URLs cannot be relative:\n"
- " foo http://example.com/repos/zig\n"
- " foo/bar -r 1234 http://example.com/repos/zag\n"
- " Use of these formats is discouraged. They should only be used if\n"
- " interoperability with 1.4 clients is desired.\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"
- " The svn:keywords, svn:executable, svn:eol-style, svn:mime-type and\n"
- " svn:needs-lock properties cannot be set on a directory. A non-recursive\n"
- " attempt will fail, and a recursive attempt will set the property\n"
- " only on the file children of the directory.\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")}} },
@@ -1231,9 +1378,14 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
{ "resolve", svn_cl__resolve, {0}, N_
("Resolve conflicts on working copy files or directories.\n"
- "usage: resolve --accept=ARG [PATH...]\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"
- " Note: the --accept option is currently required.\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"
" "
@@ -1252,11 +1404,15 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
{opt_targets, 'R', opt_depth, 'q'} },
{ "revert", svn_cl__revert, {0}, N_
- ("Restore pristine working copy file (undo most local edits).\n"
+ ("Restore pristine working copy state (undo local changes).\n"
"usage: revert PATH...\n"
"\n"
- " Note: this subcommand does not require network access, and resolves\n"
- " any conflicted states.\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_
@@ -1285,9 +1441,10 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" ' ' 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"
+ " Third column: Whether the working copy is locked for writing by\n"
+ " another Subversion client modifying the working copy\n"
+ " ' ' not locked for writing\n"
+ " 'L' locked for writing\n"
" Fourth column: Scheduled commit will contain addition-with-history\n"
" ' ' no history scheduled with commit\n"
" '+' history scheduled with commit\n"
@@ -1295,16 +1452,16 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" ' ' 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"
+ " Sixth column: Whether the item is locked in repository for exclusive commit\n"
" (without -u)\n"
- " ' ' no lock token\n"
- " 'K' lock token present\n"
+ " ' ' not locked by this working copy\n"
+ " 'K' locked by this working copy, but lock might be stolen or broken\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"
+ " ' ' not locked in repository, not locked by this working copy\n"
+ " 'K' locked in repository, lock owned by this working copy\n"
+ " 'O' locked in repository, lock owned by another working copy\n"
+ " 'T' locked in repository, lock owned by this working copy was stolen\n"
+ " 'B' not locked in repository, lock owned by this working copy is broken\n"
" Seventh column: Whether the item is the victim of a tree conflict\n"
" ' ' normal\n"
" 'C' tree-Conflicted\n"
@@ -1395,7 +1552,10 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
" svn switch --relocate http://www.example.com/repo/project \\\n"
" svn://svn.example.com/repo/project\n"),
{ 'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_relocate,
- opt_ignore_externals, opt_ignore_ancestry, opt_force, opt_accept} },
+ opt_ignore_externals, opt_ignore_ancestry, opt_force, opt_accept},
+ {{opt_ignore_ancestry,
+ N_("allow switching to a node with no common ancestor")}}
+ },
{ "unlock", svn_cl__unlock, {0}, N_
("Unlock working copy paths or URLs.\n"
@@ -1479,9 +1639,9 @@ check_lib_versions(void)
{ "svn_diff", svn_diff_version },
{ NULL, NULL }
};
-
SVN_VERSION_DEFINE(my_version);
- return svn_ver_check_list(&my_version, checklist);
+
+ return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
}
@@ -1506,15 +1666,67 @@ svn_cl__check_cancel(void *baton)
return SVN_NO_ERROR;
}
+/* Add a --search argument to OPT_STATE.
+ * These options start a new search pattern group. */
+static void
+add_search_pattern_group(svn_cl__opt_state_t *opt_state,
+ const char *pattern,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *group = NULL;
+
+ if (opt_state->search_patterns == NULL)
+ opt_state->search_patterns = apr_array_make(result_pool, 1,
+ sizeof(apr_array_header_t *));
+
+ group = apr_array_make(result_pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(group, const char *) = pattern;
+ APR_ARRAY_PUSH(opt_state->search_patterns, apr_array_header_t *) = group;
+}
+
+/* Add a --search-and argument to OPT_STATE.
+ * These patterns are added to an existing pattern group, if any. */
+static void
+add_search_pattern_to_latest_group(svn_cl__opt_state_t *opt_state,
+ const char *pattern,
+ apr_pool_t *result_pool)
+{
+ apr_array_header_t *group;
+
+ if (opt_state->search_patterns == NULL)
+ {
+ add_search_pattern_group(opt_state, pattern, result_pool);
+ return;
+ }
+
+ group = APR_ARRAY_IDX(opt_state->search_patterns,
+ opt_state->search_patterns->nelts - 1,
+ apr_array_header_t *);
+ APR_ARRAY_PUSH(group, const char *) = pattern;
+}
+
/*** Main. ***/
-int
-main(int argc, const char *argv[])
+/* Report and clear the error ERR, and return EXIT_FAILURE. Suppress the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */
+#define EXIT_ERROR(err) \
+ svn_cmdline_handle_exit_error(err, NULL, "svn: ")
+
+/* A redefinition of the public SVN_INT_ERR macro, that suppresses the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR. */
+#undef SVN_INT_ERR
+#define SVN_INT_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return EXIT_ERROR(svn_err__temp); \
+ } while (0)
+
+static int
+sub_main(int argc, const char *argv[], apr_pool_t *pool)
{
svn_error_t *err;
- apr_allocator_t *allocator;
- apr_pool_t *pool;
int opt_id;
apr_getopt_t *os;
svn_cl__opt_state_t opt_state = { 0, { 0 } };
@@ -1528,44 +1740,29 @@ main(int argc, const char *argv[])
svn_config_t *cfg_config;
svn_boolean_t descend = TRUE;
svn_boolean_t interactive_conflicts = FALSE;
+ svn_boolean_t force_interactive = FALSE;
+ svn_cl__conflict_stats_t *conflict_stats
+ = svn_cl__conflict_stats_create(pool);
+ svn_boolean_t use_notifier = TRUE;
+ svn_boolean_t reading_file_from_stdin = FALSE;
apr_hash_t *changelists;
-
- /* Initialize the app. */
- if (svn_cmdline_init("svn", stderr) != EXIT_SUCCESS)
- return EXIT_FAILURE;
-
- /* Create our top-level pool. Use a separate mutexless allocator,
- * given this application is single threaded.
- */
- if (apr_allocator_create(&allocator))
- return EXIT_FAILURE;
-
- apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
-
- pool = svn_pool_create_ex(NULL, allocator);
- apr_allocator_owner_set(allocator, pool);
+ apr_hash_t *cfg_hash;
received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
/* Check library versions */
- err = check_lib_versions();
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(check_lib_versions());
#if defined(WIN32) || defined(__CYGWIN__)
/* Set the working copy administrative directory name. */
if (getenv("SVN_ASP_DOT_NET_HACK"))
{
- err = svn_wc_set_adm_dir("_svn", pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_wc_set_adm_dir("_svn", pool));
}
#endif
/* Initialize the RA library. */
- err = svn_ra_initialize(pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_ra_initialize(pool));
/* Init our changelists hash. */
changelists = apr_hash_make(pool);
@@ -1578,20 +1775,17 @@ main(int argc, const char *argv[])
opt_state.depth = svn_depth_unknown;
opt_state.set_depth = svn_depth_unknown;
opt_state.accept_which = svn_cl__accept_unspecified;
- opt_state.show_revs = svn_cl__show_revs_merged;
+ opt_state.show_revs = svn_cl__show_revs_invalid;
/* No args? Show usage. */
if (argc <= 1)
{
- svn_cl__help(NULL, NULL, pool);
- svn_pool_destroy(pool);
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
return EXIT_FAILURE;
}
/* Else, parse options. */
- err = svn_cmdline__getopt_init(&os, argc, argv, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
os->interleave = 1;
while (1)
@@ -1606,8 +1800,7 @@ main(int argc, const char *argv[])
break;
else if (apr_err)
{
- svn_cl__help(NULL, NULL, pool);
- svn_pool_destroy(pool);
+ SVN_INT_ERR(svn_cl__help(NULL, NULL, pool));
return EXIT_FAILURE;
}
@@ -1622,13 +1815,13 @@ main(int argc, const char *argv[])
{
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err,
_("Non-numeric limit argument given"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
if (opt_state.limit <= 0)
{
err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
_("Argument to --limit must be positive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
}
break;
@@ -1649,14 +1842,13 @@ main(int argc, const char *argv[])
err = svn_error_create
(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Can't specify -c with --old"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
for (i = 0; i < change_revs->nelts; i++)
{
char *end;
svn_revnum_t changeno, changeno_end;
- svn_opt_revision_range_t *range;
const char *change_str =
APR_ARRAY_IDX(change_revs, i, const char *);
const char *s = change_str;
@@ -1683,7 +1875,7 @@ main(int argc, const char *argv[])
_("Negative number in range (%s)"
" not supported with -c"),
change_str);
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
s = end + 1;
while (*s == 'r')
@@ -1695,14 +1887,14 @@ main(int argc, const char *argv[])
err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Non-numeric change argument (%s) "
"given to -c"), change_str);
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
if (changeno == 0)
{
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("There is no change 0"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
if (is_negative)
@@ -1728,15 +1920,11 @@ main(int argc, const char *argv[])
changeno_end = changeno - 1;
}
- range = apr_palloc(pool, sizeof(*range));
- range->start.value.number = changeno;
- range->end.value.number = changeno_end;
-
opt_state.used_change_arg = TRUE;
- range->start.kind = svn_opt_revision_number;
- range->end.kind = svn_opt_revision_number;
APR_ARRAY_PUSH(opt_state.revision_ranges,
- svn_opt_revision_range_t *) = range;
+ svn_opt_revision_range_t *)
+ = svn_opt__revision_range_from_revnums(changeno, changeno_end,
+ pool);
}
}
break;
@@ -1745,13 +1933,12 @@ main(int argc, const char *argv[])
if (svn_opt_parse_revision_to_range(opt_state.revision_ranges,
opt_arg, pool) != 0)
{
- err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
- if (! err)
- err = svn_error_createf
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ err = svn_error_createf
(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Syntax error in revision argument '%s'"),
utf8_opt_arg);
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
break;
case 'v':
@@ -1771,12 +1958,10 @@ main(int argc, const char *argv[])
opt_state.incremental = TRUE;
break;
case 'F':
- err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
- if (! err)
- err = svn_stringbuf_from_file2(&(opt_state.filedata),
- utf8_opt_arg, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
+ utf8_opt_arg, pool));
+ reading_file_from_stdin = (strcmp(utf8_opt_arg, "-") == 0);
dash_F_arg = opt_arg;
break;
case opt_targets:
@@ -1787,14 +1972,9 @@ main(int argc, const char *argv[])
the targets into an array, because otherwise we wouldn't
know what delimiter to use for svn_cstring_split(). */
- err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
-
- if (! err)
- err = svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool);
- if (! err)
- err = svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool));
+ SVN_INT_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r",
TRUE, pool);
}
@@ -1820,55 +2000,51 @@ main(int argc, const char *argv[])
case opt_depth:
err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
if (err)
- return svn_cmdline_handle_exit_error
- (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
_("Error converting depth "
- "from locale to UTF-8")), pool, "svn: ");
+ "from locale to UTF-8")));
opt_state.depth = svn_depth_from_word(utf8_opt_arg);
if (opt_state.depth == svn_depth_unknown
|| opt_state.depth == svn_depth_exclude)
{
- return svn_cmdline_handle_exit_error
+ return EXIT_ERROR
(svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'%s' is not a valid depth; try "
"'empty', 'files', 'immediates', "
"or 'infinity'"),
- utf8_opt_arg), pool, "svn: ");
+ utf8_opt_arg));
}
break;
case opt_set_depth:
err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
if (err)
- return svn_cmdline_handle_exit_error
- (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ return EXIT_ERROR
+ (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
_("Error converting depth "
- "from locale to UTF-8")), pool, "svn: ");
+ "from locale to UTF-8")));
opt_state.set_depth = svn_depth_from_word(utf8_opt_arg);
/* svn_depth_exclude is okay for --set-depth. */
if (opt_state.set_depth == svn_depth_unknown)
{
- return svn_cmdline_handle_exit_error
+ return EXIT_ERROR
(svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'%s' is not a valid depth; try "
"'exclude', 'empty', 'files', "
"'immediates', or 'infinity'"),
- utf8_opt_arg), pool, "svn: ");
+ utf8_opt_arg));
}
break;
case opt_version:
opt_state.version = TRUE;
break;
case opt_auth_username:
- err = svn_utf_cstring_to_utf8(&opt_state.auth_username,
- opt_arg, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
+ opt_arg, pool));
break;
case opt_auth_password:
- err = svn_utf_cstring_to_utf8(&opt_state.auth_password,
- opt_arg, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
+ opt_arg, pool));
break;
case opt_encoding:
opt_state.encoding = apr_pstrdup(pool, opt_arg);
@@ -1891,17 +2067,26 @@ main(int argc, const char *argv[])
case opt_non_interactive:
opt_state.non_interactive = TRUE;
break;
+ case opt_force_interactive:
+ force_interactive = TRUE;
+ break;
case opt_trust_server_cert:
opt_state.trust_server_cert = TRUE;
break;
+ case opt_no_diff_added:
+ opt_state.diff.no_diff_added = TRUE;
+ break;
case opt_no_diff_deleted:
- opt_state.no_diff_deleted = TRUE;
+ opt_state.diff.no_diff_deleted = TRUE;
+ break;
+ case opt_ignore_properties:
+ opt_state.diff.ignore_properties = TRUE;
break;
case opt_show_copies_as_adds:
- opt_state.show_copies_as_adds = TRUE;
+ opt_state.diff.show_copies_as_adds = TRUE;
break;
case opt_notice_ancestry:
- opt_state.notice_ancestry = TRUE;
+ opt_state.diff.notice_ancestry = TRUE;
break;
case opt_ignore_ancestry:
opt_state.ignore_ancestry = TRUE;
@@ -1913,12 +2098,11 @@ main(int argc, const char *argv[])
opt_state.relocate = TRUE;
break;
case 'x':
- err = svn_utf_cstring_to_utf8(&opt_state.extensions, opt_arg, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.extensions,
+ opt_arg, pool));
break;
case opt_diff_cmd:
- opt_state.diff_cmd = apr_pstrdup(pool, opt_arg);
+ opt_state.diff.diff_cmd = apr_pstrdup(pool, opt_arg);
break;
case opt_merge_cmd:
opt_state.merge_cmd = apr_pstrdup(pool, opt_arg);
@@ -1935,7 +2119,7 @@ main(int argc, const char *argv[])
err = svn_error_create
(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Can't specify -c with --old"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
opt_state.old_target = apr_pstrdup(pool, opt_arg);
break;
@@ -1945,9 +2129,7 @@ main(int argc, const char *argv[])
case opt_config_dir:
{
const char *path_utf8;
- err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool));
opt_state.config_dir = svn_dirent_internal_style(path_utf8, pool);
}
break;
@@ -1957,12 +2139,9 @@ main(int argc, const char *argv[])
apr_array_make(pool, 1,
sizeof(svn_cmdline__config_argument_t*));
- err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool);
- if (!err)
- err = svn_cmdline__parse_config_option(opt_state.config_options,
- opt_arg, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
+ opt_arg, pool));
break;
case opt_autoprops:
opt_state.autoprops = TRUE;
@@ -1976,34 +2155,33 @@ main(int argc, const char *argv[])
opt_state.native_eol = apr_pstrdup(pool, opt_arg);
else
{
- err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
- if (! err)
- err = svn_error_createf
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ err = svn_error_createf
(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Syntax error in native-eol argument '%s'"),
utf8_opt_arg);
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
break;
case opt_no_unlock:
opt_state.no_unlock = TRUE;
break;
case opt_summarize:
- opt_state.summarize = TRUE;
+ opt_state.diff.summarize = TRUE;
break;
case opt_remove:
opt_state.remove = TRUE;
break;
case opt_changelist:
- opt_state.changelist = apr_pstrdup(pool, opt_arg);
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ opt_state.changelist = utf8_opt_arg;
if (opt_state.changelist[0] == '\0')
{
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Changelist names must not be empty"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
- apr_hash_set(changelists, opt_state.changelist,
- APR_HASH_KEY_STRING, (void *)1);
+ svn_hash_sets(changelists, opt_state.changelist, (void *)1);
break;
case opt_keep_changelists:
opt_state.keep_changelists = TRUE;
@@ -2020,9 +2198,8 @@ main(int argc, const char *argv[])
opt_state.no_revprops = TRUE;
break;
case opt_with_revprop:
- err = svn_opt_parse_revprop(&opt_state.revprop_table, opt_arg, pool);
- if (err != SVN_NO_ERROR)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_opt_parse_revprop(&opt_state.revprop_table,
+ opt_arg, pool));
break;
case opt_parents:
opt_state.parents = TRUE;
@@ -2033,20 +2210,18 @@ main(int argc, const char *argv[])
case opt_accept:
opt_state.accept_which = svn_cl__accept_from_word(opt_arg);
if (opt_state.accept_which == svn_cl__accept_invalid)
- return svn_cmdline_handle_exit_error
+ return EXIT_ERROR
(svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'%s' is not a valid --accept value"),
- opt_arg),
- pool, "svn: ");
+ opt_arg));
break;
case opt_show_revs:
opt_state.show_revs = svn_cl__show_revs_from_word(opt_arg);
if (opt_state.show_revs == svn_cl__show_revs_invalid)
- return svn_cmdline_handle_exit_error
+ return EXIT_ERROR
(svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("'%s' is not a valid --show-revs value"),
- opt_arg),
- pool, "svn: ");
+ opt_arg));
break;
case opt_reintegrate:
opt_state.reintegrate = TRUE;
@@ -2058,13 +2233,13 @@ main(int argc, const char *argv[])
{
err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
_("Invalid strip count '%s'"), opt_arg);
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
if (opt_state.strip < 0)
{
err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
_("Argument to --strip must be positive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
}
break;
@@ -2081,14 +2256,31 @@ main(int argc, const char *argv[])
opt_state.show_diff = TRUE;
break;
case opt_internal_diff:
- opt_state.internal_diff = TRUE;
+ opt_state.diff.internal_diff = TRUE;
+ break;
+ case opt_patch_compatible:
+ opt_state.diff.patch_compatible = TRUE;
break;
case opt_use_git_diff_format:
- opt_state.use_git_diff_format = TRUE;
+ opt_state.diff.use_git_diff_format = TRUE;
break;
case opt_allow_mixed_revisions:
opt_state.allow_mixed_rev = TRUE;
break;
+ case opt_include_externals:
+ opt_state.include_externals = TRUE;
+ break;
+ case opt_show_inherited_props:
+ opt_state.show_inherited_props = TRUE;
+ break;
+ case opt_properties_only:
+ opt_state.diff.properties_only = TRUE;
+ break;
+ case opt_search:
+ add_search_pattern_group(&opt_state, opt_arg, pool);
+ break;
+ case opt_search_and:
+ add_search_pattern_to_latest_group(&opt_state, opt_arg, pool);
default:
/* Hmmm. Perhaps this would be a good place to squirrel away
opts that commands like svn diff might need. Hmmm indeed. */
@@ -2096,10 +2288,22 @@ main(int argc, const char *argv[])
}
}
+ /* The --non-interactive and --force-interactive options are mutually
+ * exclusive. */
+ if (opt_state.non_interactive && force_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--non-interactive and --force-interactive "
+ "are mutually exclusive"));
+ return EXIT_ERROR(err);
+ }
+ else
+ opt_state.non_interactive = !svn_cmdline__be_interactive(
+ opt_state.non_interactive,
+ force_interactive);
+
/* Turn our hash of changelists into an array of unique ones. */
- err = svn_hash_keys(&(opt_state.changelists), changelists, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_hash_keys(&(opt_state.changelists), changelists, pool));
/* ### This really belongs in libsvn_client. The trouble is,
there's no one place there to run it from, no
@@ -2111,9 +2315,7 @@ main(int argc, const char *argv[])
hand, the alternative is effectively to demand that they call
svn_config_ensure() instead, so maybe we should have a generic
init function anyway. Thoughts? */
- err = svn_config_ensure(opt_state.config_dir, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_config_ensure(opt_state.config_dir, pool));
/* If the user asked for help, then the rest of the arguments are
the names of subcommands to get help on (if any), or else they're
@@ -2135,6 +2337,7 @@ main(int argc, const char *argv[])
{ "--version", svn_cl__help, {0}, "",
{opt_version, /* must accept its own option */
'q', /* brief output */
+ 'v', /* verbose output */
opt_config_dir /* all commands accept this */
} };
@@ -2145,8 +2348,7 @@ main(int argc, const char *argv[])
svn_error_clear
(svn_cmdline_fprintf(stderr, pool,
_("Subcommand argument required\n")));
- svn_cl__help(NULL, NULL, pool);
- svn_pool_destroy(pool);
+ svn_error_clear(svn_cl__help(NULL, NULL, pool));
return EXIT_FAILURE;
}
}
@@ -2158,15 +2360,24 @@ main(int argc, const char *argv[])
if (subcommand == NULL)
{
const char *first_arg_utf8;
- err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
+ first_arg, pool));
svn_error_clear
(svn_cmdline_fprintf(stderr, pool,
- _("Unknown command: '%s'\n"),
+ _("Unknown subcommand: '%s'\n"),
first_arg_utf8));
- svn_cl__help(NULL, NULL, pool);
- svn_pool_destroy(pool);
+ svn_error_clear(svn_cl__help(NULL, NULL, pool));
+
+ /* Be kind to people who try 'svn undo'. */
+ if (strcmp(first_arg_utf8, "undo") == 0)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Undo is done using either the "
+ "'svn revert' or the 'svn merge' "
+ "command.\n")));
+ }
+
return EXIT_FAILURE;
}
}
@@ -2193,14 +2404,13 @@ main(int argc, const char *argv[])
subcommand, pool);
svn_opt_format_option(&optstr, badopt, FALSE, pool);
if (subcommand->name[0] == '-')
- svn_cl__help(NULL, NULL, pool);
+ svn_error_clear(svn_cl__help(NULL, NULL, pool));
else
svn_error_clear
(svn_cmdline_fprintf
(stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
"Type 'svn help %s' for usage.\n"),
subcommand->name, optstr, subcommand->name));
- svn_pool_destroy(pool);
return EXIT_FAILURE;
}
}
@@ -2215,7 +2425,7 @@ main(int argc, const char *argv[])
_("Multiple revision arguments "
"encountered; can't specify -c twice, "
"or both -c and -r"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
}
@@ -2226,7 +2436,7 @@ main(int argc, const char *argv[])
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--depth and --set-depth are mutually "
"exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
/* Disallow simultaneous use of both --with-all-revprops and
@@ -2236,7 +2446,7 @@ main(int argc, const char *argv[])
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--with-all-revprops and --with-no-revprops "
"are mutually exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
/* Disallow simultaneous use of both --with-revprop and
@@ -2246,12 +2456,12 @@ main(int argc, const char *argv[])
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--with-revprop and --with-no-revprops "
"are mutually exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
/* Disallow simultaneous use of both -m and -F, when they are
both used to pass a commit message or lock comment. ('propset'
- takes the property value, not a commit message, from -F.)
+ takes the property value, not a commit message, from -F.)
*/
if (opt_state.filedata && opt_state.message
&& subcommand->cmd_func != svn_cl__propset)
@@ -2259,7 +2469,7 @@ main(int argc, const char *argv[])
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--message (-m) and --file (-F) "
"are mutually exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
/* --trust-server-cert can only be used with --non-interactive */
@@ -2268,17 +2478,17 @@ main(int argc, const char *argv[])
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--trust-server-cert requires "
"--non-interactive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
/* Disallow simultaneous use of both --diff-cmd and
--internal-diff. */
- if (opt_state.diff_cmd && opt_state.internal_diff)
+ if (opt_state.diff.diff_cmd && opt_state.diff.internal_diff)
{
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--diff-cmd and --internal-diff "
"are mutually exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
/* Ensure that 'revision_ranges' has at least one item, and make
@@ -2296,15 +2506,133 @@ main(int argc, const char *argv[])
opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
svn_opt_revision_range_t *)->end;
+ err = svn_config_get_config(&cfg_hash, opt_state.config_dir, pool);
+ if (err)
+ {
+ /* Fallback to default config if the config directory isn't readable
+ or is not a directory. */
+ if (APR_STATUS_IS_EACCES(err->apr_err)
+ || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+ {
+ svn_config_t *empty_cfg;
+
+ svn_handle_warning2(stderr, err, "svn: ");
+ svn_error_clear(err);
+ cfg_hash = apr_hash_make(pool);
+ SVN_INT_ERR(svn_config_create2(&empty_cfg, FALSE, FALSE, pool));
+ svn_hash_sets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG, empty_cfg);
+ SVN_INT_ERR(svn_config_create2(&empty_cfg, FALSE, FALSE, pool));
+ svn_hash_sets(cfg_hash, SVN_CONFIG_CATEGORY_SERVERS, empty_cfg);
+ }
+ else
+ return EXIT_ERROR(err);
+ }
+
+ /* Relocation is infinite-depth only. */
+ if (opt_state.relocate)
+ {
+ if (opt_state.depth != svn_depth_unknown)
+ {
+ err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--relocate and --depth are mutually "
+ "exclusive"));
+ return EXIT_ERROR(err);
+ }
+ if (! descend)
+ {
+ err = svn_error_create(
+ SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("--relocate and --non-recursive (-N) are mutually "
+ "exclusive"));
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* Only a few commands can accept a revision range; the rest can take at
+ most one revision number. */
+ if (subcommand->cmd_func != svn_cl__blame
+ && subcommand->cmd_func != svn_cl__diff
+ && subcommand->cmd_func != svn_cl__log
+ && subcommand->cmd_func != svn_cl__mergeinfo
+ && subcommand->cmd_func != svn_cl__merge)
+ {
+ if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
+ return EXIT_ERROR(err);
+ }
+ }
+
+ /* -N has a different meaning depending on the command */
+ if (!descend)
+ {
+ if (subcommand->cmd_func == svn_cl__status)
+ {
+ opt_state.depth = svn_depth_immediates;
+ }
+ else if (subcommand->cmd_func == svn_cl__revert
+ || subcommand->cmd_func == svn_cl__add
+ || subcommand->cmd_func == svn_cl__commit)
+ {
+ /* In pre-1.5 Subversion, some commands treated -N like
+ --depth=empty, so force that mapping here. Anyway, with
+ revert it makes sense to be especially conservative,
+ since revert can lose data. */
+ opt_state.depth = svn_depth_empty;
+ }
+ else
+ {
+ opt_state.depth = svn_depth_files;
+ }
+ }
+
+ cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
+
+ /* Update the options in the config */
+ if (opt_state.config_options)
+ {
+ svn_error_clear(
+ svn_cmdline__apply_config_options(cfg_hash,
+ opt_state.config_options,
+ "svn: ", "--config-option"));
+ }
+
+#if !defined(SVN_CL_NO_EXCLUSIVE_LOCK)
+ {
+ const char *exclusive_clients_option;
+ apr_array_header_t *exclusive_clients;
+
+ svn_config_get(cfg_config, &exclusive_clients_option,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE_CLIENTS,
+ NULL);
+ exclusive_clients = svn_cstring_split(exclusive_clients_option,
+ " ,", TRUE, pool);
+ for (i = 0; i < exclusive_clients->nelts; ++i)
+ {
+ const char *exclusive_client = APR_ARRAY_IDX(exclusive_clients, i,
+ const char *);
+
+ /* This blocks other clients from accessing the wc.db so it must
+ be explicitly enabled.*/
+ if (!strcmp(exclusive_client, "svn"))
+ svn_config_set(cfg_config,
+ SVN_CONFIG_SECTION_WORKING_COPY,
+ SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
+ "true");
+ }
+ }
+#endif
+
/* Create a client context object. */
command_baton.opt_state = &opt_state;
- if ((err = svn_client_create_context(&ctx, pool)))
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
command_baton.ctx = ctx;
/* If we're running a command that could result in a commit, verify
that any log message we were given on the command line makes
- sense (unless we've also been instructed not to care). */
+ sense (unless we've also been instructed not to care). This may
+ access the working copy so do it after setting the locking mode. */
if ((! opt_state.force_log)
&& (subcommand->cmd_func == svn_cl__commit
|| subcommand->cmd_func == svn_cl__copy
@@ -2327,8 +2655,8 @@ main(int argc, const char *argv[])
if (!err)
{
- err = svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE,
- pool);
+ err = svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE,
+ FALSE, pool);
if (!err && kind != svn_node_none && kind != svn_node_unknown)
{
@@ -2346,7 +2674,7 @@ main(int argc, const char *argv[])
_("Lock comment file is a versioned file; "
"use '--force-log' to override"));
}
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
}
svn_error_clear(err);
@@ -2374,105 +2702,20 @@ main(int argc, const char *argv[])
_("The lock comment is a pathname "
"(was -F intended?); use '--force-log' to override"));
}
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ return EXIT_ERROR(err);
}
}
}
- /* Relocation is infinite-depth only. */
- if (opt_state.relocate)
- {
- if (opt_state.depth != svn_depth_unknown)
- {
- err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
- _("--relocate and --depth are mutually "
- "exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
- if (! descend)
- {
- err = svn_error_create(
- SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
- _("--relocate and --non-recursive (-N) are mutually "
- "exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
- }
-
- /* Only a few commands can accept a revision range; the rest can take at
- most one revision number. */
- if (subcommand->cmd_func != svn_cl__blame
- && subcommand->cmd_func != svn_cl__diff
- && subcommand->cmd_func != svn_cl__log
- && subcommand->cmd_func != svn_cl__merge)
- {
- if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
- {
- err = svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
- }
-
- /* -N has a different meaning depending on the command */
- if (descend == FALSE)
- {
- if (subcommand->cmd_func == svn_cl__status)
- {
- opt_state.depth = svn_depth_immediates;
- }
- else if (subcommand->cmd_func == svn_cl__revert
- || subcommand->cmd_func == svn_cl__add
- || subcommand->cmd_func == svn_cl__commit)
- {
- /* In pre-1.5 Subversion, some commands treated -N like
- --depth=empty, so force that mapping here. Anyway, with
- revert it makes sense to be especially conservative,
- since revert can lose data. */
- opt_state.depth = svn_depth_empty;
- }
- else
- {
- opt_state.depth = svn_depth_files;
- }
- }
-
- err = svn_config_get_config(&(ctx->config),
- opt_state.config_dir, pool);
- if (err)
- {
- /* Fallback to default config if the config directory isn't readable
- or is not a directory. */
- if (APR_STATUS_IS_EACCES(err->apr_err)
- || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
- {
- svn_handle_warning2(stderr, err, "svn: ");
- svn_error_clear(err);
- }
- else
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
-
- cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING);
-
- /* Update the options in the config */
- if (opt_state.config_options)
- {
- svn_error_clear(
- svn_cmdline__apply_config_options(ctx->config,
- opt_state.config_options,
- "svn: ", "--config-option"));
- }
-
/* XXX: Only diff_cmd for now, overlay rest later and stop passing
opt_state altogether? */
- if (opt_state.diff_cmd)
+ if (opt_state.diff.diff_cmd)
svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS,
- SVN_CONFIG_OPTION_DIFF_CMD, opt_state.diff_cmd);
+ SVN_CONFIG_OPTION_DIFF_CMD, opt_state.diff.diff_cmd);
if (opt_state.merge_cmd)
svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS,
SVN_CONFIG_OPTION_DIFF3_CMD, opt_state.merge_cmd);
- if (opt_state.internal_diff)
+ if (opt_state.diff.internal_diff)
svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS,
SVN_CONFIG_OPTION_DIFF_CMD, NULL);
@@ -2482,38 +2725,7 @@ main(int argc, const char *argv[])
err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
_("--auto-props and --no-auto-props are "
"mutually exclusive"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
-
- /* The --reintegrate option is mutually exclusive with both
- --ignore-ancestry and --record-only. */
- if (opt_state.reintegrate)
- {
- if (opt_state.ignore_ancestry)
- {
- if (opt_state.record_only)
- {
- err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
- _("--reintegrate cannot be used with "
- "--ignore-ancestry or "
- "--record-only"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
- else
- {
- err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
- _("--reintegrate cannot be used with "
- "--ignore-ancestry"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
- }
- else if (opt_state.record_only)
- {
- err = svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
- _("--reintegrate cannot be used with "
- "--record-only"));
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
- }
+ return EXIT_ERROR(err);
}
/* Update auto-props-enable option, and populate the MIME types map,
@@ -2527,9 +2739,8 @@ main(int argc, const char *argv[])
SVN_CONFIG_OPTION_MIMETYPES_FILE, FALSE);
if (mimetypes_file && *mimetypes_file)
{
- if ((err = svn_io_parse_mimetypes_file(&(ctx->mimetypes_map),
- mimetypes_file, pool)))
- svn_handle_error2(err, stderr, TRUE, "svn: ");
+ SVN_INT_ERR(svn_io_parse_mimetypes_file(&(ctx->mimetypes_map),
+ mimetypes_file, pool));
}
if (opt_state.autoprops)
@@ -2553,14 +2764,22 @@ main(int argc, const char *argv[])
subcommands will populate the ctx->log_msg_baton3. */
ctx->log_msg_func3 = svn_cl__get_log_message;
- /* Set up the notifier. */
- if (((subcommand->cmd_func != svn_cl__status) && !opt_state.quiet)
- || ((subcommand->cmd_func == svn_cl__status) && !opt_state.xml))
+ /* Set up the notifier.
+
+ In general, we use it any time we aren't in --quiet mode. 'svn
+ status' is unique, though, in that we don't want it in --quiet mode
+ unless we're also in --verbose mode. When in --xml mode,
+ though, we never want it. */
+ if (opt_state.quiet)
+ use_notifier = FALSE;
+ if ((subcommand->cmd_func == svn_cl__status) && opt_state.verbose)
+ use_notifier = TRUE;
+ if (opt_state.xml)
+ use_notifier = FALSE;
+ if (use_notifier)
{
- err = svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
- FALSE, pool);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
+ conflict_stats, pool));
}
/* Set up our cancellation support. */
@@ -2590,71 +2809,77 @@ main(int argc, const char *argv[])
#endif
/* Set up Authentication stuff. */
- if ((err = svn_cmdline_create_auth_baton(&ab,
- opt_state.non_interactive,
- opt_state.auth_username,
- opt_state.auth_password,
- opt_state.config_dir,
- opt_state.no_auth_cache,
- opt_state.trust_server_cert,
- cfg_config,
- ctx->cancel_func,
- ctx->cancel_baton,
- pool)))
- svn_handle_error2(err, stderr, TRUE, "svn: ");
+ SVN_INT_ERR(svn_cmdline_create_auth_baton(&ab,
+ opt_state.non_interactive,
+ opt_state.auth_username,
+ opt_state.auth_password,
+ opt_state.config_dir,
+ opt_state.no_auth_cache,
+ opt_state.trust_server_cert,
+ cfg_config,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ pool));
ctx->auth_baton = ab;
- /* Set up conflict resolution callback. */
- if ((err = svn_config_get_bool(cfg_config, &interactive_conflicts,
- SVN_CONFIG_SECTION_MISCELLANY,
- SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS,
- TRUE))) /* ### interactivity on by default.
- we can change this. */
- svn_handle_error2(err, stderr, TRUE, "svn: ");
-
- if ((opt_state.accept_which == svn_cl__accept_unspecified
- && (!interactive_conflicts || opt_state.non_interactive))
- || opt_state.accept_which == svn_cl__accept_postpone)
+ if (opt_state.non_interactive)
{
- /* If no --accept option at all and we're non-interactive, we're
- leaving the conflicts behind, so don't need the callback. Same if
- the user said to postpone. */
- ctx->conflict_func = NULL;
- ctx->conflict_baton = NULL;
+ if (opt_state.accept_which == svn_cl__accept_edit)
+ return EXIT_ERROR(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=%s incompatible with"
+ " --non-interactive"),
+ SVN_CL__ACCEPT_EDIT));
+
+ if (opt_state.accept_which == svn_cl__accept_launch)
+ return EXIT_ERROR(
+ svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--accept=%s incompatible with"
+ " --non-interactive"),
+ SVN_CL__ACCEPT_LAUNCH));
+
+ /* The default action when we're non-interactive is to postpone
+ * conflict resolution. */
+ if (opt_state.accept_which == svn_cl__accept_unspecified)
+ opt_state.accept_which = svn_cl__accept_postpone;
}
- else
- {
- svn_cmdline_prompt_baton_t *pb = apr_palloc(pool, sizeof(*pb));
- pb->cancel_func = ctx->cancel_func;
- pb->cancel_baton = ctx->cancel_baton;
- if (opt_state.non_interactive)
- {
- if (opt_state.accept_which == svn_cl__accept_edit)
- return svn_cmdline_handle_exit_error
- (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("--accept=%s incompatible with"
- " --non-interactive"), SVN_CL__ACCEPT_EDIT),
- pool, "svn: ");
- if (opt_state.accept_which == svn_cl__accept_launch)
- return svn_cmdline_handle_exit_error
- (svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
- _("--accept=%s incompatible with"
- " --non-interactive"),
- SVN_CL__ACCEPT_LAUNCH),
- pool, "svn: ");
- }
+ /* Check whether interactive conflict resolution is disabled by
+ * the configuration file. If no --accept option was specified
+ * we postpone all conflicts in this case. */
+ SVN_INT_ERR(svn_config_get_bool(cfg_config, &interactive_conflicts,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_INTERACTIVE_CONFLICTS,
+ TRUE));
+ if (!interactive_conflicts)
+ {
+ /* Make 'svn resolve' non-interactive. */
+ if (subcommand->cmd_func == svn_cl__resolve)
+ opt_state.non_interactive = TRUE;
- ctx->conflict_func = svn_cl__conflict_handler;
- ctx->conflict_baton = svn_cl__conflict_baton_make(
- opt_state.accept_which,
- ctx->config,
- opt_state.editor_cmd,
- pb,
- pool);
+ /* We're not resolving conflicts interactively. If no --accept option
+ * was provided the default behaviour is to postpone all conflicts. */
+ if (opt_state.accept_which == svn_cl__accept_unspecified)
+ opt_state.accept_which = svn_cl__accept_postpone;
}
+ /* Install the default conflict handler. */
+ {
+ svn_cl__interactive_conflict_baton_t *b;
+
+ ctx->conflict_func = NULL;
+ ctx->conflict_baton = NULL;
+
+ ctx->conflict_func2 = svn_cl__conflict_func_interactive;
+ SVN_INT_ERR(svn_cl__get_conflict_func_interactive_baton(
+ &b,
+ opt_state.accept_which,
+ ctx->config, opt_state.editor_cmd, conflict_stats,
+ ctx->cancel_func, ctx->cancel_baton, pool));
+ ctx->conflict_baton2 = b;
+ }
+
/* And now we finally run the subcommand. */
err = (*subcommand->cmd_func)(os, &command_baton, pool);
if (err)
@@ -2664,8 +2889,10 @@ main(int argc, const char *argv[])
if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
|| err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
{
- err = svn_error_quick_wrap(err,
- _("Try 'svn help' for more info"));
+ err = svn_error_quick_wrap(
+ err, apr_psprintf(pool,
+ _("Try 'svn help %s' for more information"),
+ subcommand->name));
}
if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
{
@@ -2673,37 +2900,84 @@ main(int argc, const char *argv[])
_("Please see the 'svn upgrade' command"));
}
- /* 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: ");
+ if (err->apr_err == SVN_ERR_AUTHN_FAILED && opt_state.non_interactive)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Authentication failed and interactive"
+ " prompting is disabled; see the"
+ " --force-interactive option"));
+ if (reading_file_from_stdin)
+ err = svn_error_quick_wrap(err,
+ _("Reading file from standard input "
+ "because of -F option; this can "
+ "interfere with interactive "
+ "prompting"));
+ }
/* Tell the user about 'svn cleanup' if any error on the stack
was about locked working copies. */
if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
- svn_error_clear(svn_cmdline_fputs(_("svn: run 'svn cleanup' to "
- "remove locks (type 'svn help "
- "cleanup' for details)\n"),
- stderr, pool));
+ {
+ err = svn_error_quick_wrap(
+ err, _("Run 'svn cleanup' to remove locks "
+ "(type 'svn help cleanup' for details)"));
+ }
- svn_error_clear(err);
- svn_pool_destroy(pool);
- return EXIT_FAILURE;
+ if (err->apr_err == SVN_ERR_SQLITE_BUSY)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Another process is blocking the "
+ "working copy database, or the "
+ "underlying filesystem does not "
+ "support file locking; if the working "
+ "copy is on a network filesystem, make "
+ "sure file locking has been enabled "
+ "on the file server"));
+ }
+
+ if (svn_error_find_cause(err, SVN_ERR_RA_CANNOT_CREATE_TUNNEL) &&
+ (opt_state.auth_username || opt_state.auth_password))
+ {
+ err = svn_error_quick_wrap(
+ err, _("When using svn+ssh:// URLs, keep in mind that the "
+ "--username and --password options are ignored "
+ "because authentication is performed by SSH, not "
+ "Subversion"));
+ }
+
+ /* Ensure that stdout is flushed, so the user will see any write errors.
+ This makes sure that output is not silently lost. */
+ err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
+
+ return EXIT_ERROR(err);
}
else
{
/* Ensure that stdout is flushed, so the user will see any write errors.
This makes sure that output is not silently lost. */
- err = svn_cmdline_fflush(stdout);
- if (err)
- return svn_cmdline_handle_exit_error(err, pool, "svn: ");
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
- svn_pool_destroy(pool);
return EXIT_SUCCESS;
}
}
+
+int
+main(int argc, const char *argv[])
+{
+ apr_pool_t *pool;
+ int exit_code;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svn", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ exit_code = sub_main(argc, argv, pool);
+
+ svn_pool_destroy(pool);
+ return exit_code;
+}
diff --git a/subversion/svn/switch-cmd.c b/subversion/svn/switch-cmd.c
index 9c3bc14..aaef2b5 100644
--- a/subversion/svn/switch-cmd.c
+++ b/subversion/svn/switch-cmd.c
@@ -93,7 +93,8 @@ svn_cl__switch(apr_getopt_t *os,
void *baton,
apr_pool_t *scratch_pool)
{
- svn_error_t *err;
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_error_t *externals_err = SVN_NO_ERROR;
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;
@@ -172,16 +173,27 @@ svn_cl__switch(apr_getopt_t *os,
"disable this check."),
svn_dirent_local_style(target,
scratch_pool));
+ if (err->apr_err == SVN_ERR_RA_UUID_MISMATCH
+ || err->apr_err == SVN_ERR_WC_INVALID_SWITCH)
+ return svn_error_quick_wrap(
+ err,
+ _("'svn switch' does not support switching a working copy to "
+ "a different repository"));
return err;
}
- if (! opt_state->quiet)
- SVN_ERR(svn_cl__print_conflict_stats(nwb.wrapped_baton, scratch_pool));
-
if (nwb.had_externals_error)
- return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL,
- _("Failure occurred processing one or more "
- "externals definitions"));
+ externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
+ NULL,
+ _("Failure occurred processing one or "
+ "more externals definitions"));
- return SVN_NO_ERROR;
+ if (! opt_state->quiet)
+ {
+ err = svn_cl__notifier_print_conflict_stats(nwb.wrapped_baton, scratch_pool);
+ if (err)
+ return svn_error_compose_create(externals_err, err);
+ }
+
+ return svn_error_compose_create(externals_err, err);
}
diff --git a/subversion/svn/tree-conflicts.c b/subversion/svn/tree-conflicts.c
deleted file mode 100644
index b374924..0000000
--- a/subversion/svn/tree-conflicts.c
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * tree-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 "tree-conflicts.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 human-readable strings */
-static const svn_token_map_t map_conflict_action_human[] =
-{
- { N_("edit"), svn_wc_conflict_action_edit },
- { N_("delete"), svn_wc_conflict_action_delete },
- { N_("add"), svn_wc_conflict_action_add },
- { N_("replace"), svn_wc_conflict_action_replace },
- { NULL, 0 }
-};
-
-/* 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 human-readable strings */
-static const svn_token_map_t map_conflict_reason_human[] =
-{
- { N_("edit"), svn_wc_conflict_reason_edited },
- { N_("delete"), svn_wc_conflict_reason_deleted },
- { N_("missing"), svn_wc_conflict_reason_missing },
- { N_("obstruction"), svn_wc_conflict_reason_obstructed },
- { N_("add"), svn_wc_conflict_reason_added },
- { N_("replace"), svn_wc_conflict_reason_replaced },
- { N_("unversioned"), svn_wc_conflict_reason_unversioned },
- { 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 },
- { NULL, 0 }
-};
-
-/* Return a localized string representation of CONFLICT->action. */
-static const char *
-action_str(const svn_wc_conflict_description2_t *conflict)
-{
- return _(svn_token__to_word(map_conflict_action_human, conflict->action));
-}
-
-/* Return a localized string representation of CONFLICT->reason. */
-static const char *
-reason_str(const svn_wc_conflict_description2_t *conflict)
-{
- return _(svn_token__to_word(map_conflict_reason_human, conflict->reason));
-}
-
-
-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;
- reason = reason_str(conflict);
- action = action_str(conflict);
- operation = svn_cl__operation_str_human_readable(conflict->operation, pool);
- SVN_ERR_ASSERT(action && reason);
- *desc = apr_psprintf(pool, _("local %s, incoming %s upon %s"),
- reason, 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);
-
-
- apr_hash_set(att_hash, "side", APR_HASH_KEY_STRING, side);
-
- if (version->repos_url)
- apr_hash_set(att_hash, "repos-url", APR_HASH_KEY_STRING,
- version->repos_url);
-
- if (version->path_in_repos)
- apr_hash_set(att_hash, "path-in-repos", APR_HASH_KEY_STRING,
- version->path_in_repos);
-
- if (SVN_IS_VALID_REVNUM(version->peg_rev))
- apr_hash_set(att_hash, "revision", APR_HASH_KEY_STRING,
- apr_ltoa(pool, version->peg_rev));
-
- if (version->node_kind != svn_node_unknown)
- apr_hash_set(att_hash, "kind", APR_HASH_KEY_STRING,
- 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;
-}
-
-
-svn_error_t *
-svn_cl__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;
-
- apr_hash_set(att_hash, "victim", APR_HASH_KEY_STRING,
- svn_dirent_basename(conflict->local_abspath, pool));
-
- apr_hash_set(att_hash, "kind", APR_HASH_KEY_STRING,
- svn_cl__node_kind_str_xml(conflict->node_kind));
-
- apr_hash_set(att_hash, "operation", APR_HASH_KEY_STRING,
- svn_cl__operation_str_xml(conflict->operation, pool));
-
- tmp = svn_token__to_word(map_conflict_action_xml, conflict->action);
- apr_hash_set(att_hash, "action", APR_HASH_KEY_STRING, tmp);
-
- tmp = svn_token__to_word(map_conflict_reason_xml, conflict->reason);
- apr_hash_set(att_hash, "reason", APR_HASH_KEY_STRING, 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;
-}
-
diff --git a/subversion/svn/update-cmd.c b/subversion/svn/update-cmd.c
index 68d01f0..77c28f9 100644
--- a/subversion/svn/update-cmd.c
+++ b/subversion/svn/update-cmd.c
@@ -51,12 +51,12 @@ print_update_summary(apr_array_header_t *targets,
int i;
const char *path_prefix;
apr_pool_t *iterpool;
+ svn_boolean_t printed_header = FALSE;
if (targets->nelts < 2)
return SVN_NO_ERROR;
SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", scratch_pool));
- SVN_ERR(svn_cmdline_printf(scratch_pool, _("Summary of updates:\n")));
iterpool = svn_pool_create(scratch_pool);
@@ -87,6 +87,13 @@ print_update_summary(apr_array_header_t *targets,
/* Print an update summary for this target, removing the current
working directory prefix from PATH (if PATH is at or under
$CWD), and converting the path to local style for display. */
+ if (! printed_header)
+ {
+ SVN_ERR(svn_cmdline_printf(scratch_pool,
+ _("Summary of updates:\n")));
+ printed_header = TRUE;
+ }
+
SVN_ERR(svn_cmdline_printf(iterpool, _(" Updated '%s' to r%ld.\n"),
svn_cl__local_style_skip_ancestor(
path_prefix, path, iterpool),
@@ -110,6 +117,8 @@ svn_cl__update(apr_getopt_t *os,
svn_boolean_t depth_is_sticky;
struct svn_cl__check_externals_failed_notify_baton nwb;
apr_array_header_t *result_revs;
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_error_t *externals_err = SVN_NO_ERROR;
SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
opt_state->targets,
@@ -162,20 +171,26 @@ svn_cl__update(apr_getopt_t *os,
opt_state->parents,
ctx, scratch_pool));
+ if (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->quiet)
{
- SVN_ERR(print_update_summary(targets, result_revs, scratch_pool));
+ err = print_update_summary(targets, result_revs, scratch_pool);
+ if (err)
+ return svn_error_compose_create(externals_err, err);
/* ### Layering problem: This call assumes that the baton we're
* passing is the one that was originally provided by
* svn_cl__get_notifier(), but that isn't promised. */
- SVN_ERR(svn_cl__print_conflict_stats(nwb.wrapped_baton, scratch_pool));
+ err = svn_cl__notifier_print_conflict_stats(nwb.wrapped_baton,
+ scratch_pool);
+ if (err)
+ return svn_error_compose_create(externals_err, err);
}
- if (nwb.had_externals_error)
- return svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, NULL,
- _("Failure occurred processing one or more "
- "externals definitions"));
-
- return SVN_NO_ERROR;
+ return svn_error_compose_create(externals_err, err);
}
diff --git a/subversion/svn/util.c b/subversion/svn/util.c
index be8de9a..092bc7e 100644
--- a/subversion/svn/util.c
+++ b/subversion/svn/util.c
@@ -54,14 +54,17 @@
#include "svn_utf.h"
#include "svn_subst.h"
#include "svn_config.h"
+#include "svn_wc.h"
#include "svn_xml.h"
#include "svn_time.h"
+#include "svn_props.h"
#include "svn_private_config.h"
#include "cl.h"
#include "private/svn_token.h"
#include "private/svn_opt_private.h"
#include "private/svn_client_private.h"
+#include "private/svn_cmdline_private.h"
#include "private/svn_string_private.h"
@@ -93,125 +96,6 @@ svn_cl__print_commit_info(const svn_commit_info_t *commit_info,
}
-/* Helper for the next two functions. Set *EDITOR to some path to an
- editor binary. Sources to search include: the EDITOR_CMD argument
- (if not NULL), $SVN_EDITOR, the runtime CONFIG variable (if CONFIG
- is not NULL), $VISUAL, $EDITOR. Return
- SVN_ERR_CL_NO_EXTERNAL_EDITOR if no binary can be found. */
-static svn_error_t *
-find_editor_binary(const char **editor,
- const char *editor_cmd,
- apr_hash_t *config)
-{
- const char *e;
- struct svn_config_t *cfg;
-
- /* Use the editor specified on the command line via --editor-cmd, if any. */
- e = editor_cmd;
-
- /* Otherwise look for the Subversion-specific environment variable. */
- if (! e)
- e = getenv("SVN_EDITOR");
-
- /* If not found then fall back on the config file. */
- if (! e)
- {
- cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING) : NULL;
- svn_config_get(cfg, &e, SVN_CONFIG_SECTION_HELPERS,
- SVN_CONFIG_OPTION_EDITOR_CMD, NULL);
- }
-
- /* If not found yet then try general purpose environment variables. */
- if (! e)
- e = getenv("VISUAL");
- if (! e)
- e = getenv("EDITOR");
-
-#ifdef SVN_CLIENT_EDITOR
- /* If still not found then fall back on the hard-coded default. */
- if (! e)
- e = SVN_CLIENT_EDITOR;
-#endif
-
- /* Error if there is no editor specified */
- if (e)
- {
- const char *c;
-
- for (c = e; *c; c++)
- if (!svn_ctype_isspace(*c))
- break;
-
- if (! *c)
- return svn_error_create
- (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
- _("The EDITOR, SVN_EDITOR or VISUAL environment variable or "
- "'editor-cmd' run-time configuration option is empty or "
- "consists solely of whitespace. Expected a shell command."));
- }
- else
- return svn_error_create
- (SVN_ERR_CL_NO_EXTERNAL_EDITOR, NULL,
- _("None of the environment variables SVN_EDITOR, VISUAL or EDITOR are "
- "set, and no 'editor-cmd' run-time configuration option was found"));
-
- *editor = e;
- return SVN_NO_ERROR;
-}
-
-
-/* Use the visual editor to edit files. This requires that the file name itself
- be shell-safe, although the path to reach that file need not be shell-safe.
- */
-svn_error_t *
-svn_cl__edit_file_externally(const char *path,
- const char *editor_cmd,
- apr_hash_t *config,
- apr_pool_t *pool)
-{
- const char *editor, *cmd, *base_dir, *file_name, *base_dir_apr;
- char *old_cwd;
- int sys_err;
- apr_status_t apr_err;
-
- svn_dirent_split(&base_dir, &file_name, path, pool);
-
- SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
-
- apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
- if (apr_err)
- return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
-
- /* APR doesn't like "" directories */
- if (base_dir[0] == '\0')
- base_dir_apr = ".";
- else
- SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
-
- apr_err = apr_filepath_set(base_dir_apr, pool);
- if (apr_err)
- return svn_error_wrap_apr
- (apr_err, _("Can't change working directory to '%s'"), base_dir);
-
- cmd = apr_psprintf(pool, "%s %s", editor, file_name);
- sys_err = system(cmd);
-
- apr_err = apr_filepath_set(old_cwd, pool);
- if (apr_err)
- svn_handle_error2(svn_error_wrap_apr
- (apr_err, _("Can't restore working directory")),
- stderr, TRUE /* fatal */, "svn: ");
-
- if (sys_err)
- /* Extracting any meaning from sys_err is platform specific, so just
- use the raw value. */
- return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
- _("system('%s') returned %d"), cmd, sys_err);
-
- return SVN_NO_ERROR;
-}
-
svn_error_t *
svn_cl__merge_file_externally(const char *base_path,
const char *their_path,
@@ -228,8 +112,7 @@ svn_cl__merge_file_externally(const char *base_path,
{
struct svn_config_t *cfg;
merge_tool = NULL;
- cfg = config ? apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING) : NULL;
+ cfg = config ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) : NULL;
/* apr_env_get wants char **, this wants const char ** */
svn_config_get(cfg, (const char **)&merge_tool,
SVN_CONFIG_SECTION_HELPERS,
@@ -290,248 +173,6 @@ svn_cl__merge_file_externally(const char *base_path,
return SVN_NO_ERROR;
}
-svn_error_t *
-svn_cl__edit_string_externally(svn_string_t **edited_contents /* UTF-8! */,
- const char **tmpfile_left /* UTF-8! */,
- const char *editor_cmd,
- const char *base_dir /* UTF-8! */,
- const svn_string_t *contents /* UTF-8! */,
- const char *filename,
- apr_hash_t *config,
- svn_boolean_t as_text,
- const char *encoding,
- apr_pool_t *pool)
-{
- const char *editor;
- const char *cmd;
- apr_file_t *tmp_file;
- const char *tmpfile_name;
- const char *tmpfile_native;
- const char *tmpfile_apr, *base_dir_apr;
- svn_string_t *translated_contents;
- apr_status_t apr_err, apr_err2;
- apr_size_t written;
- apr_finfo_t finfo_before, finfo_after;
- svn_error_t *err = SVN_NO_ERROR, *err2;
- char *old_cwd;
- int sys_err;
- svn_boolean_t remove_file = TRUE;
-
- SVN_ERR(find_editor_binary(&editor, editor_cmd, config));
-
- /* Convert file contents from UTF-8/LF if desired. */
- if (as_text)
- {
- const char *translated;
- SVN_ERR(svn_subst_translate_cstring2(contents->data, &translated,
- APR_EOL_STR, FALSE,
- NULL, FALSE, pool));
- translated_contents = svn_string_create("", pool);
- if (encoding)
- SVN_ERR(svn_utf_cstring_from_utf8_ex2(&translated_contents->data,
- translated, encoding, pool));
- else
- SVN_ERR(svn_utf_cstring_from_utf8(&translated_contents->data,
- translated, pool));
- translated_contents->len = strlen(translated_contents->data);
- }
- else
- translated_contents = svn_string_dup(contents, pool);
-
- /* Move to BASE_DIR to avoid getting characters that need quoting
- into tmpfile_name */
- apr_err = apr_filepath_get(&old_cwd, APR_FILEPATH_NATIVE, pool);
- if (apr_err)
- return svn_error_wrap_apr(apr_err, _("Can't get working directory"));
-
- /* APR doesn't like "" directories */
- if (base_dir[0] == '\0')
- base_dir_apr = ".";
- else
- SVN_ERR(svn_path_cstring_from_utf8(&base_dir_apr, base_dir, pool));
- apr_err = apr_filepath_set(base_dir_apr, pool);
- if (apr_err)
- {
- return svn_error_wrap_apr
- (apr_err, _("Can't change working directory to '%s'"), base_dir);
- }
-
- /*** From here on, any problems that occur require us to cd back!! ***/
-
- /* Ask the working copy for a temporary file named FILENAME-something. */
- err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
- "" /* dirpath */,
- filename,
- ".tmp",
- svn_io_file_del_none, pool, pool);
-
- if (err && (APR_STATUS_IS_EACCES(err->apr_err) || err->apr_err == EROFS))
- {
- const char *temp_dir_apr;
-
- svn_error_clear(err);
-
- SVN_ERR(svn_io_temp_dir(&base_dir, pool));
-
- SVN_ERR(svn_path_cstring_from_utf8(&temp_dir_apr, base_dir, pool));
- apr_err = apr_filepath_set(temp_dir_apr, pool);
- if (apr_err)
- {
- return svn_error_wrap_apr
- (apr_err, _("Can't change working directory to '%s'"), base_dir);
- }
-
- err = svn_io_open_uniquely_named(&tmp_file, &tmpfile_name,
- "" /* dirpath */,
- filename,
- ".tmp",
- svn_io_file_del_none, pool, pool);
- }
-
- if (err)
- goto cleanup2;
-
- /*** From here on, any problems that occur require us to cleanup
- the file we just created!! ***/
-
- /* Dump initial CONTENTS to TMP_FILE. */
- apr_err = apr_file_write_full(tmp_file, translated_contents->data,
- translated_contents->len, &written);
-
- apr_err2 = apr_file_close(tmp_file);
- if (! apr_err)
- apr_err = apr_err2;
-
- /* Make sure the whole CONTENTS were written, else return an error. */
- if (apr_err)
- {
- err = svn_error_wrap_apr(apr_err, _("Can't write to '%s'"),
- tmpfile_name);
- goto cleanup;
- }
-
- err = svn_path_cstring_from_utf8(&tmpfile_apr, tmpfile_name, pool);
- if (err)
- goto cleanup;
-
- /* Get information about the temporary file before the user has
- been allowed to edit its contents. */
- apr_err = apr_stat(&finfo_before, tmpfile_apr,
- APR_FINFO_MTIME, pool);
- if (apr_err)
- {
- err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
- goto cleanup;
- }
-
- /* Backdate the file a little bit in case the editor is very fast
- and doesn't change the size. (Use two seconds, since some
- filesystems have coarse granularity.) It's OK if this call
- fails, so we don't check its return value.*/
- apr_file_mtime_set(tmpfile_apr, finfo_before.mtime - 2000, pool);
-
- /* Stat it again to get the mtime we actually set. */
- apr_err = apr_stat(&finfo_before, tmpfile_apr,
- APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
- if (apr_err)
- {
- err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
- goto cleanup;
- }
-
- /* Prepare the editor command line. */
- err = svn_utf_cstring_from_utf8(&tmpfile_native, tmpfile_name, pool);
- if (err)
- goto cleanup;
- cmd = apr_psprintf(pool, "%s %s", editor, tmpfile_native);
-
- /* If the caller wants us to leave the file around, return the path
- of the file we'll use, and make a note not to destroy it. */
- if (tmpfile_left)
- {
- *tmpfile_left = svn_dirent_join(base_dir, tmpfile_name, pool);
- remove_file = FALSE;
- }
-
- /* Now, run the editor command line. */
- sys_err = system(cmd);
- if (sys_err != 0)
- {
- /* Extracting any meaning from sys_err is platform specific, so just
- use the raw value. */
- err = svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, NULL,
- _("system('%s') returned %d"), cmd, sys_err);
- goto cleanup;
- }
-
- /* Get information about the temporary file after the assumed editing. */
- apr_err = apr_stat(&finfo_after, tmpfile_apr,
- APR_FINFO_MTIME | APR_FINFO_SIZE, pool);
- if (apr_err)
- {
- err = svn_error_wrap_apr(apr_err, _("Can't stat '%s'"), tmpfile_name);
- goto cleanup;
- }
-
- /* If the file looks changed... */
- if ((finfo_before.mtime != finfo_after.mtime) ||
- (finfo_before.size != finfo_after.size))
- {
- svn_stringbuf_t *edited_contents_s;
- err = svn_stringbuf_from_file2(&edited_contents_s, tmpfile_name, pool);
- if (err)
- goto cleanup;
-
- *edited_contents = svn_stringbuf__morph_into_string(edited_contents_s);
-
- /* Translate back to UTF8/LF if desired. */
- if (as_text)
- {
- err = svn_subst_translate_string2(edited_contents, FALSE, FALSE,
- *edited_contents, encoding, FALSE,
- pool, pool);
- if (err)
- {
- err = svn_error_quick_wrap
- (err,
- _("Error normalizing edited contents to internal format"));
- goto cleanup;
- }
- }
- }
- else
- {
- /* No edits seem to have been made */
- *edited_contents = NULL;
- }
-
- cleanup:
- if (remove_file)
- {
- /* Remove the file from disk. */
- err2 = svn_io_remove_file2(tmpfile_name, FALSE, pool);
-
- /* Only report remove error if there was no previous error. */
- if (! err && err2)
- err = err2;
- else
- svn_error_clear(err2);
- }
-
- cleanup2:
- /* If we against all probability can't cd back, all further relative
- file references would be screwed up, so we have to abort. */
- apr_err = apr_filepath_set(old_cwd, pool);
- if (apr_err)
- {
- svn_handle_error2(svn_error_wrap_apr
- (apr_err, _("Can't restore working directory")),
- stderr, TRUE /* fatal */, "svn: ");
- }
-
- return svn_error_trace(err);
-}
-
/* A svn_client_ctx_t's log_msg_baton3, for use with
svn_cl__make_log_msg_baton(). */
@@ -583,8 +224,7 @@ svn_cl__make_log_msg_baton(void **baton,
}
else if (config)
{
- svn_config_t *cfg = apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
- APR_HASH_KEY_STRING);
+ svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
svn_config_get(cfg, &(lmb->message_encoding),
SVN_CONFIG_SECTION_MISCELLANY,
SVN_CONFIG_OPTION_LOG_ENCODING,
@@ -732,12 +372,12 @@ svn_cl__get_log_message(const char **log_msg,
while (! message)
{
/* We still don't have a valid commit message. Use $EDITOR to
- get one. Note that svn_cl__edit_externally will still return
- a UTF-8'ized log message. */
+ get one. Note that svn_cl__edit_string_externally will still
+ return a UTF-8'ized log message. */
int i;
svn_stringbuf_t *tmp_message = svn_stringbuf_dup(default_msg, pool);
svn_error_t *err = SVN_NO_ERROR;
- svn_string_t *msg_string = svn_string_create("", pool);
+ svn_string_t *msg_string = svn_string_create_empty(pool);
for (i = 0; i < commit_items->nelts; i++)
{
@@ -793,12 +433,12 @@ svn_cl__get_log_message(const char **log_msg,
/* Use the external edit to get a log message. */
if (! lmb->non_interactive)
{
- err = svn_cl__edit_string_externally(&msg_string, &lmb->tmpfile_left,
- lmb->editor_cmd, lmb->base_dir,
- msg_string, "svn-commit",
- lmb->config, TRUE,
- lmb->message_encoding,
- pool);
+ err = svn_cmdline__edit_string_externally(&msg_string, &lmb->tmpfile_left,
+ lmb->editor_cmd, lmb->base_dir,
+ msg_string, "svn-commit",
+ lmb->config, TRUE,
+ lmb->message_encoding,
+ pool);
}
else /* non_interactive flag says we can't pop up an editor, so error */
{
@@ -876,7 +516,7 @@ svn_cl__get_log_message(const char **log_msg,
{
SVN_ERR(svn_io_remove_file2(lmb->tmpfile_left, FALSE, pool));
*tmp_file = lmb->tmpfile_left = NULL;
- message = svn_stringbuf_create("", pool);
+ message = svn_stringbuf_create_empty(pool);
}
/* If the user chooses anything else, the loop will
@@ -947,7 +587,7 @@ svn_cl__try(svn_error_t *err,
va_list ap;
va_start(ap, quiet);
- while ((apr_err = va_arg(ap, apr_status_t)) != SVN_NO_ERROR)
+ while ((apr_err = va_arg(ap, apr_status_t)) != APR_SUCCESS)
{
if (errors_seen)
{
@@ -1058,7 +698,7 @@ svn_error_t *
svn_cl__xml_print_header(const char *tagname,
apr_pool_t *pool)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
/* <?xml version="1.0" encoding="UTF-8"?> */
svn_xml_make_header2(&sb, "UTF-8", pool);
@@ -1074,7 +714,7 @@ svn_error_t *
svn_cl__xml_print_footer(const char *tagname,
apr_pool_t *pool)
{
- svn_stringbuf_t *sb = svn_stringbuf_create("", pool);
+ svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
/* "</TAGNAME>" */
svn_xml_make_close_tag(&sb, pool, tagname);
@@ -1264,56 +904,6 @@ svn_cl__time_cstring_to_human_cstring(const char **human_cstring,
return SVN_NO_ERROR;
}
-
-/* Return a copy, allocated in POOL, of the next line of text from *STR
- * up to and including a CR and/or an LF. Change *STR to point to the
- * remainder of the string after the returned part. If there are no
- * characters to be returned, return NULL; never return an empty string.
- */
-static const char *
-next_line(const char **str, apr_pool_t *pool)
-{
- const char *start = *str;
- const char *p = *str;
-
- /* n.b. Throughout this fn, we never read any character after a '\0'. */
- /* Skip over all non-EOL characters, if any. */
- while (*p != '\r' && *p != '\n' && *p != '\0')
- p++;
- /* Skip over \r\n or \n\r or \r or \n, if any. */
- if (*p == '\r' || *p == '\n')
- {
- char c = *p++;
-
- if ((c == '\r' && *p == '\n') || (c == '\n' && *p == '\r'))
- p++;
- }
-
- /* Now p points after at most one '\n' and/or '\r'. */
- *str = p;
-
- if (p == start)
- return NULL;
-
- return svn_string_ncreate(start, p - start, pool)->data;
-}
-
-const char *
-svn_cl__indent_string(const char *str,
- const char *indent,
- apr_pool_t *pool)
-{
- svn_stringbuf_t *out = svn_stringbuf_create("", pool);
- const char *line;
-
- while ((line = next_line(&str, pool)))
- {
- svn_stringbuf_appendcstr(out, indent);
- svn_stringbuf_appendcstr(out, line);
- }
- return out->data;
-}
-
const char *
svn_cl__node_description(const svn_wc_conflict_version_t *node,
const char *wc_repos_root_URL,
@@ -1358,10 +948,14 @@ svn_cl__eat_peg_revisions(apr_array_header_t **true_targets_p,
for (i = 0; i < targets->nelts; i++)
{
const char *target = APR_ARRAY_IDX(targets, i, const char *);
- const char *true_target;
+ const char *true_target, *peg;
- SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, NULL,
+ SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg,
target, pool));
+ if (peg[0] && peg[1])
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s': a peg revision is not allowed here"),
+ target);
APR_ARRAY_PUSH(true_targets, const char *) = true_target;
}
@@ -1378,9 +972,7 @@ svn_cl__assert_homogeneous_target_type(const apr_array_header_t *targets)
err = svn_client__assert_homogeneous_target_type(targets);
if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
- return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
- _("Cannot mix repository and working copy "
- "targets"));
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err, NULL);
return err;
}
@@ -1419,3 +1011,59 @@ svn_cl__local_style_skip_ancestor(const char *parent_path,
return svn_dirent_local_style(relpath ? relpath : path, pool);
}
+
+svn_error_t *
+svn_cl__propset_print_binary_mime_type_warning(apr_array_header_t *targets,
+ const char *propname,
+ const svn_string_t *propval,
+ apr_pool_t *scratch_pool)
+{
+ if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *detected_mimetype;
+ const char *target = APR_ARRAY_IDX(targets, i, const char *);
+ const char *local_abspath;
+ const svn_string_t *canon_propval;
+ svn_node_kind_t node_kind;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
+ SVN_ERR(svn_io_check_path(local_abspath, &node_kind, iterpool));
+ if (node_kind != svn_node_file)
+ continue;
+
+ SVN_ERR(svn_wc_canonicalize_svn_prop(&canon_propval,
+ propname, propval,
+ local_abspath,
+ svn_node_file,
+ FALSE, NULL, NULL,
+ iterpool));
+
+ if (svn_mime_type_is_binary(canon_propval->data))
+ {
+ SVN_ERR(svn_io_detect_mimetype2(&detected_mimetype,
+ local_abspath, NULL,
+ iterpool));
+ if (detected_mimetype == NULL ||
+ !svn_mime_type_is_binary(detected_mimetype))
+ svn_error_clear(svn_cmdline_fprintf(stderr, iterpool,
+ _("svn: warning: '%s' is a binary mime-type but file '%s' "
+ "looks like text; diff, merge, blame, and other "
+ "operations will stop working on this file\n"),
+ canon_propval->data,
+ svn_dirent_local_style(local_abspath, iterpool)));
+
+ }
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+