diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2015-03-18 13:33:26 +0000 |
---|---|---|
committer | <> | 2015-07-08 14:41:01 +0000 |
commit | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (patch) | |
tree | 98bae10dde41c746c51ae97ec4f879e330415aa7 /subversion/svn | |
parent | 239dfafe71711b2f4c43d7b90a1228d7bdc5195e (diff) | |
download | subversion-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')
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; +} + |