diff options
Diffstat (limited to 'subversion/svn')
-rw-r--r-- | subversion/svn/cl-conflicts.c | 308 | ||||
-rw-r--r-- | subversion/svn/cl-conflicts.h | 7 | ||||
-rw-r--r-- | subversion/svn/cl-log.h | 5 | ||||
-rw-r--r-- | subversion/svn/cl.h | 96 | ||||
-rw-r--r-- | subversion/svn/cleanup-cmd.c | 5 | ||||
-rw-r--r-- | subversion/svn/conflict-callbacks.c | 2103 | ||||
-rw-r--r-- | subversion/svn/diff-cmd.c | 37 | ||||
-rw-r--r-- | subversion/svn/help-cmd.c | 34 | ||||
-rw-r--r-- | subversion/svn/info-cmd.c | 181 | ||||
-rw-r--r-- | subversion/svn/list-cmd.c | 31 | ||||
-rw-r--r-- | subversion/svn/log-cmd.c | 42 | ||||
-rw-r--r-- | subversion/svn/merge-cmd.c | 123 | ||||
-rw-r--r-- | subversion/svn/notify.c | 134 | ||||
-rw-r--r-- | subversion/svn/propdel-cmd.c | 8 | ||||
-rw-r--r-- | subversion/svn/propedit-cmd.c | 46 | ||||
-rw-r--r-- | subversion/svn/propget-cmd.c | 43 | ||||
-rw-r--r-- | subversion/svn/propset-cmd.c | 20 | ||||
-rw-r--r-- | subversion/svn/resolve-cmd.c | 193 | ||||
-rw-r--r-- | subversion/svn/shelve-cmd.c | 369 | ||||
-rw-r--r-- | subversion/svn/status.c | 7 | ||||
-rw-r--r-- | subversion/svn/svn.c | 499 | ||||
-rw-r--r-- | subversion/svn/switch-cmd.c | 10 | ||||
-rw-r--r-- | subversion/svn/update-cmd.c | 13 | ||||
-rw-r--r-- | subversion/svn/util.c | 22 |
24 files changed, 3192 insertions, 1144 deletions
diff --git a/subversion/svn/cl-conflicts.c b/subversion/svn/cl-conflicts.c index 8507e8c3b309c..0dc0abf291141 100644 --- a/subversion/svn/cl-conflicts.c +++ b/subversion/svn/cl-conflicts.c @@ -58,14 +58,6 @@ static const svn_token_map_t map_conflict_reason_xml[] = { 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 * @@ -229,14 +221,14 @@ operation_str(svn_wc_operation_t operation) svn_error_t * svn_cl__get_human_readable_prop_conflict_description( const char **desc, - const svn_wc_conflict_description2_t *conflict, + svn_client_conflict_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) + switch (svn_client_conflict_get_local_change(conflict)) { case svn_wc_conflict_reason_edited: reason_str = _("local edit"); @@ -251,12 +243,14 @@ svn_cl__get_human_readable_prop_conflict_description( reason_str = _("local obstruction"); break; default: - reason_str = apr_psprintf(pool, _("local %s"), - svn_token__to_word(map_conflict_reason_xml, - conflict->reason)); + reason_str = apr_psprintf( + pool, _("local %s"), + svn_token__to_word( + map_conflict_reason_xml, + svn_client_conflict_get_local_change(conflict))); break; } - switch (conflict->action) + switch (svn_client_conflict_get_incoming_change(conflict)) { case svn_wc_conflict_action_edit: action_str = _("incoming edit"); @@ -268,51 +262,63 @@ svn_cl__get_human_readable_prop_conflict_description( action_str = _("incoming delete"); break; default: - action_str = apr_psprintf(pool, _("incoming %s"), - svn_token__to_word(map_conflict_action_xml, - conflict->action)); + action_str = apr_psprintf( + pool, _("incoming %s"), + svn_token__to_word( + map_conflict_action_xml, + svn_client_conflict_get_incoming_change(conflict))); break; } SVN_ERR_ASSERT(reason_str && action_str); *desc = apr_psprintf(pool, _("%s, %s %s"), reason_str, action_str, - operation_str(conflict->operation)); + operation_str( + svn_client_conflict_get_operation(conflict))); 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, + svn_client_conflict_t *conflict, apr_pool_t *pool) { const char *action, *reason, *operation; svn_node_kind_t incoming_kind; + svn_wc_conflict_action_t conflict_action; + svn_wc_conflict_reason_t conflict_reason; + svn_wc_operation_t conflict_operation; + svn_node_kind_t conflict_node_kind; + + conflict_action = svn_client_conflict_get_incoming_change(conflict); + conflict_reason = svn_client_conflict_get_local_change(conflict); + conflict_operation = svn_client_conflict_get_operation(conflict); + conflict_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); /* 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) + 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; + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + NULL, NULL, &incoming_kind, conflict, pool, pool)); } - else if (conflict->action == svn_wc_conflict_action_add || - conflict->action == svn_wc_conflict_action_replace) + 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; + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + NULL, NULL, &incoming_kind, conflict, pool, pool)); } - reason = local_reason_str(conflict->node_kind, conflict->reason, - conflict->operation); - action = incoming_action_str(incoming_kind, conflict->action); - operation = operation_str(conflict->operation); + reason = local_reason_str(conflict_node_kind, conflict_reason, + conflict_operation); + action = incoming_action_str(incoming_kind, conflict_action); + operation = operation_str(conflict_operation); SVN_ERR_ASSERT(operation); if (action && reason) @@ -326,12 +332,12 @@ svn_cl__get_human_readable_tree_conflict_description( 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_node_kind_to_word(conflict_node_kind), svn_token__to_word(map_conflict_reason_xml, - conflict->reason), + conflict_reason), svn_node_kind_to_word(incoming_kind), svn_token__to_word(map_conflict_action_xml, - conflict->action), + conflict_action), operation); } return SVN_NO_ERROR; @@ -360,13 +366,16 @@ svn_cl__get_human_readable_action_description( /* Helper for svn_cl__append_tree_conflict_info_xml(). - * Appends the attributes of the given VERSION to ATT_HASH. + * Appends the repository location of a conflicted node 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, + const char *repos_root_url, + const char *repos_relpath, + svn_revnum_t peg_rev, + svn_node_kind_t node_kind, apr_pool_t *pool) { apr_hash_t *att_hash = apr_hash_make(pool); @@ -374,18 +383,17 @@ add_conflict_version_xml(svn_stringbuf_t **pstr, svn_hash_sets(att_hash, "side", side); - if (version->repos_url) - svn_hash_sets(att_hash, "repos-url", version->repos_url); + if (repos_root_url) + svn_hash_sets(att_hash, "repos-url", repos_root_url); - if (version->path_in_repos) - svn_hash_sets(att_hash, "path-in-repos", version->path_in_repos); + if (repos_relpath) + svn_hash_sets(att_hash, "path-in-repos", repos_relpath); - if (SVN_IS_VALID_REVNUM(version->peg_rev)) - svn_hash_sets(att_hash, "revision", apr_ltoa(pool, version->peg_rev)); + if (SVN_IS_VALID_REVNUM(peg_rev)) + svn_hash_sets(att_hash, "revision", apr_ltoa(pool, peg_rev)); - if (version->node_kind != svn_node_unknown) - svn_hash_sets(att_hash, "kind", - svn_cl__node_kind_str_xml(version->node_kind)); + if (node_kind != svn_node_unknown) + svn_hash_sets(att_hash, "kind", svn_cl__node_kind_str_xml(node_kind)); svn_xml_make_open_tag_hash(pstr, pool, svn_xml_self_closing, "version", att_hash); @@ -395,25 +403,34 @@ add_conflict_version_xml(svn_stringbuf_t **pstr, static svn_error_t * append_tree_conflict_info_xml(svn_stringbuf_t *str, - const svn_wc_conflict_description2_t *conflict, + svn_client_conflict_t *conflict, apr_pool_t *pool) { apr_hash_t *att_hash = apr_hash_make(pool); const char *tmp; + const char *repos_root_url; + const char *repos_relpath; + svn_revnum_t peg_rev; + svn_node_kind_t node_kind; svn_hash_sets(att_hash, "victim", - svn_dirent_basename(conflict->local_abspath, pool)); + svn_dirent_basename( + svn_client_conflict_get_local_abspath(conflict), pool)); svn_hash_sets(att_hash, "kind", - svn_cl__node_kind_str_xml(conflict->node_kind)); + svn_cl__node_kind_str_xml( + svn_client_conflict_tree_get_victim_node_kind(conflict))); svn_hash_sets(att_hash, "operation", - svn_cl__operation_str_xml(conflict->operation, pool)); + svn_cl__operation_str_xml( + svn_client_conflict_get_operation(conflict), pool)); - tmp = svn_token__to_word(map_conflict_action_xml, conflict->action); + tmp = svn_token__to_word(map_conflict_action_xml, + svn_client_conflict_get_incoming_change(conflict)); svn_hash_sets(att_hash, "action", tmp); - tmp = svn_token__to_word(map_conflict_reason_xml, conflict->reason); + tmp = svn_token__to_word(map_conflict_reason_xml, + svn_client_conflict_get_local_change(conflict)); svn_hash_sets(att_hash, "reason", tmp); /* Open the tree-conflict tag. */ @@ -422,17 +439,30 @@ append_tree_conflict_info_xml(svn_stringbuf_t *str, /* 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(svn_client_conflict_get_repos_info(&repos_root_url, NULL, conflict, + pool, pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(&repos_relpath, + &peg_rev, + &node_kind, + conflict, + pool, + pool)); + if (repos_root_url && repos_relpath) + SVN_ERR(add_conflict_version_xml(&str, "source-left", + repos_root_url, repos_relpath, peg_rev, + node_kind, pool)); + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(&repos_relpath, + &peg_rev, + &node_kind, + conflict, + pool, + pool)); + if (repos_root_url && repos_relpath) SVN_ERR(add_conflict_version_xml(&str, "source-right", - conflict->src_right_version, - pool)); + repos_root_url, repos_relpath, peg_rev, + node_kind, pool)); svn_xml_make_close_tag(&str, pool, "tree-conflict"); @@ -441,78 +471,128 @@ append_tree_conflict_info_xml(svn_stringbuf_t *str, svn_error_t * svn_cl__append_conflict_info_xml(svn_stringbuf_t *str, - const svn_wc_conflict_description2_t *conflict, + svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) { apr_hash_t *att_hash; - const char *kind; - if (conflict->kind == svn_wc_conflict_kind_tree) + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + svn_boolean_t tree_conflicted; + svn_wc_operation_t conflict_operation; + const char *repos_root_url; + const char *repos_relpath; + svn_revnum_t peg_rev; + svn_node_kind_t node_kind; + + conflict_operation = svn_client_conflict_get_operation(conflict); + + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, + scratch_pool, scratch_pool)); + if (tree_conflicted) { /* Uses other element type */ return svn_error_trace( append_tree_conflict_info_xml(str, conflict, scratch_pool)); } + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, + scratch_pool, 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_cl__operation_str_xml(conflict_operation, scratch_pool)); 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); + svn_cl__operation_str_xml(conflict_operation, scratch_pool)); - 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) + if (text_conflicted) { - 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; + const char *base_abspath; + const char *my_abspath; + const char *their_abspath; + + svn_hash_sets(att_hash, "type", "text"); + + /* "<conflict>" */ + svn_xml_make_open_tag_hash(&str, scratch_pool, + svn_xml_normal, "conflict", att_hash); + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &repos_relpath, &peg_rev, &node_kind, conflict, + scratch_pool, scratch_pool)); + if (repos_root_url && repos_relpath) + SVN_ERR(add_conflict_version_xml(&str, "source-left", + repos_root_url, repos_relpath, peg_rev, + node_kind, scratch_pool)); + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &repos_relpath, &peg_rev, &node_kind, conflict, + scratch_pool, scratch_pool)); + if (repos_root_url && repos_relpath) + SVN_ERR(add_conflict_version_xml(&str, "source-right", + repos_root_url, repos_relpath, peg_rev, + node_kind, scratch_pool)); + + SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, + &base_abspath, + &their_abspath, + conflict, scratch_pool, + scratch_pool)); + /* "<prev-base-file> xx </prev-base-file>" */ + svn_cl__xml_tagged_cdata( + &str, scratch_pool, "prev-base-file", base_abspath); + + /* "<prev-wc-file> xx </prev-wc-file>" */ + svn_cl__xml_tagged_cdata( + &str, scratch_pool, "prev-wc-file", my_abspath); + + /* "<cur-base-file> xx </cur-base-file>" */ + svn_cl__xml_tagged_cdata( + &str, scratch_pool, "cur-base-file", their_abspath); + + /* "</conflict>" */ + svn_xml_make_close_tag(&str, scratch_pool, "conflict"); } - /* "</conflict>" */ - svn_xml_make_close_tag(&str, scratch_pool, "conflict"); + if (props_conflicted->nelts > 0) + { + const char *reject_abspath; + + svn_hash_sets(att_hash, "type", "property"); + + /* "<conflict>" */ + svn_xml_make_open_tag_hash(&str, scratch_pool, + svn_xml_normal, "conflict", att_hash); + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &repos_relpath, &peg_rev, &node_kind, conflict, + scratch_pool, scratch_pool)); + if (repos_root_url && repos_relpath) + SVN_ERR(add_conflict_version_xml(&str, "source-left", + repos_root_url, repos_relpath, peg_rev, + node_kind, scratch_pool)); + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &repos_relpath, &peg_rev, &node_kind, conflict, + scratch_pool, scratch_pool)); + if (repos_root_url && repos_relpath) + SVN_ERR(add_conflict_version_xml(&str, "source-right", + repos_root_url, repos_relpath, peg_rev, + node_kind, scratch_pool)); + + /* "<prop-file> xx </prop-file>" */ + reject_abspath = + svn_client_conflict_prop_get_reject_abspath(conflict); + svn_cl__xml_tagged_cdata( + &str, scratch_pool, "prop-file", reject_abspath); + + /* "</conflict>" */ + svn_xml_make_close_tag(&str, scratch_pool, "conflict"); + } return SVN_NO_ERROR; } diff --git a/subversion/svn/cl-conflicts.h b/subversion/svn/cl-conflicts.h index d4880748cd9ca..bcc6c1b520f57 100644 --- a/subversion/svn/cl-conflicts.h +++ b/subversion/svn/cl-conflicts.h @@ -31,6 +31,7 @@ #include "svn_types.h" #include "svn_string.h" +#include "svn_client.h" #include "svn_wc.h" #ifdef __cplusplus @@ -48,7 +49,7 @@ extern "C" { svn_error_t * svn_cl__get_human_readable_prop_conflict_description( const char **desc, - const svn_wc_conflict_description2_t *conflict, + svn_client_conflict_t *conflict, apr_pool_t *pool); /** @@ -60,7 +61,7 @@ svn_cl__get_human_readable_prop_conflict_description( svn_error_t * svn_cl__get_human_readable_tree_conflict_description( const char **desc, - const svn_wc_conflict_description2_t *conflict, + svn_client_conflict_t *conflict, apr_pool_t *pool); /* Like svn_cl__get_human_readable_tree_conflict_description but @@ -80,7 +81,7 @@ svn_cl__get_human_readable_action_description( svn_error_t * svn_cl__append_conflict_info_xml( svn_stringbuf_t *str, - const svn_wc_conflict_description2_t *conflict, + svn_client_conflict_t *conflict, apr_pool_t *pool); #ifdef __cplusplus diff --git a/subversion/svn/cl-log.h b/subversion/svn/cl-log.h index 5b4c7aa7a68b5..e2d5646351a60 100644 --- a/subversion/svn/cl-log.h +++ b/subversion/svn/cl-log.h @@ -31,6 +31,8 @@ #include "svn_types.h" +#include "private/svn_string_private.h" + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -70,6 +72,9 @@ typedef struct svn_cl__log_receiver_baton * the log message, or a changed path matches one of these patterns. */ apr_array_header_t *search_patterns; + /* Buffer for Unicode normalization and case folding. */ + svn_membuf_t buffer; + /* Pool for persistent allocations. */ apr_pool_t *pool; } svn_cl__log_receiver_baton; diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h index 42e770e075c51..a5b1d9b948ae3 100644 --- a/subversion/svn/cl.h +++ b/subversion/svn/cl.h @@ -83,7 +83,10 @@ typedef enum svn_cl__accept_t svn_cl__accept_edit, /* Launch user's resolver and resolve conflict with edited file. */ - svn_cl__accept_launch + svn_cl__accept_launch, + + /* Use recommended resolution if available, else leave the conflict alone. */ + svn_cl__accept_recommended } svn_cl__accept_t; @@ -97,6 +100,7 @@ typedef enum svn_cl__accept_t #define SVN_CL__ACCEPT_THEIRS_FULL "theirs-full" #define SVN_CL__ACCEPT_EDIT "edit" #define SVN_CL__ACCEPT_LAUNCH "launch" +#define SVN_CL__ACCEPT_RECOMMENDED "recommended" /* Return the svn_cl__accept_t value corresponding to WORD, using exact * case-sensitive string comparison. Return svn_cl__accept_invalid if WORD @@ -174,6 +178,7 @@ typedef struct svn_cl__opt_state_t svn_boolean_t help; /* print usage message */ const char *auth_username; /* auth username */ const char *auth_password; /* auth password */ + svn_boolean_t auth_password_from_stdin; /* read password from stdin */ const char *extensions; /* subprocess extension args */ apr_array_header_t *targets; /* target list from file */ svn_boolean_t xml; /* output in xml, e.g., "svn log --xml" */ @@ -249,12 +254,18 @@ typedef struct svn_cl__opt_state_t svn_boolean_t show_passwords; /* show cached passwords */ svn_boolean_t pin_externals; /* pin externals to last-changed revisions */ const char *show_item; /* print only the given item */ + svn_boolean_t adds_as_modification; /* update 'add vs add' no tree conflict */ + svn_boolean_t vacuum_pristines; /* remove unreferenced pristines */ + svn_boolean_t list; } svn_cl__opt_state_t; +/* Conflict stats for operations such as update and merge. */ +typedef struct svn_cl__conflict_stats_t svn_cl__conflict_stats_t; typedef struct svn_cl__cmd_baton_t { svn_cl__opt_state_t *opt_state; + svn_cl__conflict_stats_t *conflict_stats; svn_client_ctx_t *ctx; } svn_cl__cmd_baton_t; @@ -293,6 +304,9 @@ svn_opt_subcommand_t svn_cl__revert, svn_cl__resolve, svn_cl__resolved, + svn_cl__shelve, + svn_cl__unshelve, + svn_cl__shelves, svn_cl__status, svn_cl__switch, svn_cl__unlock, @@ -335,8 +349,7 @@ svn_cl__try(svn_error_t *err, /* Our cancellation callback. */ -svn_error_t * -svn_cl__check_cancel(void *baton); +extern svn_cancel_func_t svn_cl__check_cancel; @@ -346,9 +359,6 @@ svn_cl__check_cancel(void *baton); 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 * @@ -362,6 +372,14 @@ svn_cl__conflict_stats_resolved(svn_cl__conflict_stats_t *conflict_stats, const char *path_local, svn_wc_conflict_kind_t conflict_kind); +/* Set *CONFLICTED_PATHS to the conflicted paths contained in CONFLICT_STATS. + * If no conflicted path exists, set *CONFLICTED_PATHS to NULL. */ +svn_error_t * +svn_cl__conflict_stats_get_paths(apr_array_header_t **conflicted_paths, + svn_cl__conflict_stats_t *conflict_stats, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Print the conflict stats accumulated in CONFLICT_STATS. * * Return any error encountered during printing. @@ -371,36 +389,33 @@ svn_error_t * svn_cl__print_conflict_stats(svn_cl__conflict_stats_t *conflict_stats, apr_pool_t *scratch_pool); -/* 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. +/* + * Interactively resolve the conflict a @a CONFLICT. + * TODO: more docs + */ +svn_error_t * +svn_cl__resolve_conflict(svn_boolean_t *quit, + svn_boolean_t *external_failed, + svn_boolean_t *printed_summary, + svn_client_conflict_t *conflict, + svn_cl__accept_t accept_which, + const char *editor_cmd, + const char *path_prefix, + svn_cmdline_prompt_baton_t *pb, + svn_cl__conflict_stats_t *conflict_stats, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool); + +/* + * Interactively resolve conflicts for all TARGETS. + * TODO: more docs */ 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__walk_conflicts(apr_array_header_t *targets, + svn_cl__conflict_stats_t *conflict_stats, + svn_cl__opt_state_t *opt_state, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool); /*** Command-line output functions -- printing to the user. ***/ @@ -777,15 +792,18 @@ 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 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. +/* Return a string showing a conflicted node's kind, URL and revision, + * to the extent that that information is available. If REPOS_ROOT_URL or + * REPOS_RELPATH are NULL, this prints just a 'none' node kind. * WC_REPOS_ROOT_URL should reflect the target working copy's repository - * root URL. If NODE is from that same URL, the printed URL is abbreviated + * root URL. If the node is from that same URL, the printed URL is abbreviated * to caret notation (^/). WC_REPOS_ROOT_URL may be NULL, in which case * this function tries to print the conflicted node's complete URL. */ const char * -svn_cl__node_description(const svn_wc_conflict_version_t *node, +svn_cl__node_description(const char *repos_root_url, + const char *repos_relpath, + svn_revnum_t peg_rev, + svn_node_kind_t node_kind, const char *wc_repos_root_URL, apr_pool_t *pool); diff --git a/subversion/svn/cleanup-cmd.c b/subversion/svn/cleanup-cmd.c index 6b0b62efe9302..516b933f68f4c 100644 --- a/subversion/svn/cleanup-cmd.c +++ b/subversion/svn/cleanup-cmd.c @@ -72,13 +72,14 @@ svn_cl__cleanup(apr_getopt_t *os, SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool)); - if (opt_state->remove_unversioned || opt_state->remove_ignored) + if (opt_state->remove_unversioned || opt_state->remove_ignored || + opt_state->vacuum_pristines) { svn_error_t *err = svn_client_vacuum(target_abspath, opt_state->remove_unversioned, opt_state->remove_ignored, TRUE /* fix_timestamps */, - FALSE /* vacuum_pristines */, + opt_state->vacuum_pristines, opt_state->include_externals, ctx, iterpool); diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c index a9cb39a2ade16..278fffcdb8826 100644 --- a/subversion/svn/conflict-callbacks.c +++ b/subversion/svn/conflict-callbacks.c @@ -44,53 +44,9 @@ #include "svn_private_config.h" #define ARRAY_LEN(ary) ((sizeof (ary)) / (sizeof ((ary)[0]))) -#define MAX_ARRAY_LEN(aryx, aryz) \ - (ARRAY_LEN((aryx)) > ARRAY_LEN((aryz)) \ - ? ARRAY_LEN((aryx)) : ARRAY_LEN((aryz))) -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_boolean_t printed_summary; -}; - -svn_error_t * -svn_cl__get_conflict_func_interactive_baton( - svn_cl__interactive_conflict_baton_t **b, - svn_cl__accept_t accept_which, - apr_hash_t *config, - const char *editor_cmd, - svn_cl__conflict_stats_t *conflict_stats, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *result_pool) -{ - svn_cmdline_prompt_baton_t *pb = apr_palloc(result_pool, sizeof(*pb)); - pb->cancel_func = cancel_func; - pb->cancel_baton = cancel_baton; - - *b = apr_palloc(result_pool, sizeof(**b)); - (*b)->accept_which = accept_which; - (*b)->config = config; - (*b)->editor_cmd = editor_cmd; - (*b)->external_failed = FALSE; - (*b)->pb = pb; - SVN_ERR(svn_dirent_get_absolute(&(*b)->path_prefix, "", result_pool)); - (*b)->quit = FALSE; - (*b)->conflict_stats = conflict_stats; - (*b)->printed_summary = FALSE; - - return SVN_NO_ERROR; -} - svn_cl__accept_t svn_cl__accept_from_word(const char *word) { @@ -122,15 +78,19 @@ svn_cl__accept_from_word(const char *word) if (strcmp(word, SVN_CL__ACCEPT_LAUNCH) == 0 || strcmp(word, "l") == 0 || strcmp(word, ":-l") == 0) return svn_cl__accept_launch; + if (strcmp(word, SVN_CL__ACCEPT_RECOMMENDED) == 0 + || strcmp(word, "r") == 0) + return svn_cl__accept_recommended; /* word is an invalid action. */ return svn_cl__accept_invalid; } /* Print on stdout a diff that shows incoming conflicting changes - * corresponding to the conflict described in DESC. */ + * corresponding to the conflict described in CONFLICT. */ static svn_error_t * -show_diff(const svn_wc_conflict_description2_t *desc, +show_diff(svn_client_conflict_t *conflict, + const char *merged_abspath, const char *path_prefix, svn_cancel_func_t cancel_func, void *cancel_baton, @@ -141,8 +101,13 @@ show_diff(const svn_wc_conflict_description2_t *desc, svn_diff_t *diff; svn_stream_t *output; svn_diff_file_options_t *options; + const char *my_abspath; + const char *their_abspath; - if (desc->merged_file) + SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, NULL, + &their_abspath, + conflict, pool, pool)); + if (merged_abspath) { /* For conflicts recorded by the 'merge' operation, show a diff between * 'mine' (the working version of the file as it appeared before the @@ -150,32 +115,32 @@ show_diff(const svn_wc_conflict_description2_t *desc, * 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 + * show a diff between '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) + if (svn_client_conflict_get_operation(conflict) == svn_wc_operation_merge) { - path1 = desc->my_abspath; + path1 = my_abspath; label1 = _("MINE"); } else { - path1 = desc->their_abspath; + path1 = their_abspath; label1 = _("THEIRS"); } - path2 = desc->merged_file; + path2 = merged_abspath; label2 = _("MERGED"); } else { /* There's no merged file, but we can show the difference between mine and theirs. */ - path1 = desc->their_abspath; + path1 = their_abspath; label1 = _("THEIRS"); - path2 = desc->my_abspath; + path2 = my_abspath; label2 = _("MINE"); } @@ -204,9 +169,9 @@ show_diff(const svn_wc_conflict_description2_t *desc, /* Print on stdout just the conflict hunks of a diff among the 'base', 'their' - * and 'my' files of DESC. */ + * and 'my' files of CONFLICT. */ static svn_error_t * -show_conflicts(const svn_wc_conflict_description2_t *desc, +show_conflicts(svn_client_conflict_t *conflict, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) @@ -214,89 +179,78 @@ show_conflicts(const svn_wc_conflict_description2_t *desc, svn_diff_t *diff; svn_stream_t *output; svn_diff_file_options_t *options; + const char *base_abspath; + const char *my_abspath; + const char *their_abspath; + SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, + &base_abspath, &their_abspath, + conflict, pool, pool)); options = svn_diff_file_options_create(pool); options->ignore_eol_style = TRUE; SVN_ERR(svn_stream_for_stdout(&output, pool)); - SVN_ERR(svn_diff_file_diff3_2(&diff, - desc->base_abspath, - desc->my_abspath, - desc->their_abspath, + SVN_ERR(svn_diff_file_diff3_2(&diff, base_abspath, my_abspath, their_abspath, options, pool)); /* ### Consider putting the markers/labels from ### svn_wc__merge_internal in the conflict description. */ - return svn_diff_file_output_merge3(output, diff, - desc->base_abspath, - desc->my_abspath, - desc->their_abspath, - _("||||||| ORIGINAL"), - _("<<<<<<< MINE (select with 'mc')"), - _(">>>>>>> THEIRS (select with 'tc')"), - "=======", - svn_diff_conflict_display_only_conflicts, - cancel_func, - cancel_baton, - pool); + return svn_diff_file_output_merge3( + output, diff, base_abspath, my_abspath, their_abspath, + _("||||||| ORIGINAL"), + _("<<<<<<< MINE (select with 'mc')"), + _(">>>>>>> THEIRS (select with 'tc')"), + "=======", + svn_diff_conflict_display_only_conflicts, + cancel_func, + cancel_baton, + 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. + * If MERGED_PROPVAL is non-NULL, use it as 'my' version instead of + * 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, + const svn_string_t *base_propval, + const svn_string_t *my_propval, + const svn_string_t *their_propval, + const svn_string_t *merged_propval, svn_cancel_func_t cancel_func, void *cancel_baton, 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 + /* If any of the property values is missing, use an empty value 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; - } - + if (base_propval == NULL) + base_propval = svn_string_create_empty(pool); + if (my_propval == NULL) + my_propval = svn_string_create_empty(pool); + if (their_propval == NULL) + their_propval = svn_string_create_empty(pool); + 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_merge3(output, diff, - base_abspath, - merged_abspath ? merged_abspath - : my_abspath, - their_abspath, - _("||||||| ORIGINAL"), - _("<<<<<<< MINE"), - _(">>>>>>> THEIRS"), - "=======", - svn_diff_conflict_display_modified_original_latest, - cancel_func, - cancel_baton, - pool)); + SVN_ERR(svn_diff_mem_string_diff3(&diff, base_propval, + merged_propval ? + merged_propval : my_propval, + their_propval, options, pool)); + SVN_ERR(svn_diff_mem_string_output_merge3( + output, diff, base_propval, + merged_propval ? merged_propval : my_propval, their_propval, + _("||||||| ORIGINAL"), + _("<<<<<<< MINE"), + _(">>>>>>> THEIRS"), + "=======", + svn_diff_conflict_display_modified_original_latest, + cancel_func, + cancel_baton, + pool)); return SVN_NO_ERROR; } @@ -309,8 +263,10 @@ merge_prop_conflict(svn_stream_t *output, * 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, +show_prop_conflict(const svn_string_t *base_propval, + const svn_string_t *my_propval, + const svn_string_t *their_propval, + const svn_string_t *merged_propval, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) @@ -318,13 +274,13 @@ show_prop_conflict(const svn_wc_conflict_description2_t *desc, svn_stream_t *output; SVN_ERR(svn_stream_for_stdout(&output, pool)); - SVN_ERR(merge_prop_conflict(output, desc, merged_abspath, - cancel_func, cancel_baton, pool)); + SVN_ERR(merge_prop_conflict(output, base_propval, my_propval, their_propval, + merged_propval, cancel_func, cancel_baton, pool)); return SVN_NO_ERROR; } -/* Run an external editor, passing it the MERGED_FILE, or, if the +/* Run an external editor, passing it the MERGED_ABSPATH, 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. @@ -335,16 +291,17 @@ show_prop_conflict(const svn_wc_conflict_description2_t *desc, * return that error. */ static svn_error_t * open_editor(svn_boolean_t *performed_edit, - const char *merged_file, - svn_cl__interactive_conflict_baton_t *b, + const char *merged_abspath, + const char *editor_cmd, + apr_hash_t *config, apr_pool_t *pool) { svn_error_t *err; - if (merged_file) + if (merged_abspath) { - err = svn_cmdline__edit_file_externally(merged_file, b->editor_cmd, - b->config, pool); + err = svn_cmdline__edit_file_externally(merged_abspath, editor_cmd, + config, pool); if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) { @@ -368,34 +325,44 @@ open_editor(svn_boolean_t *performed_edit, return SVN_NO_ERROR; } -/* Run an external editor, passing it the 'merged' property in DESC. +/* Run an external editor on the merged property value with conflict markers. + * Return the edited result in *MERGED_PROPVAL. + * If the edit is aborted, set *MERGED_ABSPATH and *MERGED_PROPVAL to NULL. * 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, +edit_prop_conflict(const svn_string_t **merged_propval, + const svn_string_t *base_propval, + const svn_string_t *my_propval, + const svn_string_t *their_propval, + const char *editor_cmd, + apr_hash_t *config, + svn_cmdline_prompt_baton_t *pb, 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, - b->pb->cancel_func, - b->pb->cancel_baton, + SVN_ERR(svn_stream_open_unique(&merged_prop, &file_path, NULL, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + SVN_ERR(merge_prop_conflict(merged_prop, base_propval, my_propval, + their_propval, NULL, + pb->cancel_func, + pb->cancel_baton, scratch_pool)); SVN_ERR(svn_stream_close(merged_prop)); - SVN_ERR(svn_io_file_flush(file, scratch_pool)); - SVN_ERR(open_editor(&performed_edit, file_path, b, scratch_pool)); - *merged_file_path = (performed_edit ? file_path : NULL); + SVN_ERR(open_editor(&performed_edit, file_path, editor_cmd, + config, scratch_pool)); + if (performed_edit && merged_propval) + { + svn_stringbuf_t *buf; + + SVN_ERR(svn_stringbuf_from_file2(&buf, file_path, scratch_pool)); + *merged_propval = svn_string_create_from_buf(buf, result_pool); + } return SVN_NO_ERROR; } @@ -403,169 +370,166 @@ edit_prop_conflict(const char **merged_file_path, /* Maximum line length for the prompt string. */ #define MAX_PROMPT_WIDTH 70 -/* Description of a resolver option */ +/* Description of a resolver option. + * Resolver options are used to build the resolver's conflict prompt. + * The user types a code to select the corresponding conflict resolution option. + * Some resolver options have a corresponding --accept argument. */ 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 ..._undefined if not a simple choice */ + svn_client_conflict_option_id_t choice; + /* or ..._undefined if not from libsvn_client */ + const char *accept_arg; /* --accept option argument (NOT localized) */ } 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[] = +typedef struct client_option_t { - /* 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]"), - svn_wc_conflict_choose_undefined }, - { "df", N_("show diff"), N_("show all changes made to merged file"), - svn_wc_conflict_choose_undefined }, - { "r", N_("mark resolved"), N_("accept merged version of file [working]"), - svn_wc_conflict_choose_merged }, - { "", "", "", svn_wc_conflict_choose_unspecified }, - { "dc", N_("display conflict"), N_("show all conflicts " - "(ignoring merged version)"), - svn_wc_conflict_choose_undefined }, - { "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 merge tool to resolve conflict"), - svn_wc_conflict_choose_undefined }, - { "l", N_("launch tool"), N_("launch external merge tool to resolve " - "conflict [launch]"), - svn_wc_conflict_choose_undefined }, - { "i", N_("internal merge tool"), N_("use built-in merge tool to " - "resolve conflict"), - svn_wc_conflict_choose_undefined }, - { "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', '?')"), - svn_wc_conflict_choose_undefined }, + const char *code; /* one or two characters */ + const char *label; /* label in prompt (localized) */ + const char *long_desc; /* longer description (localized) */ + svn_client_conflict_option_id_t choice; + /* or ..._undefined if not from libsvn_client */ + const char *accept_arg; /* --accept option argument (NOT localized) */ + svn_boolean_t is_recommended; /* if TRUE, try this option before prompting */ +} client_option_t; + +/* Resolver options for conflict options offered by libsvn_client. */ +static const resolver_option_t builtin_resolver_options[] = +{ + { "r", svn_client_conflict_option_merged_text, + SVN_CL__ACCEPT_WORKING }, + { "mc", svn_client_conflict_option_working_text_where_conflicted, + SVN_CL__ACCEPT_MINE_CONFLICT }, + { "tc", svn_client_conflict_option_incoming_text_where_conflicted, + SVN_CL__ACCEPT_THEIRS_CONFLICT }, + { "mf", svn_client_conflict_option_working_text, + SVN_CL__ACCEPT_MINE_FULL}, + { "tf", svn_client_conflict_option_incoming_text, + SVN_CL__ACCEPT_THEIRS_FULL }, + { "p", svn_client_conflict_option_postpone, + SVN_CL__ACCEPT_POSTPONE }, + + /* This option resolves a tree conflict to the current working copy state. */ + { "r", svn_client_conflict_option_accept_current_wc_state, + SVN_CL__ACCEPT_WORKING }, + + /* These options use the same code since they only occur in + * distinct conflict scenarios. */ + { "u", svn_client_conflict_option_update_move_destination }, + { "u", svn_client_conflict_option_update_any_moved_away_children }, + + /* Options for incoming add vs local add. */ + { "i", svn_client_conflict_option_incoming_add_ignore }, + + /* Options for incoming file add vs local file add upon merge. */ + { "m", svn_client_conflict_option_incoming_added_file_text_merge }, + { "M", svn_client_conflict_option_incoming_added_file_replace_and_merge }, + + /* Options for incoming dir add vs local dir add upon merge. */ + { "m", svn_client_conflict_option_incoming_added_dir_merge }, + { "R", svn_client_conflict_option_incoming_added_dir_replace }, + { "M", svn_client_conflict_option_incoming_added_dir_replace_and_merge }, + + /* Options for incoming delete vs any. */ + { "i", svn_client_conflict_option_incoming_delete_ignore }, + { "a", svn_client_conflict_option_incoming_delete_accept }, + + /* Options for incoming move vs local edit. */ + { "m", svn_client_conflict_option_incoming_move_file_text_merge }, + { "m", svn_client_conflict_option_incoming_move_dir_merge }, + + /* Options for local move vs incoming edit. */ + { "m", svn_client_conflict_option_local_move_file_text_merge }, + { NULL } }; -/* Resolver options for a binary file conflict. */ -static const resolver_option_t binary_conflict_options[] = +/* Extra resolver options offered by 'svn' for any conflict. */ +static const client_option_t extra_resolver_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. */ - { "r", N_("mark resolved"), N_("accept the working copy version of file " - " [working]"), - svn_wc_conflict_choose_merged }, - { "tf", N_("their version"), N_("accept the incoming version of file " - " [theirs-full]"), - svn_wc_conflict_choose_theirs_full }, - { "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', '?')"), - svn_wc_conflict_choose_undefined }, + margin of 9 spaces if needed) */ + { "q", N_("Quit resolution"), N_("postpone all remaining conflicts"), + svn_client_conflict_option_postpone }, { 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"), - svn_wc_conflict_choose_undefined }, - { "e", N_("edit property"), N_("change merged property value in an editor" - " [edit]"), - svn_wc_conflict_choose_undefined }, - { "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 '?')"), - svn_wc_conflict_choose_undefined }, - { NULL } -}; -/* Resolver options for a tree conflict */ -static const resolver_option_t tree_conflict_options[] = +/* Additional resolver options offered by 'svn' for a text conflict. */ +static const client_option_t extra_resolver_options_text[] = { - { "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 '?')"), - svn_wc_conflict_choose_undefined }, + /* Translators: keep long_desc below 70 characters (wrap with a left + margin of 9 spaces if needed) */ + { "e", N_("Edit file"), N_("change merged file in an editor"), + svn_client_conflict_option_undefined, + SVN_CL__ACCEPT_EDIT }, + { "df", N_("Show diff"), N_("show all changes made to merged file"), + svn_client_conflict_option_undefined}, + { "dc", N_("Display conflict"), N_("show all conflicts " + "(ignoring merged version)"), + svn_client_conflict_option_undefined }, + { "m", N_("Merge"), N_("use merge tool to resolve conflict"), + svn_client_conflict_option_undefined }, + { "l", N_("Launch tool"), N_("launch external merge tool to resolve " + "conflict"), + svn_client_conflict_option_undefined, + SVN_CL__ACCEPT_LAUNCH }, + { "i", N_("Internal merge tool"), N_("use built-in merge tool to " + "resolve conflict"), + svn_client_conflict_option_undefined }, + { "s", N_("Show all options"), N_("show this list (also 'h', '?')"), + svn_client_conflict_option_undefined }, { NULL } }; -static const resolver_option_t tree_conflict_options_update_moved_away[] = +/* Additional resolver options offered by 'svn' for a property conflict. */ +static const client_option_t extra_resolver_options_prop[] = { - { "mc", N_("apply update to move destination (recommended)"), - 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 '?')"), - svn_wc_conflict_choose_undefined }, + /* Translators: keep long_desc below 70 characters (wrap with a left + margin of 9 spaces if needed) */ + { "dc", N_("Display conflict"), N_("show conflicts in this property"), + svn_client_conflict_option_undefined }, + { "e", N_("Edit property"), N_("change merged property value in an " + "editor"), + svn_client_conflict_option_undefined, + SVN_CL__ACCEPT_EDIT }, + { "h", N_("Help"), N_("show this help (also '?')"), + svn_client_conflict_option_undefined }, { NULL } }; -static const resolver_option_t tree_conflict_options_update_edit_deleted_dir[] = +/* Additional resolver options offered by 'svn' for a tree conflict. */ +static const client_option_t extra_resolver_options_tree[] = { - { "mc", N_("prepare for updating moved-away children, if any (recommended)"), - N_("allow updating moved-away children " - "with 'svn resolve' [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 '?')"), - svn_wc_conflict_choose_undefined }, + /* Translators: keep long_desc below 70 characters (wrap with a left + margin of 9 spaces if needed) */ + { "d", N_("Set repository move destination path"), + N_("pick repository move target from list of possible targets"), + svn_client_conflict_option_undefined }, + + { "w", N_("Set working copy move destination path"), + N_("pick working copy move target from list of possible targets"), + svn_client_conflict_option_undefined }, + + { "h", N_("Help"), N_("show this help (also '?')"), + svn_client_conflict_option_undefined }, + { 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, +static const client_option_t * +find_option(const apr_array_header_t *options, const char *option_code) { - const resolver_option_t *opt; + int i; - for (opt = options; opt->code; opt++) + for (i = 0; i < options->nelts; i++) { + const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *); + /* Ignore code "" (blank lines) which is not a valid answer. */ if (opt->code[0] && strcmp(opt->code, option_code) == 0) return opt; @@ -573,10 +537,76 @@ find_option(const resolver_option_t *options, return NULL; } +/* Find the first recommended option in OPTIONS. */ +static const client_option_t * +find_recommended_option(const apr_array_header_t *options) +{ + int i; + + for (i = 0; i < options->nelts; i++) + { + const client_option_t *opt = APR_ARRAY_IDX(options, i, client_option_t *); + + /* Ignore code "" (blank lines) which is not a valid answer. */ + if (opt->code[0] && opt->is_recommended) + return opt; + } + return NULL; +} + +/* Return a pointer to the client_option_t in OPTIONS matching the ID of + * conflict option BUILTIN_OPTION. @a out will be set to NULL if the + * option was not found. */ +static svn_error_t * +find_option_by_builtin(client_option_t **out, + svn_client_conflict_t *conflict, + const resolver_option_t *options, + svn_client_conflict_option_t *builtin_option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const resolver_option_t *opt; + svn_client_conflict_option_id_t id; + svn_client_conflict_option_id_t recommended_id; + + id = svn_client_conflict_option_get_id(builtin_option); + recommended_id = svn_client_conflict_get_recommended_option_id(conflict); + + for (opt = options; opt->code; opt++) + { + if (opt->choice == id) + { + client_option_t *client_opt; + + client_opt = apr_pcalloc(result_pool, sizeof(*client_opt)); + client_opt->choice = id; + client_opt->code = opt->code; + client_opt->label = svn_client_conflict_option_get_label( + builtin_option, + result_pool); + client_opt->long_desc = svn_client_conflict_option_get_description( + builtin_option, + result_pool); + client_opt->accept_arg = opt->accept_arg; + client_opt->is_recommended = + (recommended_id != svn_client_conflict_option_unspecified && + id == recommended_id); + + *out = client_opt; + + return SVN_NO_ERROR; + } + } + + *out = NULL; + + return SVN_NO_ERROR; +} + /* 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, +prompt_string(const apr_array_header_t *options, const char *const *option_codes, apr_pool_t *pool) { @@ -585,10 +615,11 @@ prompt_string(const resolver_option_t *options, const char *line_sep = apr_psprintf(pool, "\n%*s", left_margin, ""); int this_line_len = left_margin; svn_boolean_t first = TRUE; + int i = 0; while (1) { - const resolver_option_t *opt; + const client_option_t *opt; const char *s; int slen; @@ -597,18 +628,21 @@ prompt_string(const resolver_option_t *options, if (! *option_codes) break; opt = find_option(options, *option_codes++); + if (opt == NULL) + continue; } else { - opt = options++; - if (! opt->code) + if (i >= options->nelts) break; + opt = APR_ARRAY_IDX(options, i, client_option_t *); + i++; } if (! first) result = apr_pstrcat(pool, result, ",", SVN_VA_NULL); - s = apr_psprintf(pool, _(" (%s) %s"), - opt->code, _(opt->short_desc)); + s = apr_psprintf(pool, " (%s) %s", opt->code, + opt->label ? opt->label : opt->long_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) @@ -624,33 +658,48 @@ prompt_string(const resolver_option_t *options, } /* Return a help string listing the OPTIONS. */ -static const char * -help_string(const resolver_option_t *options, +static svn_error_t * +help_string(const char **result, + const apr_array_header_t *options, apr_pool_t *pool) { - const char *result = ""; - const resolver_option_t *opt; + apr_pool_t *iterpool; + int i; - for (opt = options; opt->code; opt++) + *result = ""; + iterpool = svn_pool_create(pool); + for (i = 0; i < options->nelts; i++) { + const client_option_t *opt; + svn_pool_clear(iterpool); + + opt = APR_ARRAY_IDX(options, i, + client_option_t *); + /* 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)); + if (opt->accept_arg) + *result = apr_psprintf(pool, "%s%-6s - %s [%s]\n", + *result, s, opt->long_desc, + opt->accept_arg); + else + *result = apr_psprintf(pool, "%s%-6s - %s\n", *result, s, + opt->long_desc); } else { - result = apr_pstrcat(pool, result, "\n", SVN_VA_NULL); + *result = apr_pstrcat(pool, *result, "\n", SVN_VA_NULL); } } - result = apr_pstrcat(pool, result, + svn_pool_destroy(iterpool); + *result = apr_pstrcat(pool, *result, _("Words in square brackets are the corresponding " "--accept option arguments.\n"), SVN_VA_NULL); - return result; + return SVN_NO_ERROR; } /* Prompt the user with CONFLICT_OPTIONS, restricted to the options listed @@ -659,12 +708,14 @@ help_string(const resolver_option_t *options, * 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. + * CONFLICT_DESCRIPTION (if not NULL) and 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, +prompt_user(const client_option_t **opt, + const apr_array_header_t *conflict_options, const char *const *options_to_show, + const char *conflict_description, void *prompt_baton, apr_pool_t *scratch_pool) { @@ -675,9 +726,13 @@ prompt_user(const resolver_option_t **opt, 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))); + const char *helpstr; + + if (conflict_description) + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", + conflict_description)); + SVN_ERR(help_string(&helpstr, conflict_options, scratch_pool)); + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", helpstr)); *opt = NULL; } else @@ -692,41 +747,174 @@ prompt_user(const resolver_option_t **opt, 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. +/* Set *OPTIONS to an array of resolution options for CONFLICT. */ +static svn_error_t * +build_text_conflict_options(apr_array_header_t **options, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + svn_boolean_t is_binary, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const client_option_t *o; + apr_array_header_t *builtin_options; + int nopt; + int i; + apr_pool_t *iterpool; + + SVN_ERR(svn_client_conflict_text_get_resolution_options(&builtin_options, + conflict, ctx, + scratch_pool, + scratch_pool)); + nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options); + if (!is_binary) + nopt += ARRAY_LEN(extra_resolver_options_text); + *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < builtin_options->nelts; i++) + { + client_option_t *opt; + svn_client_conflict_option_t *builtin_option; + + svn_pool_clear(iterpool); + builtin_option = APR_ARRAY_IDX(builtin_options, i, + svn_client_conflict_option_t *); + SVN_ERR(find_option_by_builtin(&opt, conflict, + builtin_resolver_options, + builtin_option, + result_pool, + iterpool)); + if (opt == NULL) + continue; /* ### unknown option -- assign a code dynamically? */ + + APR_ARRAY_PUSH(*options, client_option_t *) = opt; + } + + for (o = extra_resolver_options; o->code; o++) + APR_ARRAY_PUSH(*options, const client_option_t *) = o; + if (!is_binary) + { + for (o = extra_resolver_options_text; o->code; o++) + APR_ARRAY_PUSH(*options, const client_option_t *) = o; + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Mark CONFLICT as resolved to resolution option with ID OPTION_ID. + * If TEXT_CONFLICTED is true, resolve text conflicts described by CONFLICT. + * IF PROPNAME is not NULL, mark the conflict in the specified property as + * resolved. If PROPNAME is "", mark all property conflicts described by + * CONFLICT as resolved. + * If TREE_CONFLICTED is true, resolve tree conflicts described by CONFLICT. + * Adjust CONFLICT_STATS as necessary (PATH_PREFIX is needed for this step). */ +static svn_error_t * +mark_conflict_resolved(svn_client_conflict_t *conflict, + svn_client_conflict_option_id_t option_id, + svn_boolean_t text_conflicted, + const char *propname, + svn_boolean_t tree_conflicted, + const char *path_prefix, + svn_cl__conflict_stats_t *conflict_stats, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *local_relpath + = svn_cl__local_style_skip_ancestor( + path_prefix, svn_client_conflict_get_local_abspath(conflict), + scratch_pool); + + if (text_conflicted) + { + SVN_ERR(svn_client_conflict_text_resolve_by_id(conflict, option_id, + ctx, scratch_pool)); + svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, + svn_wc_conflict_kind_text); + } + + if (propname) + { + SVN_ERR(svn_client_conflict_prop_resolve_by_id(conflict, propname, + option_id, ctx, + scratch_pool)); + svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, + svn_wc_conflict_kind_property); + } + + if (tree_conflicted) + { + SVN_ERR(svn_client_conflict_tree_resolve_by_id(conflict, option_id, + ctx, scratch_pool)); + svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, + svn_wc_conflict_kind_tree); + } + + return SVN_NO_ERROR; +} + +/* Ask the user what to do about the text conflict described by CONFLICT + * and either resolve the conflict accordingly or postpone resolution. * 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, +handle_text_conflict(svn_boolean_t *resolved, + svn_boolean_t *postponed, + svn_boolean_t *quit, + svn_boolean_t *printed_description, + svn_client_conflict_t *conflict, + const char *path_prefix, + svn_cmdline_prompt_baton_t *pb, + const char *editor_cmd, + apr_hash_t *config, + svn_cl__conflict_stats_t *conflict_stats, + svn_client_ctx_t *ctx, 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 by setting the - result->save_merge flag)? */ + /* Have they done something that might have affected the merged file? */ 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; const char *local_relpath; - - SVN_ERR_ASSERT(desc->kind == svn_wc_conflict_kind_text); - - local_relpath = svn_cl__local_style_skip_ancestor(b->path_prefix, - desc->local_abspath, - scratch_pool);; - - if (desc->is_binary) - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, - _("Conflict discovered in binary file '%s'.\n"), - local_relpath)); - else - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, - _("Conflict discovered in file '%s'.\n"), - local_relpath)); + const char *local_abspath = svn_client_conflict_get_local_abspath(conflict); + const char *mime_type = svn_client_conflict_text_get_mime_type(conflict); + svn_boolean_t is_binary = mime_type ? svn_mime_type_is_binary(mime_type) + : FALSE; + const char *base_abspath; + const char *my_abspath; + const char *their_abspath; + const char *merged_abspath = svn_client_conflict_get_local_abspath(conflict); + apr_array_header_t *text_conflict_options; + svn_client_conflict_option_id_t option_id; + + option_id = svn_client_conflict_option_unspecified; + + SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, + &base_abspath, &their_abspath, + conflict, scratch_pool, + scratch_pool)); + + local_relpath = svn_cl__local_style_skip_ancestor(path_prefix, + local_abspath, + scratch_pool); + + if (!*printed_description) + { + if (is_binary) + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("Merge conflict discovered in binary " + "file '%s'.\n"), + local_relpath)); + else + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("Merge conflict discovered in file '%s'.\n"), + local_relpath)); + *printed_description = TRUE; + } /* ### TODO This whole feature availability check is grossly outdated. DIFF_ALLOWED needs either to be redefined or to go away. @@ -736,21 +924,18 @@ handle_text_conflict(svn_wc_conflict_result_t *result, 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->is_binary && - ((desc->merged_file && desc->base_abspath) - || (!desc->base_abspath && desc->my_abspath && desc->their_abspath))) + if (!is_binary && + ((merged_abspath && base_abspath) + || (!base_abspath && my_abspath && their_abspath))) diff_allowed = TRUE; + SVN_ERR(build_text_conflict_options(&text_conflict_options, conflict, ctx, + is_binary, scratch_pool, scratch_pool)); while (TRUE) { - const char *options[1 + MAX_ARRAY_LEN(binary_conflict_options, - text_conflict_options)]; - - const resolver_option_t *conflict_options = desc->is_binary - ? binary_conflict_options - : text_conflict_options; - const char **next_option = options; - const resolver_option_t *opt; + const char *suggested_options[9]; /* filled statically below */ + const char **next_option = suggested_options; + const client_option_t *opt; svn_pool_clear(iterpool); @@ -758,30 +943,26 @@ handle_text_conflict(svn_wc_conflict_result_t *result, if (diff_allowed) { /* We need one more path for this feature. */ - if (desc->my_abspath) + if (my_abspath) *next_option++ = "df"; *next_option++ = "e"; /* We need one more path for this feature. */ - if (desc->my_abspath) + if (my_abspath) *next_option++ = "m"; if (knows_something) *next_option++ = "r"; - - *next_option++ = "mc"; - *next_option++ = "tc"; } else { - if (knows_something || desc->is_binary) + if (knows_something || is_binary) *next_option++ = "r"; - /* The 'mine-full' option selects the ".mine" file so only offer - * it if that file exists. It does not exist for binary files, - * for example (questionable historical behaviour since 1.0). */ - if (desc->my_abspath) + /* The 'mine-full' option selects the ".mine" file for texts or + * the current working directory file for binary files. */ + if (my_abspath || is_binary) *next_option++ = "mf"; *next_option++ = "tf"; @@ -789,26 +970,28 @@ handle_text_conflict(svn_wc_conflict_result_t *result, *next_option++ = "s"; *next_option++ = NULL; - SVN_ERR(prompt_user(&opt, conflict_options, options, b->pb, iterpool)); + SVN_ERR(prompt_user(&opt, text_conflict_options, suggested_options, + NULL, 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; + option_id = opt->choice; + *quit = TRUE; break; } else if (strcmp(opt->code, "s") == 0) { + const char *helpstr; + + SVN_ERR(help_string(&helpstr, text_conflict_options, iterpool)); SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "\n%s\n", - help_string(conflict_options, - iterpool))); + helpstr)); } else if (strcmp(opt->code, "dc") == 0) { - if (desc->is_binary) + if (is_binary) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; cannot " @@ -816,24 +999,23 @@ handle_text_conflict(svn_wc_conflict_result_t *result, "binary file.\n\n"))); continue; } - else if (! (desc->my_abspath && desc->base_abspath && - desc->their_abspath)) + else if (! (my_abspath && base_abspath && their_abspath)) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; original " "files not available.\n\n"))); continue; } - SVN_ERR(show_conflicts(desc, - b->pb->cancel_func, - b->pb->cancel_baton, + SVN_ERR(show_conflicts(conflict, + pb->cancel_func, + pb->cancel_baton, iterpool)); knows_something = TRUE; } else if (strcmp(opt->code, "df") == 0) { /* Re-check preconditions. */ - if (! diff_allowed || ! desc->my_abspath) + if (! diff_allowed || ! my_abspath) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; there's no " @@ -841,14 +1023,15 @@ handle_text_conflict(svn_wc_conflict_result_t *result, continue; } - SVN_ERR(show_diff(desc, b->path_prefix, - b->pb->cancel_func, b->pb->cancel_baton, + SVN_ERR(show_diff(conflict, merged_abspath, path_prefix, + pb->cancel_func, pb->cancel_baton, 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)); + SVN_ERR(open_editor(&performed_edit, merged_abspath, editor_cmd, + config, iterpool)); if (performed_edit) knows_something = TRUE; } @@ -858,7 +1041,7 @@ handle_text_conflict(svn_wc_conflict_result_t *result, svn_error_t *err; /* Re-check preconditions. */ - if (! desc->my_abspath) + if (! my_abspath) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; there's no " @@ -866,11 +1049,11 @@ handle_text_conflict(svn_wc_conflict_result_t *result, continue; } - err = svn_cl__merge_file_externally(desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, b->config, + err = svn_cl__merge_file_externally(base_abspath, + their_abspath, + my_abspath, + merged_abspath, + local_abspath, config, NULL, iterpool); if (err) { @@ -881,16 +1064,16 @@ handle_text_conflict(svn_wc_conflict_result_t *result, /* Try the internal merge tool. */ svn_error_clear(err); SVN_ERR(svn_cl__merge_file(&remains_in_conflict, - desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->path_prefix, - b->editor_cmd, - b->config, - b->pb->cancel_func, - b->pb->cancel_baton, + base_abspath, + their_abspath, + my_abspath, + merged_abspath, + local_abspath, + path_prefix, + editor_cmd, + config, + pb->cancel_func, + pb->cancel_baton, iterpool)); knows_something = !remains_in_conflict; } @@ -922,21 +1105,20 @@ handle_text_conflict(svn_wc_conflict_result_t *result, { /* ### 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 + /* ### 'merged_abspath' 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) + if (base_abspath && their_abspath && my_abspath && merged_abspath) { svn_error_t *err; char buf[1024]; const char *message; - err = svn_cl__merge_file_externally(desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->config, NULL, iterpool); + err = svn_cl__merge_file_externally(base_abspath, + their_abspath, + my_abspath, + merged_abspath, + local_abspath, + config, NULL, iterpool); if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) { @@ -962,26 +1144,26 @@ handle_text_conflict(svn_wc_conflict_result_t *result, svn_boolean_t remains_in_conflict = TRUE; SVN_ERR(svn_cl__merge_file(&remains_in_conflict, - desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->path_prefix, - b->editor_cmd, - b->config, - b->pb->cancel_func, - b->pb->cancel_baton, + base_abspath, + their_abspath, + my_abspath, + merged_abspath, + local_abspath, + path_prefix, + editor_cmd, + config, + pb->cancel_func, + pb->cancel_baton, iterpool)); if (!remains_in_conflict) knows_something = TRUE; } - else if (opt->choice != svn_wc_conflict_choose_undefined) + else if (opt->choice != svn_client_conflict_option_undefined) { - if ((opt->choice == svn_wc_conflict_choose_mine_conflict - || opt->choice == svn_wc_conflict_choose_theirs_conflict) - && desc->is_binary) + if ((opt->choice == svn_client_conflict_option_working_text_where_conflicted + || opt->choice == svn_client_conflict_option_incoming_text_where_conflicted) + && is_binary) { SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, _("Invalid option; cannot choose " @@ -993,7 +1175,7 @@ handle_text_conflict(svn_wc_conflict_result_t *result, /* 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 + if (opt->choice == svn_client_conflict_option_merged_text && ! knows_something && diff_allowed) { SVN_ERR(svn_cmdline_fprintf( @@ -1003,59 +1185,137 @@ handle_text_conflict(svn_wc_conflict_result_t *result, continue; } - result->choice = opt->choice; - if (performed_edit) - result->save_merged = TRUE; + option_id = opt->choice; break; } } svn_pool_destroy(iterpool); + if (option_id != svn_client_conflict_option_unspecified && + option_id != svn_client_conflict_option_postpone) + { + SVN_ERR(mark_conflict_resolved(conflict, option_id, + TRUE, NULL, FALSE, + path_prefix, conflict_stats, + ctx, scratch_pool)); + *resolved = TRUE; + } + else + { + *resolved = FALSE; + *postponed = (option_id == svn_client_conflict_option_postpone); + } + 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. +/* Set *OPTIONS to an array of resolution options for CONFLICT. */ +static svn_error_t * +build_prop_conflict_options(apr_array_header_t **options, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const client_option_t *o; + apr_array_header_t *builtin_options; + int nopt; + int i; + apr_pool_t *iterpool; + + SVN_ERR(svn_client_conflict_prop_get_resolution_options(&builtin_options, + conflict, ctx, + scratch_pool, + scratch_pool)); + nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options) + + ARRAY_LEN(extra_resolver_options_prop); + *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < builtin_options->nelts; i++) + { + client_option_t *opt; + svn_client_conflict_option_t *builtin_option; + + svn_pool_clear(iterpool); + builtin_option = APR_ARRAY_IDX(builtin_options, i, + svn_client_conflict_option_t *); + SVN_ERR(find_option_by_builtin(&opt, conflict, + builtin_resolver_options, + builtin_option, + result_pool, + iterpool)); + if (opt == NULL) + continue; /* ### unknown option -- assign a code dynamically? */ + + APR_ARRAY_PUSH(*options, client_option_t *) = opt; + } + + svn_pool_destroy(iterpool); + + for (o = extra_resolver_options; o->code; o++) + APR_ARRAY_PUSH(*options, const client_option_t *) = o; + for (o = extra_resolver_options_prop; o->code; o++) + APR_ARRAY_PUSH(*options, const client_option_t *) = o; + + return SVN_NO_ERROR; +} + +/* Ask the user what to do about the conflicted property PROPNAME described + * by CONFLICT and return the corresponding resolution option in *OPTION. * 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) +handle_one_prop_conflict(svn_client_conflict_option_t **option, + svn_boolean_t *quit, + const char *path_prefix, + svn_cmdline_prompt_baton_t *pb, + const char *editor_cmd, + apr_hash_t *config, + svn_client_conflict_t *conflict, + const char *propname, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { apr_pool_t *iterpool; - const char *message; - const char *merged_file_path = NULL; + const char *description; + const svn_string_t *merged_propval = NULL; svn_boolean_t resolved_allowed = FALSE; + const svn_string_t *base_propval; + const svn_string_t *my_propval; + const svn_string_t *their_propval; + apr_array_header_t *resolution_options; + apr_array_header_t *prop_conflict_options; - /* ### 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_client_conflict_prop_get_propvals(NULL, &my_propval, + &base_propval, &their_propval, + conflict, propname, + scratch_pool)); SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, _("Conflict for property '%s' discovered" " on '%s'.\n"), - desc->property_name, + propname, svn_cl__local_style_skip_ancestor( - b->path_prefix, desc->local_abspath, + path_prefix, + svn_client_conflict_get_local_abspath(conflict), 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)); - + SVN_ERR(svn_client_conflict_prop_get_description(&description, conflict, + scratch_pool, scratch_pool)); + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", description)); + + SVN_ERR(svn_client_conflict_prop_get_resolution_options(&resolution_options, + conflict, ctx, + result_pool, + scratch_pool)); + SVN_ERR(build_prop_conflict_options(&prop_conflict_options, conflict, ctx, + scratch_pool, scratch_pool)); 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; + const client_option_t *opt; + const char *suggested_options[9]; /* filled statically below */ + const char **next_option = suggested_options; *next_option++ = "p"; *next_option++ = "mf"; @@ -1070,29 +1330,32 @@ handle_prop_conflict(svn_wc_conflict_result_t *result, svn_pool_clear(iterpool); - SVN_ERR(prompt_user(&opt, prop_conflict_options, options, b->pb, - iterpool)); + SVN_ERR(prompt_user(&opt, prop_conflict_options, suggested_options, + NULL, 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; + *option = svn_client_conflict_option_find_by_id(resolution_options, + opt->choice); + *quit = TRUE; break; } else if (strcmp(opt->code, "dc") == 0) { - SVN_ERR(show_prop_conflict(desc, merged_file_path, - b->pb->cancel_func, b->pb->cancel_baton, + SVN_ERR(show_prop_conflict(base_propval, my_propval, their_propval, + merged_propval, + pb->cancel_func, pb->cancel_baton, scratch_pool)); } else if (strcmp(opt->code, "e") == 0) { - SVN_ERR(edit_prop_conflict(&merged_file_path, desc, b, + SVN_ERR(edit_prop_conflict(&merged_propval, + base_propval, my_propval, their_propval, + editor_cmd, config, pb, result_pool, scratch_pool)); - resolved_allowed = (merged_file_path != NULL); + resolved_allowed = (merged_propval != NULL); } else if (strcmp(opt->code, "r") == 0) { @@ -1104,13 +1367,17 @@ handle_prop_conflict(svn_wc_conflict_result_t *result, continue; } - result->merged_file = merged_file_path; - result->choice = svn_wc_conflict_choose_merged; + *option = svn_client_conflict_option_find_by_id( + resolution_options, + svn_client_conflict_option_merged_text); + svn_client_conflict_option_set_merged_propval(*option, + merged_propval); break; } - else if (opt->choice != svn_wc_conflict_choose_undefined) + else if (opt->choice != svn_client_conflict_option_undefined) { - result->choice = opt->choice; + *option = svn_client_conflict_option_find_by_id(resolution_options, + opt->choice); break; } } @@ -1119,252 +1386,854 @@ handle_prop_conflict(svn_wc_conflict_result_t *result, 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. +/* Ask the user what to do about the property conflicts described by CONFLICT + * and either resolve them accordingly or postpone resolution. * 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) +handle_prop_conflicts(svn_boolean_t *resolved, + svn_boolean_t *postponed, + svn_boolean_t *quit, + const svn_string_t **merged_value, + const char *path_prefix, + svn_cmdline_prompt_baton_t *pb, + const char *editor_cmd, + apr_hash_t *config, + svn_client_conflict_t *conflict, + svn_cl__conflict_stats_t *conflict_stats, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - const char *readable_desc; + apr_array_header_t *props_conflicted; apr_pool_t *iterpool; + int i; + int nresolved = 0; - 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)); + SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL, + conflict, scratch_pool, + scratch_pool)); iterpool = svn_pool_create(scratch_pool); - while (1) + for (i = 0; i < props_conflicted->nelts; i++) { - const resolver_option_t *opt; - const resolver_option_t *tc_opts; + const char *propname = APR_ARRAY_IDX(props_conflicted, i, const char *); + svn_client_conflict_option_t *option; + svn_client_conflict_option_id_t option_id; svn_pool_clear(iterpool); - tc_opts = tree_conflict_options; + SVN_ERR(handle_one_prop_conflict(&option, quit, path_prefix, pb, + editor_cmd, config, conflict, propname, + ctx, + iterpool, iterpool)); + option_id = svn_client_conflict_option_get_id(option); - if (desc->operation == svn_wc_operation_update || - desc->operation == svn_wc_operation_switch) + if (option_id != svn_client_conflict_option_unspecified && + option_id != svn_client_conflict_option_postpone) { - if (desc->reason == svn_wc_conflict_reason_moved_away) - { - tc_opts = tree_conflict_options_update_moved_away; - } - else if (desc->reason == svn_wc_conflict_reason_deleted || - desc->reason == svn_wc_conflict_reason_replaced) - { - if (desc->action == svn_wc_conflict_action_edit && - desc->node_kind == svn_node_dir) - tc_opts = tree_conflict_options_update_edit_deleted_dir; - } + const char *local_relpath = + svn_cl__local_style_skip_ancestor( + path_prefix, svn_client_conflict_get_local_abspath(conflict), + iterpool); + + SVN_ERR(svn_client_conflict_prop_resolve(conflict, propname, option, + ctx, iterpool)); + svn_cl__conflict_stats_resolved(conflict_stats, local_relpath, + svn_wc_conflict_kind_property); + nresolved++; + *postponed = FALSE; } + else + *postponed = (option_id == svn_client_conflict_option_postpone); - SVN_ERR(prompt_user(&opt, tc_opts, NULL, b->pb, iterpool)); - if (! opt) - continue; + if (*quit) + break; + } + svn_pool_destroy(iterpool); - if (strcmp(opt->code, "q") == 0) + /* Indicate success if no property conflicts remain. */ + *resolved = (nresolved == props_conflicted->nelts); + + return SVN_NO_ERROR; +} + +/* Set *OPTIONS to an array of resolution options for CONFLICT. */ +static svn_error_t * +build_tree_conflict_options( + apr_array_header_t **options, + apr_array_header_t **possible_moved_to_repos_relpaths, + apr_array_header_t **possible_moved_to_abspaths, + svn_boolean_t *all_options_are_dumb, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const client_option_t *o; + apr_array_header_t *builtin_options; + int nopt; + int i; + int next_unknown_option_code = 1; + apr_pool_t *iterpool; + + if (all_options_are_dumb != NULL) + *all_options_are_dumb = TRUE; + + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&builtin_options, + conflict, ctx, + scratch_pool, + scratch_pool)); + nopt = builtin_options->nelts + ARRAY_LEN(extra_resolver_options_tree) + + ARRAY_LEN(extra_resolver_options); + *options = apr_array_make(result_pool, nopt, sizeof(client_option_t *)); + *possible_moved_to_abspaths = NULL; + *possible_moved_to_repos_relpaths = NULL; + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < builtin_options->nelts; i++) + { + client_option_t *opt; + svn_client_conflict_option_t *builtin_option; + svn_client_conflict_option_id_t id; + + svn_pool_clear(iterpool); + builtin_option = APR_ARRAY_IDX(builtin_options, i, + svn_client_conflict_option_t *); + SVN_ERR(find_option_by_builtin(&opt, conflict, + builtin_resolver_options, + builtin_option, + result_pool, + iterpool)); + if (opt == NULL) { - result->choice = opt->choice; - b->accept_which = svn_cl__accept_postpone; - b->quit = TRUE; - break; + /* Unkown option. Assign a dynamic option code. */ + opt = apr_pcalloc(result_pool, sizeof(*opt)); + opt->code = apr_psprintf(result_pool, "%d", next_unknown_option_code); + next_unknown_option_code++; + opt->label = svn_client_conflict_option_get_label(builtin_option, + result_pool); + opt->long_desc = svn_client_conflict_option_get_description( + builtin_option, result_pool); + opt->choice = svn_client_conflict_option_get_id(builtin_option); + opt->accept_arg = NULL; } - else if (opt->choice != svn_wc_conflict_choose_undefined) + + APR_ARRAY_PUSH(*options, client_option_t *) = opt; + + id = svn_client_conflict_option_get_id(builtin_option); + + /* Check if we got a "smart" tree conflict option. */ + if (all_options_are_dumb != NULL && + *all_options_are_dumb && + id != svn_client_conflict_option_postpone && + id != svn_client_conflict_option_accept_current_wc_state) + *all_options_are_dumb = FALSE; + + if (id == svn_client_conflict_option_incoming_move_file_text_merge || + id == svn_client_conflict_option_incoming_move_dir_merge) { - result->choice = opt->choice; - break; + SVN_ERR( + svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + possible_moved_to_repos_relpaths, builtin_option, + result_pool, iterpool)); + SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates( + possible_moved_to_abspaths, builtin_option, + result_pool, iterpool)); } } + svn_pool_destroy(iterpool); + for (o = extra_resolver_options_tree; o->code; o++) + { + /* Add move target choice options only if there are multiple + * move targets to choose from. */ + if (strcmp(o->code, "d") == 0 && + (*possible_moved_to_repos_relpaths == NULL || + (*possible_moved_to_repos_relpaths)->nelts <= 1)) + continue; + if (strcmp(o->code, "w") == 0 && + (*possible_moved_to_abspaths == NULL || + (*possible_moved_to_abspaths)->nelts <= 1)) + continue; + + APR_ARRAY_PUSH(*options, const client_option_t *) = o; + } + for (o = extra_resolver_options; o->code; o++) + APR_ARRAY_PUSH(*options, const client_option_t *) = o; + return SVN_NO_ERROR; } -/* The body of svn_cl__conflict_func_interactive(). */ +/* Make the user select a move target path for the moved-away VICTIM_ABSPATH. */ 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) +prompt_move_target_path(int *preferred_move_target_idx, + apr_array_header_t *possible_moved_to_paths, + svn_boolean_t paths_are_local, + svn_cmdline_prompt_baton_t *pb, + const char *victim_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { - svn_cl__interactive_conflict_baton_t *b = baton; - svn_error_t *err; + const char *move_targets_prompt = ""; + const char *move_targets_list = ""; + const char *wcroot_abspath; + const char *victim_relpath; + int i; + apr_int64_t idx; + apr_pool_t *iterpool; + + SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, victim_abspath, + ctx, scratch_pool, scratch_pool)); + victim_relpath = svn_cl__local_style_skip_ancestor(wcroot_abspath, + victim_abspath, + scratch_pool), + iterpool = svn_pool_create(scratch_pool); + + /* Build the prompt. */ + for (i = 0; i < possible_moved_to_paths->nelts; i++) + { + svn_pool_clear(iterpool); - /* Start out assuming we're going to postpone the conflict. */ - *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, - NULL, result_pool); + if (paths_are_local) + { + const char *moved_to_abspath; + const char *moved_to_relpath; + + moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_paths, i, + const char *); + moved_to_relpath = svn_cl__local_style_skip_ancestor( + wcroot_abspath, moved_to_abspath, iterpool), + move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '%s'\n", + move_targets_list, i + 1, + moved_to_relpath); + } + else + { + const char *moved_to_repos_relpath; - switch (b->accept_which) + moved_to_repos_relpath = APR_ARRAY_IDX(possible_moved_to_paths, i, + const char *); + move_targets_list = apr_psprintf(scratch_pool, "%s (%d): '^/%s'\n", + move_targets_list, i + 1, + moved_to_repos_relpath); + } + } + if (paths_are_local) + move_targets_prompt = + apr_psprintf(scratch_pool, + _("Possible working copy destinations for moved-away '%s' " + "are:\n%s" + "Only one destination can be a move; the others are " + "copies.\n" + "Specify the correct move target path by number: "), + victim_relpath, move_targets_list); + else + move_targets_prompt = + apr_psprintf(scratch_pool, + _("Possible repository destinations for moved-away '%s' " + "are:\n%s" + "Only one destination can be a move; the others are " + "copies.\n" + "Specify the correct move target path by number: "), + victim_relpath, move_targets_list); + + /* Keep asking the user until we got a valid choice. */ + while (1) { - case svn_cl__accept_invalid: - case svn_cl__accept_unspecified: - /* No (or no valid) --accept option, fall through to prompting. */ + const char *answer; + svn_error_t *err; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_cmdline_prompt_user2(&answer, move_targets_prompt, + pb, iterpool)); + err = svn_cstring_strtoi64(&idx, answer, 1, + possible_moved_to_paths->nelts, 10); + if (err) + { + char buf[1024]; + + svn_cmdline_fprintf(stderr, iterpool, "%s\n", + svn_err_best_message(err, buf, sizeof(buf))); + svn_error_clear(err); + continue; + } + break; - case svn_cl__accept_postpone: - (*result)->choice = svn_wc_conflict_choose_postpone; - return SVN_NO_ERROR; - case svn_cl__accept_base: - (*result)->choice = svn_wc_conflict_choose_base; - return SVN_NO_ERROR; - case svn_cl__accept_working: - /* If the caller didn't merge the property values, then I guess - * 'choose working' means 'choose mine'... */ - if (! desc->merged_file) - (*result)->merged_file = desc->my_abspath; - (*result)->choice = svn_wc_conflict_choose_merged; - return SVN_NO_ERROR; - case svn_cl__accept_mine_conflict: - (*result)->choice = svn_wc_conflict_choose_mine_conflict; - return SVN_NO_ERROR; - case svn_cl__accept_theirs_conflict: - (*result)->choice = svn_wc_conflict_choose_theirs_conflict; - return SVN_NO_ERROR; - case svn_cl__accept_mine_full: - (*result)->choice = svn_wc_conflict_choose_mine_full; - return SVN_NO_ERROR; - case svn_cl__accept_theirs_full: - (*result)->choice = svn_wc_conflict_choose_theirs_full; - return SVN_NO_ERROR; - case svn_cl__accept_edit: - if (desc->merged_file) + } + + svn_pool_destroy(iterpool); + + SVN_ERR_ASSERT((idx - 1) == (int)(idx - 1)); + *preferred_move_target_idx = (int)(idx - 1); + return SVN_NO_ERROR; +} + +/* Ask the user what to do about the tree conflict described by CONFLICT + * and either resolve the conflict accordingly or postpone resolution. + * SCRATCH_POOL is used for temporary allocations. */ +static svn_error_t * +handle_tree_conflict(svn_boolean_t *resolved, + svn_boolean_t *postponed, + svn_boolean_t *quit, + svn_boolean_t *printed_description, + svn_client_conflict_t *conflict, + const char *path_prefix, + svn_cmdline_prompt_baton_t *pb, + svn_cl__conflict_stats_t *conflict_stats, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + apr_array_header_t *tree_conflict_options; + svn_client_conflict_option_id_t option_id; + const char *local_abspath; + const char *conflict_description; + const char *local_change_description; + const char *incoming_change_description; + apr_array_header_t *possible_moved_to_repos_relpaths; + apr_array_header_t *possible_moved_to_abspaths; + svn_boolean_t all_options_are_dumb; + const struct client_option_t *recommended_option; + svn_boolean_t repos_move_target_chosen = FALSE; + svn_boolean_t wc_move_target_chosen = FALSE; + + option_id = svn_client_conflict_option_unspecified; + local_abspath = svn_client_conflict_get_local_abspath(conflict); + + /* Always show the best possible conflict description and options. */ + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, scratch_pool)); + + SVN_ERR(svn_client_conflict_tree_get_description( + &incoming_change_description, &local_change_description, + conflict, ctx, scratch_pool, scratch_pool)); + conflict_description = apr_psprintf(scratch_pool, "%s\n%s", + incoming_change_description, + local_change_description); + if (!*printed_description) + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("Tree conflict on '%s':\n%s\n"), + svn_cl__local_style_skip_ancestor( + path_prefix, local_abspath, scratch_pool), + conflict_description)); + + SVN_ERR(build_tree_conflict_options(&tree_conflict_options, + &possible_moved_to_repos_relpaths, + &possible_moved_to_abspaths, + &all_options_are_dumb, + conflict, ctx, + scratch_pool, scratch_pool)); + + /* Try a recommended resolution option before prompting. */ + recommended_option = find_recommended_option(tree_conflict_options); + if (recommended_option) + { + svn_error_t *err; + apr_status_t root_cause; + + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("Applying recommended resolution '%s':\n"), + recommended_option->label)); + + err = mark_conflict_resolved(conflict, recommended_option->choice, + FALSE, NULL, TRUE, + path_prefix, conflict_stats, + ctx, scratch_pool); + if (!err) + { + *resolved = TRUE; + return SVN_NO_ERROR; + } + + root_cause = svn_error_root_cause(err)->apr_err; + if (root_cause != SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE && + root_cause != SVN_ERR_WC_OBSTRUCTED_UPDATE && + root_cause != SVN_ERR_WC_FOUND_CONFLICT) + return svn_error_trace(err); + + /* Fall back to interactive prompting. */ + svn_error_clear(err); + } + + if (all_options_are_dumb) + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("\nSubversion is not smart enough to resolve " + "this tree conflict automatically!\nSee 'svn " + "help resolve' for more information.\n\n"))); + + iterpool = svn_pool_create(scratch_pool); + while (1) + { + const client_option_t *opt; + + svn_pool_clear(iterpool); + + if (!repos_move_target_chosen && + possible_moved_to_repos_relpaths && + possible_moved_to_repos_relpaths->nelts > 1) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("Ambiguous move destinations exist in the repository; " + "try the 'd' option\n"))); + if (!wc_move_target_chosen && possible_moved_to_abspaths && + possible_moved_to_abspaths->nelts > 1) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("Ambiguous move destinations exist in the working copy; " + "try the 'w' option\n"))); + + SVN_ERR(prompt_user(&opt, tree_conflict_options, NULL, + conflict_description, pb, iterpool)); + *printed_description = TRUE; + if (! opt) + continue; + + if (strcmp(opt->code, "q") == 0) + { + option_id = opt->choice; + *quit = TRUE; + break; + } + else if (strcmp(opt->code, "d") == 0) { - if (b->external_failed) + int preferred_move_target_idx; + apr_array_header_t *options; + svn_client_conflict_option_t *conflict_option; + + SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, + possible_moved_to_repos_relpaths, + FALSE, + pb, local_abspath, ctx, iterpool)); + + /* Update preferred move target path. */ + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, + conflict, + ctx, + iterpool, + iterpool)); + conflict_option = + svn_client_conflict_option_find_by_id( + options, + svn_client_conflict_option_incoming_move_file_text_merge); + if (conflict_option == NULL) { - (*result)->choice = svn_wc_conflict_choose_postpone; - return SVN_NO_ERROR; + conflict_option = + svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_incoming_move_dir_merge); } - 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 || - err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + if (conflict_option) { - char buf[1024]; - const char *message; - - message = svn_err_best_message(err, buf, sizeof(buf)); - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", - message)); - svn_error_clear(err); - b->external_failed = TRUE; + SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath( + conflict_option, preferred_move_target_idx, + ctx, iterpool)); + repos_move_target_chosen = TRUE; + wc_move_target_chosen = FALSE; + + /* Update option description. */ + SVN_ERR(build_tree_conflict_options( + &tree_conflict_options, + &possible_moved_to_repos_relpaths, + &possible_moved_to_abspaths, + NULL, conflict, ctx, + scratch_pool, scratch_pool)); + + /* Update conflict description. */ + SVN_ERR(svn_client_conflict_tree_get_description( + &incoming_change_description, &local_change_description, + conflict, ctx, scratch_pool, scratch_pool)); + conflict_description = apr_psprintf(scratch_pool, "%s\n%s", + incoming_change_description, + local_change_description); } - else if (err) - return svn_error_trace(err); - (*result)->choice = svn_wc_conflict_choose_merged; - return SVN_NO_ERROR; + continue; } - /* else, fall through to prompting. */ - break; - case svn_cl__accept_launch: - if (desc->base_abspath && desc->their_abspath - && desc->my_abspath && desc->merged_file) + else if (strcmp(opt->code, "w") == 0) { - svn_boolean_t remains_in_conflict; - - if (b->external_failed) + int preferred_move_target_idx; + apr_array_header_t *options; + svn_client_conflict_option_t *conflict_option; + + SVN_ERR(prompt_move_target_path(&preferred_move_target_idx, + possible_moved_to_abspaths, TRUE, + pb, local_abspath, ctx, iterpool)); + + /* Update preferred move target path. */ + SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, + conflict, + ctx, + iterpool, + iterpool)); + conflict_option = + svn_client_conflict_option_find_by_id( + options, + svn_client_conflict_option_incoming_move_file_text_merge); + if (conflict_option == NULL) { - (*result)->choice = svn_wc_conflict_choose_postpone; - return SVN_NO_ERROR; + conflict_option = + svn_client_conflict_option_find_by_id( + options, svn_client_conflict_option_incoming_move_dir_merge); } - err = svn_cl__merge_file_externally(desc->base_abspath, - desc->their_abspath, - desc->my_abspath, - desc->merged_file, - desc->local_abspath, - b->config, - &remains_in_conflict, - scratch_pool); - if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || - err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + if (conflict_option) { - char buf[1024]; - const char *message; - - message = svn_err_best_message(err, buf, sizeof(buf)); - SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", - message)); - b->external_failed = TRUE; - return svn_error_trace(err); + SVN_ERR(svn_client_conflict_option_set_moved_to_abspath( + conflict_option, preferred_move_target_idx, ctx, + iterpool)); + wc_move_target_chosen = TRUE; + + /* Update option description. */ + SVN_ERR(build_tree_conflict_options( + &tree_conflict_options, + &possible_moved_to_repos_relpaths, + &possible_moved_to_abspaths, + NULL, conflict, ctx, + scratch_pool, scratch_pool)); } - else if (err) - return svn_error_trace(err); - - if (remains_in_conflict) - (*result)->choice = svn_wc_conflict_choose_postpone; - else - (*result)->choice = svn_wc_conflict_choose_merged; - return SVN_NO_ERROR; + continue; + } + else if (opt->choice != svn_client_conflict_option_undefined) + { + option_id = opt->choice; + break; } - /* else, fall through to prompting. */ - break; } - - /* Print a summary of conflicts before starting interactive resolution */ - if (! b->printed_summary) + svn_pool_destroy(iterpool); + if (option_id != svn_client_conflict_option_unspecified && + option_id != svn_client_conflict_option_postpone) { - SVN_ERR(svn_cl__print_conflict_stats(b->conflict_stats, scratch_pool)); - b->printed_summary = TRUE; + SVN_ERR(mark_conflict_resolved(conflict, option_id, + FALSE, NULL, TRUE, + path_prefix, conflict_stats, + ctx, scratch_pool)); + *resolved = TRUE; + } + else + { + *resolved = FALSE; + *postponed = (option_id == svn_client_conflict_option_postpone); } - /* We're in interactive mode and either the user gave no --accept - option or the option did not apply; let's prompt. */ - - /* Handle the most common cases, which is either: + return SVN_NO_ERROR; +} - Conflicting edits on a file's text, or - Conflicting edits on a property. - */ - if (((desc->kind == svn_wc_conflict_kind_text) - && (desc->action == svn_wc_conflict_action_edit) - && (desc->reason == svn_wc_conflict_reason_edited))) - SVN_ERR(handle_text_conflict(*result, desc, b, scratch_pool)); - else if (desc->kind == svn_wc_conflict_kind_property) - SVN_ERR(handle_prop_conflict(*result, desc, b, result_pool, scratch_pool)); - else if (desc->kind == svn_wc_conflict_kind_tree) - SVN_ERR(handle_tree_conflict(*result, desc, b, scratch_pool)); +static svn_error_t * +resolve_conflict_interactively(svn_boolean_t *resolved, + svn_boolean_t *postponed, + svn_boolean_t *quit, + svn_boolean_t *external_failed, + svn_boolean_t *printed_summary, + svn_boolean_t *printed_description, + svn_client_conflict_t *conflict, + const char *editor_cmd, + apr_hash_t *config, + const char *path_prefix, + svn_cmdline_prompt_baton_t *pb, + svn_cl__conflict_stats_t *conflict_stats, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + svn_boolean_t tree_conflicted; + const svn_string_t *merged_propval; + + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, + scratch_pool, + scratch_pool)); - else /* other types of conflicts -- do nothing about them. */ + /* Print a summary of conflicts before starting interactive resolution */ + if (! *printed_summary) { - (*result)->choice = svn_wc_conflict_choose_postpone; + SVN_ERR(svn_cl__print_conflict_stats(conflict_stats, scratch_pool)); + *printed_summary = TRUE; } + *resolved = FALSE; + if (text_conflicted + && (svn_client_conflict_get_incoming_change(conflict) == + svn_wc_conflict_action_edit) + && (svn_client_conflict_get_local_change(conflict) == + svn_wc_conflict_reason_edited)) + SVN_ERR(handle_text_conflict(resolved, postponed, quit, printed_description, + conflict, path_prefix, pb, editor_cmd, config, + conflict_stats, ctx, scratch_pool)); + if (props_conflicted->nelts > 0) + SVN_ERR(handle_prop_conflicts(resolved, postponed, quit, &merged_propval, + path_prefix, pb, editor_cmd, config, conflict, + conflict_stats, ctx, result_pool, scratch_pool)); + if (tree_conflicted) + SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description, + conflict, path_prefix, pb, conflict_stats, ctx, + scratch_pool)); + return SVN_NO_ERROR; } svn_error_t * -svn_cl__conflict_func_interactive(svn_wc_conflict_result_t **result, - const svn_wc_conflict_description2_t *desc, - void *baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +svn_cl__resolve_conflict(svn_boolean_t *quit, + svn_boolean_t *external_failed, + svn_boolean_t *printed_summary, + svn_client_conflict_t *conflict, + svn_cl__accept_t accept_which, + const char *editor_cmd, + const char *path_prefix, + svn_cmdline_prompt_baton_t *pb, + svn_cl__conflict_stats_t *conflict_stats, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { - svn_cl__interactive_conflict_baton_t *b = baton; + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + svn_boolean_t tree_conflicted; + const char *local_abspath; + svn_client_conflict_option_id_t option_id; + + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, + scratch_pool, + scratch_pool)); + local_abspath = svn_client_conflict_get_local_abspath(conflict); + + if (accept_which == svn_cl__accept_unspecified) + { + option_id = svn_client_conflict_option_unspecified; + } + else if (accept_which == svn_cl__accept_postpone) + { + option_id = svn_client_conflict_option_postpone; + } + else if (accept_which == svn_cl__accept_base) + { + option_id = svn_client_conflict_option_base_text; + } + else if (accept_which == svn_cl__accept_working) + { + option_id = svn_client_conflict_option_merged_text; - SVN_ERR(conflict_func_interactive(result, desc, baton, - result_pool, scratch_pool)); + if (text_conflicted) + { + const char *mime_type = + svn_client_conflict_text_get_mime_type(conflict); - /* If we are resolving a conflict, adjust the summary of conflicts. */ - if ((*result)->choice != svn_wc_conflict_choose_postpone) + /* There is no merged text for binary conflicts, behave as + * if 'mine-full' was chosen. */ + if (mime_type && svn_mime_type_is_binary(mime_type)) + option_id = svn_client_conflict_option_working_text; + } + else if (tree_conflicted) + { + /* For tree conflicts, map 'working' to 'accept current working + * copy state'. */ + option_id = svn_client_conflict_option_accept_current_wc_state; + } + } + else if (accept_which == svn_cl__accept_theirs_conflict) + { + option_id = svn_client_conflict_option_incoming_text_where_conflicted; + } + else if (accept_which == svn_cl__accept_mine_conflict) { - const char *local_path - = svn_cl__local_style_skip_ancestor( - b->path_prefix, desc->local_abspath, scratch_pool); + option_id = svn_client_conflict_option_working_text_where_conflicted; + + if (tree_conflicted) + { + svn_wc_operation_t operation; + + operation = svn_client_conflict_get_operation(conflict); + if (operation == svn_wc_operation_update || + operation == svn_wc_operation_switch) + { + svn_wc_conflict_reason_t reason; - svn_cl__conflict_stats_resolved(b->conflict_stats, local_path, - desc->kind); + reason = svn_client_conflict_get_local_change(conflict); + if (reason == svn_wc_conflict_reason_moved_away) + { + /* Map 'mine-conflict' to 'update move destination'. */ + option_id = + svn_client_conflict_option_update_move_destination; + } + else if (reason == svn_wc_conflict_reason_deleted || + reason == svn_wc_conflict_reason_replaced) + { + svn_wc_conflict_action_t action; + svn_node_kind_t node_kind; + + action = svn_client_conflict_get_incoming_change(conflict); + node_kind = + svn_client_conflict_tree_get_victim_node_kind(conflict); + + if (action == svn_wc_conflict_action_edit && + node_kind == svn_node_dir) + { + /* Map 'mine-conflict' to 'update any moved away + * children'. */ + option_id = + svn_client_conflict_option_update_any_moved_away_children; + } + } + } + } + } + else if (accept_which == svn_cl__accept_theirs_full) + { + option_id = svn_client_conflict_option_incoming_text; } + else if (accept_which == svn_cl__accept_mine_full) + { + option_id = svn_client_conflict_option_working_text; + } + else if (accept_which == svn_cl__accept_edit) + { + option_id = svn_client_conflict_option_unspecified; + + if (local_abspath) + { + if (*external_failed) + { + option_id = svn_client_conflict_option_postpone; + } + else + { + svn_error_t *err; + + err = svn_cmdline__edit_file_externally(local_abspath, + editor_cmd, + ctx->config, + scratch_pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR || + err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + { + char buf[1024]; + const char *message; + + message = svn_err_best_message(err, buf, sizeof(buf)); + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + message)); + svn_error_clear(err); + *external_failed = TRUE; + } + else if (err) + return svn_error_trace(err); + option_id = svn_client_conflict_option_merged_text; + } + } + } + else if (accept_which == svn_cl__accept_launch) + { + const char *base_abspath = NULL; + const char *my_abspath = NULL; + const char *their_abspath = NULL; + + option_id = svn_client_conflict_option_unspecified; + + if (text_conflicted) + SVN_ERR(svn_client_conflict_text_get_contents(NULL, &my_abspath, + &base_abspath, + &their_abspath, + conflict, scratch_pool, + scratch_pool)); + + if (base_abspath && their_abspath && my_abspath && local_abspath) + { + if (*external_failed) + { + option_id = svn_client_conflict_option_postpone; + } + else + { + svn_boolean_t remains_in_conflict; + svn_error_t *err; + + err = svn_cl__merge_file_externally(base_abspath, their_abspath, + my_abspath, local_abspath, + local_abspath, ctx->config, + &remains_in_conflict, + scratch_pool); + if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_MERGE_TOOL || + err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) + { + char buf[1024]; + const char *message; + + message = svn_err_best_message(err, buf, sizeof(buf)); + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", + message)); + *external_failed = TRUE; + return svn_error_trace(err); + } + else if (err) + return svn_error_trace(err); + + if (remains_in_conflict) + option_id = svn_client_conflict_option_postpone; + else + option_id = svn_client_conflict_option_merged_text; + } + } + } + else if (accept_which == svn_cl__accept_recommended) + { + svn_client_conflict_option_id_t recommended_id; + + if (tree_conflicted) + SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, + scratch_pool)); + recommended_id = svn_client_conflict_get_recommended_option_id(conflict); + if (recommended_id != svn_client_conflict_option_unspecified) + option_id = recommended_id; + else + option_id = svn_client_conflict_option_postpone; + } + else + SVN_ERR_MALFUNCTION(); + + /* If we are in interactive mode and either the user gave no --accept + * option or the option did not apply, then prompt. */ + if (option_id == svn_client_conflict_option_unspecified) + { + svn_boolean_t resolved = FALSE; + svn_boolean_t postponed = FALSE; + svn_boolean_t printed_description = FALSE; + svn_error_t *err; + + *quit = FALSE; + + while (!resolved && !postponed && !*quit) + { + err = resolve_conflict_interactively(&resolved, &postponed, quit, + external_failed, + printed_summary, + &printed_description, + conflict, + editor_cmd, ctx->config, + path_prefix, pb, + conflict_stats, ctx, + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE) + { + /* Conflict resolution has failed. Let the user try again. + * It is always possible to break out of this loop with + * the 'quit' or 'postpone' options. */ + svn_handle_warning2(stderr, err, "svn: "); + svn_error_clear(err); + err = SVN_NO_ERROR; + } + SVN_ERR(err); + } + } + else if (option_id != svn_client_conflict_option_postpone) + SVN_ERR(mark_conflict_resolved(conflict, option_id, + text_conflicted, + props_conflicted->nelts > 0 ? "" : NULL, + tree_conflicted, + path_prefix, conflict_stats, + ctx, scratch_pool)); + return SVN_NO_ERROR; } diff --git a/subversion/svn/diff-cmd.c b/subversion/svn/diff-cmd.c index 71853c7f27768..9e389ec856a69 100644 --- a/subversion/svn/diff-cmd.c +++ b/subversion/svn/diff-cmd.c @@ -233,6 +233,43 @@ svn_cl__diff(apr_getopt_t *os, svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "paths", SVN_VA_NULL); SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); } + if (opt_state->diff.summarize) + { + if (opt_state->diff.use_git_diff_format) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--git"); + if (opt_state->diff.patch_compatible) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--patch-compatible"); + if (opt_state->diff.show_copies_as_adds) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--show-copies-as-adds"); + if (opt_state->diff.internal_diff) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--internal-diff"); + if (opt_state->diff.diff_cmd) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--diff-cmd"); + if (opt_state->diff.no_diff_added) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--no-diff-added"); + if (opt_state->diff.no_diff_deleted) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--no-diff-deleted"); + if (opt_state->force) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' not valid with '--summarize' option"), + "--force"); + /* Not handling ignore-properties, and properties-only as there should + be a patch adding support for these being applied soon */ + } SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, diff --git a/subversion/svn/help-cmd.c b/subversion/svn/help-cmd.c index b095d30fd1c24..fc3558683033f 100644 --- a/subversion/svn/help-cmd.c +++ b/subversion/svn/help-cmd.c @@ -53,8 +53,9 @@ svn_cl__help(apr_getopt_t *os, N_("usage: svn <subcommand> [options] [args]\n" "Subversion command-line client.\n" "Type 'svn help <subcommand>' for help on a specific subcommand.\n" - "Type 'svn --version' to see the program version and RA modules\n" - " or 'svn --version --quiet' to see just the version number.\n" + "Type 'svn --version' to see the program version and RA modules,\n" + " 'svn --version --verbose' to see dependency versions as well,\n" + " 'svn --version --quiet' to see just the version number.\n" "\n" "Most subcommands take file and/or directory arguments, recursing\n" "on the directories. If no arguments are supplied to such a\n" @@ -147,21 +148,24 @@ svn_cl__help(apr_getopt_t *os, _("\nThe following authentication credential caches are available:\n\n")); /*### There is no API to query available providers at run time. */ + if (config_path) + { #if (defined(WIN32) && !defined(__MINGW32__)) - version_footer = - svn_stringbuf_create(apr_psprintf(pool, _("%s* Wincrypt cache in %s\n"), - version_footer->data, - svn_dirent_local_style(config_path, - pool)), - pool); + version_footer = + svn_stringbuf_create(apr_psprintf(pool, _("%s* Wincrypt cache in %s\n"), + version_footer->data, + svn_dirent_local_style(config_path, + pool)), + pool); #elif !defined(SVN_DISABLE_PLAINTEXT_PASSWORD_STORAGE) - version_footer = - svn_stringbuf_create(apr_psprintf(pool, _("%s* Plaintext cache in %s\n"), - version_footer->data, - svn_dirent_local_style(config_path, - pool)), - pool); + version_footer = + svn_stringbuf_create(apr_psprintf(pool, _("%s* Plaintext cache in %s\n"), + version_footer->data, + svn_dirent_local_style(config_path, + pool)), + pool); #endif + } #ifdef SVN_HAVE_GNOME_KEYRING svn_stringbuf_appendcstr(version_footer, "* Gnome Keyring\n"); #endif @@ -181,7 +185,7 @@ svn_cl__help(apr_getopt_t *os, opt_state ? opt_state->quiet : FALSE, opt_state ? opt_state->verbose : FALSE, version_footer->data, - help_header, /* already gettext()'d */ + _(help_header), svn_cl__cmd_table, svn_cl__options, svn_cl__global_options, diff --git a/subversion/svn/info-cmd.c b/subversion/svn/info-cmd.c index fafc398c5f6c0..e0c0041b468a0 100644 --- a/subversion/svn/info-cmd.c +++ b/subversion/svn/info-cmd.c @@ -162,6 +162,9 @@ typedef struct print_info_baton_t /* Did we already print a line of output? */ svn_boolean_t start_new_line; + + /* The client context. */ + svn_client_ctx_t *ctx; } print_info_baton_t; @@ -391,15 +394,24 @@ print_info_xml(void *baton, if (info->wc_info && info->wc_info->conflicts) { int i; + apr_pool_t *iterpool; + iterpool = svn_pool_create(pool); for (i = 0; i < info->wc_info->conflicts->nelts; i++) { - const svn_wc_conflict_description2_t *conflict = + const svn_wc_conflict_description2_t *desc = APR_ARRAY_IDX(info->wc_info->conflicts, i, const svn_wc_conflict_description2_t *); + svn_client_conflict_t *conflict; + + svn_pool_clear(iterpool); - SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, pool)); + SVN_ERR(svn_client_conflict_get(&conflict, desc->local_abspath, + receiver_baton->ctx, + iterpool, iterpool)); + SVN_ERR(svn_cl__append_conflict_info_xml(sb, conflict, iterpool)); } + svn_pool_destroy(iterpool); } if (info->lock) @@ -581,68 +593,93 @@ print_info(void *baton, if (info->wc_info->conflicts) { - svn_boolean_t printed_prop_conflict_file = FALSE; svn_boolean_t printed_tc = FALSE; - int i; + svn_stringbuf_t *conflicted_props = NULL; + svn_client_conflict_t *conflict; + svn_boolean_t text_conflicted; + apr_array_header_t *props_conflicted; + svn_boolean_t tree_conflicted; + const svn_wc_conflict_description2_t *desc2 = + APR_ARRAY_IDX(info->wc_info->conflicts, 0, + const svn_wc_conflict_description2_t *); + + SVN_ERR(svn_client_conflict_get(&conflict, desc2->local_abspath, + receiver_baton->ctx, pool, pool)); + SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, + &props_conflicted, + &tree_conflicted, + conflict, pool, pool)); + if (text_conflicted) + { + const char *base_abspath = NULL; + const char *my_abspath = NULL; + const char *their_abspath = NULL; + + SVN_ERR(svn_client_conflict_text_get_contents( + NULL, &my_abspath, &base_abspath, &their_abspath, + conflict, pool, pool)); + + if (base_abspath) + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Previous Base File: %s\n"), + svn_cl__local_style_skip_ancestor( + receiver_baton->path_prefix, + base_abspath, + pool))); + + if (my_abspath) + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Previous Working File: %s\n"), + svn_cl__local_style_skip_ancestor( + receiver_baton->path_prefix, + my_abspath, + pool))); + + if (their_abspath) + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict Current Base File: %s\n"), + svn_cl__local_style_skip_ancestor( + receiver_baton->path_prefix, + their_abspath, + pool))); + } - for (i = 0; i < info->wc_info->conflicts->nelts; i++) + if (props_conflicted) { - const svn_wc_conflict_description2_t *conflict = - APR_ARRAY_IDX(info->wc_info->conflicts, i, - const svn_wc_conflict_description2_t *); - const char *desc; + int i; - switch (conflict->kind) + for (i = 0; i < props_conflicted->nelts; i++) { - case svn_wc_conflict_kind_text: - if (conflict->base_abspath) - SVN_ERR(svn_cmdline_printf(pool, - _("Conflict Previous Base File: %s\n"), - svn_cl__local_style_skip_ancestor( - receiver_baton->path_prefix, - conflict->base_abspath, - pool))); - - if (conflict->my_abspath) - SVN_ERR(svn_cmdline_printf(pool, - _("Conflict Previous Working File: %s\n"), - svn_cl__local_style_skip_ancestor( - receiver_baton->path_prefix, - conflict->my_abspath, - pool))); - - if (conflict->their_abspath) - SVN_ERR(svn_cmdline_printf(pool, - _("Conflict Current Base File: %s\n"), - svn_cl__local_style_skip_ancestor( - receiver_baton->path_prefix, - conflict->their_abspath, - pool))); - break; - - case svn_wc_conflict_kind_property: - if (! printed_prop_conflict_file) - SVN_ERR(svn_cmdline_printf(pool, - _("Conflict Properties File: %s\n"), - svn_cl__local_style_skip_ancestor( - receiver_baton->path_prefix, - conflict->prop_reject_abspath, - pool))); - printed_prop_conflict_file = TRUE; - break; - - case svn_wc_conflict_kind_tree: - printed_tc = TRUE; - SVN_ERR( - svn_cl__get_human_readable_tree_conflict_description( - &desc, conflict, pool)); - - SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n", - _("Tree conflict"), desc)); - break; + const char *name; + + name = APR_ARRAY_IDX(props_conflicted, i, const char *); + if (conflicted_props == NULL) + conflicted_props = svn_stringbuf_create(name, pool); + else + { + svn_stringbuf_appendbyte(conflicted_props, ' '); + svn_stringbuf_appendcstr(conflicted_props, name); + } } } + if (tree_conflicted) + { + const char *desc; + + printed_tc = TRUE; + SVN_ERR( + svn_cl__get_human_readable_tree_conflict_description( + &desc, conflict, pool)); + + SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n", + _("Tree conflict"), desc)); + } + + if (conflicted_props) + SVN_ERR(svn_cmdline_printf(pool, _("Conflicted Properties: %s\n"), + conflicted_props->data)); + /* 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 @@ -650,30 +687,40 @@ print_info(void *baton, { 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 *); + const char *repos_root_url; + const char *repos_relpath; + svn_revnum_t peg_rev; + svn_node_kind_t node_kind; if (!printed_tc) { const char *desc; SVN_ERR(svn_cl__get_human_readable_action_description(&desc, - svn_wc_conflict_action_edit, - conflict->operation, - conflict->node_kind, pool)); + svn_wc_conflict_action_edit, + svn_client_conflict_get_operation(conflict), + info->kind, + pool)); SVN_ERR(svn_cmdline_printf(pool, "%s: %s\n", _("Conflict Details"), desc)); } + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, pool, pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &repos_relpath, &peg_rev, &node_kind, conflict, + pool, pool)); src_left_version = - svn_cl__node_description(conflict->src_left_version, - info->repos_root_URL, pool); + svn_cl__node_description(repos_root_url, repos_relpath, + peg_rev, node_kind, info->repos_root_URL, pool); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &repos_relpath, &peg_rev, &node_kind, conflict, + pool, pool)); src_right_version = - svn_cl__node_description(conflict->src_right_version, - info->repos_root_URL, pool); + svn_cl__node_description(repos_root_url, repos_relpath, + peg_rev, node_kind, info->repos_root_URL, pool); if (src_left_version) SVN_ERR(svn_cmdline_printf(pool, " %s: %s\n", @@ -874,6 +921,8 @@ svn_cl__info(apr_getopt_t *os, /* Add "." if user passed 0 arguments. */ svn_opt_push_implicit_dot_target(targets, pool); + receiver_baton.ctx = ctx; + if (opt_state->xml) { receiver = print_info_xml; diff --git a/subversion/svn/list-cmd.c b/subversion/svn/list-cmd.c index 5ea140fc93d1b..da18252243d90 100644 --- a/subversion/svn/list-cmd.c +++ b/subversion/svn/list-cmd.c @@ -351,6 +351,8 @@ svn_cl__list(apr_getopt_t *os, const char *target = APR_ARRAY_IDX(targets, i, const char *); const char *truepath; svn_opt_revision_t peg_revision; + apr_array_header_t *patterns = NULL; + int k; /* Initialize the following variables for every list target. */ @@ -375,8 +377,33 @@ svn_cl__list(apr_getopt_t *os, SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); } - err = svn_client_list3(truepath, &peg_revision, - &(opt_state->start_revision), + if (opt_state->search_patterns) + { + patterns = apr_array_make(subpool, 4, sizeof(const char *)); + for (k = 0; k < opt_state->search_patterns->nelts; ++k) + { + apr_array_header_t *pattern_group + = APR_ARRAY_IDX(opt_state->search_patterns, k, + apr_array_header_t *); + const char *pattern; + + /* Should never fail but ... */ + if (pattern_group->nelts != 1) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'search-and' option is not supported")); + + pattern = APR_ARRAY_IDX(pattern_group, 0, const char *); +#if defined(WIN32) + /* As we currently can't pass glob patterns via the Windows + CLI, fall back to sub-string search. */ + pattern = apr_psprintf(subpool, "*%s*", pattern); +#endif + APR_ARRAY_PUSH(patterns, const char *) = pattern; + } + } + + err = svn_client_list4(truepath, &peg_revision, + &(opt_state->start_revision), patterns, opt_state->depth, dirent_fields, (opt_state->xml || opt_state->verbose), diff --git a/subversion/svn/log-cmd.c b/subversion/svn/log-cmd.c index 44f8a4cff2927..57f8415069143 100644 --- a/subversion/svn/log-cmd.c +++ b/subversion/svn/log-cmd.c @@ -38,6 +38,7 @@ #include "private/svn_cmdline_private.h" #include "private/svn_sorts_private.h" +#include "private/svn_utf_private.h" #include "cl.h" #include "cl-log.h" @@ -110,6 +111,24 @@ display_diff(const svn_log_entry_t *log_entry, return SVN_NO_ERROR; } +/* Return TRUE if STR matches PATTERN. Else, return FALSE. Assumes that + * PATTERN is a UTF-8 string prepared for case- and accent-insensitive + * comparison via svn_utf__xfrm(). */ +static svn_boolean_t +match(const char *pattern, const char *str, svn_membuf_t *buf) +{ + svn_error_t *err; + + err = svn_utf__xfrm(&str, str, strlen(str), TRUE, TRUE, buf); + if (err) + { + /* Can't match invalid data. */ + svn_error_clear(err); + return FALSE; + } + + return apr_fnmatch(pattern, str, 0) == APR_SUCCESS; +} /* 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. @@ -120,22 +139,22 @@ match_search_pattern(const char *search_pattern, const char *date, const char *log_message, apr_hash_t *changed_paths, + svn_membuf_t *buf, 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) + if (author && match(pattern, author, buf)) return TRUE; /* Does the date the search pattern? */ - if (date && apr_fnmatch(pattern, date, flags) == APR_SUCCESS) + if (date && match(pattern, date, buf)) return TRUE; /* Does the log message the search pattern? */ - if (log_message && apr_fnmatch(pattern, log_message, flags) == APR_SUCCESS) + if (log_message && match(pattern, log_message, buf)) return TRUE; if (changed_paths) @@ -150,15 +169,14 @@ match_search_pattern(const char *search_pattern, const char *path = apr_hash_this_key(hi); svn_log_changed_path2_t *log_item; - if (apr_fnmatch(pattern, path, flags) == APR_SUCCESS) + if (match(pattern, path, buf)) return TRUE; /* Match copy-from paths, too. */ log_item = apr_hash_this_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) + && match(pattern, log_item->copyfrom_path, buf)) return TRUE; } } @@ -168,13 +186,14 @@ match_search_pattern(const char *search_pattern, /* 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. */ + * BUF and SCRATCH_POOL are 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, + svn_membuf_t *buf, apr_pool_t *scratch_pool) { int i; @@ -197,7 +216,7 @@ match_search_patterns(apr_array_header_t *search_patterns, pattern = APR_ARRAY_IDX(pattern_group, j, const char *); match = match_search_pattern(pattern, author, date, message, - changed_paths, iterpool); + changed_paths, buf, iterpool); if (!match) break; } @@ -331,7 +350,7 @@ svn_cl__log_entry_receiver(void *baton, if (lb->search_patterns && ! match_search_patterns(lb->search_patterns, author, date, message, - log_entry->changed_paths2, pool)) + log_entry->changed_paths2, &lb->buffer, pool)) { if (log_entry->has_children) { @@ -535,7 +554,7 @@ svn_cl__log_entry_receiver_xml(void *baton, /* Match search pattern before XML-escaping. */ if (lb->search_patterns && ! match_search_patterns(lb->search_patterns, author, date, message, - log_entry->changed_paths2, pool)) + log_entry->changed_paths2, &lb->buffer, pool)) { if (log_entry->has_children) { @@ -795,6 +814,7 @@ svn_cl__log(apr_getopt_t *os, lb.diff_extensions = opt_state->extensions; lb.merge_stack = NULL; lb.search_patterns = opt_state->search_patterns; + svn_membuf__create(&lb.buffer, 0, pool); lb.pool = pool; if (opt_state->xml) diff --git a/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c index cbc818b89f7cc..f5c19198a5b29 100644 --- a/subversion/svn/merge-cmd.c +++ b/subversion/svn/merge-cmd.c @@ -150,6 +150,85 @@ run_merge(svn_boolean_t two_sources_specified, return merge_err; } +/* Baton type for conflict_func_merge_cmd(). */ +struct conflict_func_merge_cmd_baton { + svn_cl__accept_t accept_which; + const char *path_prefix; + svn_cl__conflict_stats_t *conflict_stats; +}; + +/* This implements the `svn_wc_conflict_resolver_func2_t ' interface. + * + * The merge subcommand needs to install this legacy conflict callback + * in case the user passed an --accept option to 'svn merge'. + * Otherwise, merges involving multiple editor drives might encounter a + * conflict during one of the editor drives and abort with an error, + * rather than resolving conflicts as per the --accept option and + * continuing with the next editor drive. + * ### TODO add an svn_client_merge API that makes this callback unnecessary + */ +static svn_error_t * +conflict_func_merge_cmd(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) +{ + struct conflict_func_merge_cmd_baton *b = baton; + svn_wc_conflict_choice_t choice; + + switch (b->accept_which) + { + case svn_cl__accept_postpone: + case svn_cl__accept_invalid: + case svn_cl__accept_unspecified: + case svn_cl__accept_recommended: + /* Postpone or no valid --accept option, postpone the conflict. */ + choice = svn_wc_conflict_choose_postpone; + break; + case svn_cl__accept_base: + choice = svn_wc_conflict_choose_base; + break; + case svn_cl__accept_working: + choice = svn_wc_conflict_choose_merged; + break; + case svn_cl__accept_mine_conflict: + choice = svn_wc_conflict_choose_mine_conflict; + break; + case svn_cl__accept_theirs_conflict: + choice = svn_wc_conflict_choose_theirs_conflict; + break; + case svn_cl__accept_mine_full: + choice = svn_wc_conflict_choose_mine_full; + break; + case svn_cl__accept_theirs_full: + choice = svn_wc_conflict_choose_theirs_full; + break; + case svn_cl__accept_edit: + case svn_cl__accept_launch: + /* The 'edit' and 'launch' options used to be valid in Subversion 1.9 but + * we can't support these options for the purposes of this callback. */ + choice = svn_wc_conflict_choose_postpone; + break; + } + + *result = svn_wc_create_conflict_result(choice, NULL, result_pool); + + /* If we are resolving a conflict, adjust the summary of conflicts. */ + if (choice != svn_wc_conflict_choose_postpone) + { + const char *local_path; + + local_path = svn_cl__local_style_skip_ancestor(b->path_prefix, + desc->local_abspath, + scratch_pool); + svn_cl__conflict_stats_resolved(b->conflict_stats, local_path, + desc->kind); + } + + return SVN_NO_ERROR; +} + /* This implements the `svn_opt_subcommand_t' interface. */ svn_error_t * svn_cl__merge(apr_getopt_t *os, @@ -157,6 +236,8 @@ svn_cl__merge(apr_getopt_t *os, apr_pool_t *pool) { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_cl__conflict_stats_t *conflict_stats = + ((svn_cl__cmd_baton_t *) baton)->conflict_stats; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; apr_array_header_t *targets; const char *sourcepath1 = NULL, *sourcepath2 = NULL, *targetpath = ""; @@ -165,6 +246,7 @@ svn_cl__merge(apr_getopt_t *os, 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; + apr_array_header_t *conflicted_paths; svn_boolean_t has_explicit_target = FALSE; /* Merge doesn't support specifying a revision or revision range @@ -432,6 +514,22 @@ svn_cl__merge(apr_getopt_t *os, "with --reintegrate")); } + /* Install a legacy conflict handler if the --accept option was given. + * Else, svn_client_merge5() may abort the merge in an undesirable way. + * See the docstring at conflict_func_merge_cmd() for details */ + if (opt_state->accept_which != svn_cl__accept_unspecified) + { + struct conflict_func_merge_cmd_baton *b = apr_pcalloc(pool, sizeof(*b)); + + b->accept_which = opt_state->accept_which; + SVN_ERR(svn_dirent_get_absolute(&b->path_prefix, "", pool)); + b->conflict_stats = conflict_stats; + + ctx->conflict_func2 = conflict_func_merge_cmd; + ctx->conflict_baton2 = b; + } + +retry: merge_err = run_merge(two_sources_specified, sourcepath1, peg_revision1, sourcepath2, @@ -447,6 +545,31 @@ svn_cl__merge(apr_getopt_t *os, "fix invalid mergeinfo in target with 'svn propset'")); } + /* Run the interactive resolver if conflicts were raised. */ + SVN_ERR(svn_cl__conflict_stats_get_paths(&conflicted_paths, conflict_stats, + pool, pool)); + if (conflicted_paths) + { + SVN_ERR(svn_cl__walk_conflicts(conflicted_paths, conflict_stats, + opt_state, ctx, pool)); + if (merge_err && + svn_error_root_cause(merge_err)->apr_err == SVN_ERR_WC_FOUND_CONFLICT) + { + svn_error_t *err; + + /* Check if all conflicts were resolved just now. */ + err = svn_cl__conflict_stats_get_paths(&conflicted_paths, + conflict_stats, pool, pool); + if (err) + merge_err = svn_error_compose_create(merge_err, err); + else if (conflicted_paths == NULL) + { + svn_error_clear(merge_err); + goto retry; /* ### conflicts resolved; continue merging */ + } + } + } + if (!opt_state->quiet) { svn_error_t *err = svn_cl__notifier_print_conflict_stats( diff --git a/subversion/svn/notify.c b/subversion/svn/notify.c index c530694c7adf3..c15301e32b8bf 100644 --- a/subversion/svn/notify.c +++ b/subversion/svn/notify.c @@ -39,6 +39,7 @@ #include "svn_hash.h" #include "cl.h" #include "private/svn_subr_private.h" +#include "private/svn_sorts_private.h" #include "private/svn_dep_compat.h" #include "svn_private_config.h" @@ -53,6 +54,7 @@ struct notify_baton svn_boolean_t is_wc_to_repos_copy; svn_boolean_t sent_first_txdelta; int in_external; + svn_revnum_t progress_revision; svn_boolean_t had_print_error; /* Used to not keep printing error messages when we've already had one print error. */ @@ -145,6 +147,76 @@ resolved_str(apr_pool_t *pool, int n_resolved) } svn_error_t * +svn_cl__conflict_stats_get_paths(apr_array_header_t **conflicted_paths, + svn_cl__conflict_stats_t *conflict_stats, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + + int n_text = apr_hash_count(conflict_stats->text_conflicts); + int n_prop = apr_hash_count(conflict_stats->prop_conflicts); + int n_tree = apr_hash_count(conflict_stats->tree_conflicts); + apr_hash_t *all_conflicts; + + *conflicted_paths = NULL; + if (n_text == 0 && n_prop == 0 && n_tree == 0) + return SVN_NO_ERROR; + + /* Use a hash table to ensure paths with multiple conflicts are + * returned just once. */ + all_conflicts = apr_hash_make(result_pool); + if (n_text > 0) + { + apr_array_header_t *k_text; + int i; + + SVN_ERR(svn_hash_keys(&k_text, conflict_stats->text_conflicts, + scratch_pool)); + for (i = 0; i < k_text->nelts; i++) + { + const char *path = APR_ARRAY_IDX(k_text, i, const char *); + + svn_hash_sets(all_conflicts, path, ""); + } + } + + if (n_prop > 0) + { + apr_array_header_t *k_prop; + int i; + + SVN_ERR(svn_hash_keys(&k_prop, conflict_stats->prop_conflicts, + scratch_pool)); + for (i = 0; i < k_prop->nelts; i++) + { + const char *path = APR_ARRAY_IDX(k_prop, i, const char *); + + svn_hash_sets(all_conflicts, path, ""); + } + } + + if (n_tree > 0) + { + apr_array_header_t *k_tree; + int i; + + SVN_ERR(svn_hash_keys(&k_tree, conflict_stats->tree_conflicts, + scratch_pool)); + for (i = 0; i < k_tree->nelts; i++) + { + const char *path = APR_ARRAY_IDX(k_tree, i, const char *); + + svn_hash_sets(all_conflicts, path, ""); + } + } + + svn_hash_keys(conflicted_paths, all_conflicts, result_pool); + svn_sort__array(*conflicted_paths, svn_sort_compare_paths); + + return SVN_NO_ERROR; +} + +svn_error_t * svn_cl__print_conflict_stats(svn_cl__conflict_stats_t *conflict_stats, apr_pool_t *scratch_pool) { @@ -253,6 +325,13 @@ notify_body(struct notify_baton *nb, _("Skipped target: '%s' -- copy-source is missing\n"), path_local)); } + else if (n->content_state == svn_wc_notify_state_obstructed) + { + SVN_ERR(svn_cmdline_printf( + pool, + _("Skipped '%s' -- obstructed by unversioned node\n"), + path_local)); + } else { SVN_ERR(svn_cmdline_printf(pool, _("Skipped '%s'\n"), path_local)); @@ -372,6 +451,56 @@ notify_body(struct notify_baton *nb, path_local)); break; + case svn_wc_notify_resolved_text: + SVN_ERR(svn_cmdline_printf(pool, + _("Merge conflicts in '%s' marked as " + "resolved.\n"), + path_local)); + break; + + case svn_wc_notify_resolved_prop: + SVN_ERR_ASSERT(n->prop_name && strlen(n->prop_name) > 0); + SVN_ERR(svn_cmdline_printf(pool, + _("Conflict in property '%s' at '%s' marked " + "as resolved.\n"), + n->prop_name, path_local)); + break; + + case svn_wc_notify_resolved_tree: + SVN_ERR(svn_cmdline_printf(pool, + _("Tree conflict at '%s' marked as " + "resolved.\n"), + path_local)); + break; + + case svn_wc_notify_begin_search_tree_conflict_details: + SVN_ERR(svn_cmdline_printf(pool, + _("Searching tree conflict details for '%s' " + "in repository:\n"), + path_local)); + nb->progress_revision = 0; + break; + + case svn_wc_notify_tree_conflict_details_progress: + /* First printf is to obliterate any previous progress printf, + assuming no more than 10 digit revisions. Avoid i18n so the + text length is known. We only need to do this if the new + revision is 4 digits less than the previous revision but that + requires counting digits. Dividing by 1000 works well + enough: it triggers when needed, it sometimes triggers when + not needed, but in typical cases it doesn't trigger as the + revisions don't vary much. */ + if (n->revision < nb->progress_revision / 1000) + SVN_ERR(svn_cmdline_printf(pool, "\rChecking r ")); + SVN_ERR(svn_cmdline_printf(pool, "\rChecking r%ld...", n->revision)); + nb->progress_revision = n->revision; + break; + + case svn_wc_notify_end_search_tree_conflict_details: + SVN_ERR(svn_cmdline_printf(pool, _(" done\n"))); + nb->progress_revision = 0; + break; + case svn_wc_notify_add: /* We *should* only get the MIME_TYPE if PATH is a file. If we do get it, and the mime-type is not textual, note that this @@ -415,8 +544,10 @@ notify_body(struct notify_baton *nb, store_path(nb, nb->conflict_stats->prop_conflicts, path_local); statchar_buf[1] = 'C'; } + else if (n->prop_state == svn_wc_notify_state_merged) + statchar_buf[1] = 'G'; else if (n->prop_state == svn_wc_notify_state_changed) - statchar_buf[1] = 'U'; + statchar_buf[1] = 'U'; if (statchar_buf[0] != ' ' || statchar_buf[1] != ' ') { @@ -1118,6 +1249,7 @@ svn_cl__get_notifier(svn_wc_notify_func2_t *notify_func_p, nb->is_export = FALSE; nb->is_wc_to_repos_copy = FALSE; nb->in_external = 0; + nb->progress_revision = 0; nb->had_print_error = FALSE; nb->conflict_stats = conflict_stats; SVN_ERR(svn_dirent_get_absolute(&nb->path_prefix, "", pool)); diff --git a/subversion/svn/propdel-cmd.c b/subversion/svn/propdel-cmd.c index 28c95970b41d9..b396b5dc3cf68 100644 --- a/subversion/svn/propdel-cmd.c +++ b/subversion/svn/propdel-cmd.c @@ -49,13 +49,13 @@ svn_cl__propdel(apr_getopt_t *os, { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; - const char *pname, *pname_utf8; + const char *pname; apr_array_header_t *args, *targets; /* Get the property's name (and a UTF-8 version of that name). */ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); pname = APR_ARRAY_IDX(args, 0, const char *); - SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool)); + SVN_ERR(svn_utf_cstring_to_utf8(&pname, pname, pool)); /* No need to check svn_prop_name_is_valid for *deleting* properties, and it may even be useful to allow, in case invalid properties sneaked through somehow. */ @@ -78,7 +78,7 @@ svn_cl__propdel(apr_getopt_t *os, &URL, ctx, pool)); /* Let libsvn_client do the real work. */ - SVN_ERR(svn_client_revprop_set2(pname_utf8, NULL, NULL, + SVN_ERR(svn_client_revprop_set2(pname, NULL, NULL, URL, &(opt_state->start_revision), &rev, FALSE, ctx, pool)); } @@ -94,7 +94,7 @@ svn_cl__propdel(apr_getopt_t *os, opt_state->depth = svn_depth_empty; /* For each target, remove the property PNAME. */ - SVN_ERR(svn_client_propset_local(pname_utf8, NULL, targets, + SVN_ERR(svn_client_propset_local(pname, NULL, targets, opt_state->depth, FALSE, opt_state->changelists, ctx, pool)); } diff --git a/subversion/svn/propedit-cmd.c b/subversion/svn/propedit-cmd.c index 520fe6c00c871..59ef24bda93b7 100644 --- a/subversion/svn/propedit-cmd.c +++ b/subversion/svn/propedit-cmd.c @@ -47,7 +47,7 @@ /*** Code. ***/ struct commit_info_baton { - const char *pname_utf8; + const char *pname; const char *target_local; }; @@ -60,7 +60,7 @@ commit_info_handler(const svn_commit_info_t *commit_info, SVN_ERR(svn_cmdline_printf(pool, _("Set new value for property '%s' on '%s'\n"), - cib->pname_utf8, cib->target_local)); + cib->pname, cib->target_local)); SVN_ERR(svn_cl__print_commit_info(commit_info, NULL, pool)); return SVN_NO_ERROR; @@ -74,23 +74,23 @@ svn_cl__propedit(apr_getopt_t *os, { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; - const char *pname, *pname_utf8; + const char *pname; apr_array_header_t *args, *targets; /* Validate the input and get the property's name (and a UTF-8 version of that name). */ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); pname = APR_ARRAY_IDX(args, 0, const char *); - SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool)); - if (! svn_prop_name_is_valid(pname_utf8)) + SVN_ERR(svn_utf_cstring_to_utf8(&pname, pname, pool)); + if (! svn_prop_name_is_valid(pname)) return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, _("'%s' is not a valid Subversion property name"), - pname_utf8); + pname); if (!opt_state->force) - SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop, + SVN_ERR(svn_cl__check_svn_prop_name(pname, opt_state->revprop, svn_cl__prop_use_edit, pool)); - if (opt_state->encoding && !svn_prop_needs_translation(pname_utf8)) + if (opt_state->encoding && !svn_prop_needs_translation(pname)) return svn_error_create (SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("--encoding option applies only to textual" @@ -120,7 +120,7 @@ svn_cl__propedit(apr_getopt_t *os, &URL, ctx, pool)); /* Fetch the current property. */ - SVN_ERR(svn_client_revprop_get(pname_utf8, &propval, + SVN_ERR(svn_client_revprop_get(pname, &propval, URL, &(opt_state->start_revision), &rev, ctx, pool)); @@ -145,13 +145,13 @@ svn_cl__propedit(apr_getopt_t *os, opt_state->editor_cmd, temp_dir, propval, "svn-prop", ctx->config, - svn_prop_needs_translation(pname_utf8), + svn_prop_needs_translation(pname), opt_state->encoding, pool)); /* ...and re-set the property's value accordingly. */ if (propval) { - SVN_ERR(svn_client_revprop_set2(pname_utf8, + SVN_ERR(svn_client_revprop_set2(pname, propval, &original_propval, URL, &(opt_state->start_revision), &rev, opt_state->force, ctx, pool)); @@ -160,13 +160,13 @@ svn_cl__propedit(apr_getopt_t *os, (svn_cmdline_printf (pool, _("Set new value for property '%s' on revision %ld\n"), - pname_utf8, rev)); + pname, rev)); } else { SVN_ERR(svn_cmdline_printf (pool, _("No changes to property '%s' on revision %ld\n"), - pname_utf8, rev)); + pname, rev)); } } else if (opt_state->start_revision.kind != svn_opt_revision_unspecified) @@ -174,7 +174,7 @@ svn_cl__propedit(apr_getopt_t *os, return svn_error_createf (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Cannot specify revision for editing versioned property '%s'"), - pname_utf8); + pname); } else /* operate on a normal, versioned property (not a revprop) */ { @@ -206,7 +206,7 @@ svn_cl__propedit(apr_getopt_t *os, SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); - cib.pname_utf8 = pname_utf8; + cib.pname = pname; /* For each target, edit the property PNAME. */ for (i = 0; i < targets->nelts; i++) @@ -234,7 +234,7 @@ svn_cl__propedit(apr_getopt_t *os, peg_revision.kind = svn_opt_revision_unspecified; /* Fetch the current property. */ - SVN_ERR(svn_client_propget5(&props, NULL, pname_utf8, abspath_or_url, + SVN_ERR(svn_client_propget5(&props, NULL, pname, abspath_or_url, &peg_revision, &(opt_state->start_revision), &base_rev, svn_depth_empty, @@ -282,7 +282,7 @@ svn_cl__propedit(apr_getopt_t *os, "svn-prop", ctx->config, svn_prop_needs_translation - (pname_utf8), + (pname), opt_state->encoding, subpool)); @@ -295,7 +295,7 @@ svn_cl__propedit(apr_getopt_t *os, { svn_error_t *err = SVN_NO_ERROR; - svn_cl__check_boolean_prop_val(pname_utf8, edited_propval->data, + svn_cl__check_boolean_prop_val(pname, edited_propval->data, subpool); if (ctx->log_msg_func3) @@ -304,7 +304,7 @@ svn_cl__propedit(apr_getopt_t *os, subpool)); if (svn_path_is_url(target)) { - err = svn_client_propset_remote(pname_utf8, edited_propval, + err = svn_client_propset_remote(pname, edited_propval, target, opt_state->force, base_rev, opt_state->revprop_table, @@ -319,9 +319,9 @@ svn_cl__propedit(apr_getopt_t *os, APR_ARRAY_PUSH(targs, const char *) = target; SVN_ERR(svn_cl__propset_print_binary_mime_type_warning( - targs, pname_utf8, propval, subpool)); + targs, pname, propval, subpool)); - err = svn_client_propset_local(pname_utf8, edited_propval, + err = svn_client_propset_local(pname, edited_propval, targs, svn_depth_empty, opt_state->force, NULL, ctx, subpool); @@ -339,14 +339,14 @@ svn_cl__propedit(apr_getopt_t *os, if (!svn_path_is_url(target)) SVN_ERR(svn_cmdline_printf( subpool, _("Set new value for property '%s' on '%s'\n"), - pname_utf8, target_local)); + pname, target_local)); } else { SVN_ERR (svn_cmdline_printf (subpool, _("No changes to property '%s' on '%s'\n"), - pname_utf8, target_local)); + pname, target_local)); } } svn_pool_destroy(subpool); diff --git a/subversion/svn/propget-cmd.c b/subversion/svn/propget-cmd.c index fa04c202ce3dc..fe2d1bd3fba19 100644 --- a/subversion/svn/propget-cmd.c +++ b/subversion/svn/propget-cmd.c @@ -137,7 +137,7 @@ print_properties_xml(const char *pname, return SVN_NO_ERROR; } -/* Print the property PNAME_UTF with the value PROPVAL set on ABSPATH_OR_URL +/* Print the property PNAME with the value PROPVAL set on ABSPATH_OR_URL to the stream OUT. If INHERITED_PROPERTY is true then the property described is inherited, @@ -153,7 +153,7 @@ print_single_prop(svn_string_t *propval, const char *abspath_or_URL, const char *wc_path_prefix, svn_stream_t *out, - const char *pname_utf8, + const char *pname, svn_boolean_t print_filenames, svn_boolean_t omit_newline, svn_boolean_t like_proplist, @@ -211,14 +211,14 @@ print_single_prop(svn_string_t *propval, /* 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_hash_sets(hash, pname, 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)) + if (svn_prop_needs_translation(pname)) SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, scratch_pool)); @@ -244,7 +244,7 @@ print_single_prop(svn_string_t *propval, 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. + PNAME is the property name of all the properties. If PRINT_FILENAMES is true, print the item's path before each property. @@ -255,7 +255,7 @@ print_single_prop(svn_string_t *propval, static svn_error_t * print_properties(svn_stream_t *out, const char *target_abspath_or_url, - const char *pname_utf8, + const char *pname, apr_hash_t *props, apr_array_header_t *inherited_props, svn_boolean_t print_filenames, @@ -282,7 +282,7 @@ print_properties(svn_stream_t *out, iprop->prop_hash)); SVN_ERR(print_single_prop(propval, target_abspath_or_url, iprop->path_or_url, - path_prefix, out, pname_utf8, + path_prefix, out, pname, print_filenames, omit_newline, like_proplist, TRUE, iterpool)); } @@ -298,7 +298,7 @@ print_properties(svn_stream_t *out, svn_pool_clear(iterpool); SVN_ERR(print_single_prop(propval, target_abspath_or_url, filename, - path_prefix, out, pname_utf8, print_filenames, + path_prefix, out, pname, print_filenames, omit_newline, like_proplist, FALSE, iterpool)); } @@ -317,7 +317,7 @@ svn_cl__propget(apr_getopt_t *os, { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; - const char *pname, *pname_utf8; + const char *pname; apr_array_header_t *args, *targets; svn_stream_t *out; svn_boolean_t warned = FALSE; @@ -328,15 +328,14 @@ svn_cl__propget(apr_getopt_t *os, _("--verbose cannot be used with --revprop or " "--no-newline or --xml")); - /* PNAME is first argument (and PNAME_UTF8 will be a UTF-8 version - thereof) */ + /* PNAME is first argument */ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); pname = APR_ARRAY_IDX(args, 0, const char *); - SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool)); - if (! svn_prop_name_is_valid(pname_utf8)) + SVN_ERR(svn_utf_cstring_to_utf8(&pname, pname, pool)); + if (! svn_prop_name_is_valid(pname)) return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, _("'%s' is not a valid Subversion property name"), - pname_utf8); + pname); SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, @@ -363,7 +362,7 @@ svn_cl__propget(apr_getopt_t *os, &URL, ctx, pool)); /* Let libsvn_client do the real work. */ - SVN_ERR(svn_client_revprop_get(pname_utf8, &propval, + SVN_ERR(svn_client_revprop_get(pname, &propval, URL, &(opt_state->start_revision), &rev, ctx, pool)); @@ -372,7 +371,7 @@ svn_cl__propget(apr_getopt_t *os, return svn_error_createf(SVN_ERR_PROPERTY_NOT_FOUND, NULL, _("Property '%s' not found on " "revision %s"), - pname_utf8, + pname, svn_opt__revision_to_string( &opt_state->start_revision, pool)); @@ -390,7 +389,7 @@ svn_cl__propget(apr_getopt_t *os, "revprops", "rev", revstr, SVN_VA_NULL); - svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, FALSE, + svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool); svn_xml_make_close_tag(&sb, pool, "revprops"); @@ -405,7 +404,7 @@ svn_cl__propget(apr_getopt_t *os, /* If this is a special Subversion property, it is stored as UTF8 and LF, so convert to the native locale and eol-style. */ - if (svn_prop_needs_translation(pname_utf8)) + if (svn_prop_needs_translation(pname)) SVN_ERR(svn_subst_detranslate_string(&printable_val, propval, TRUE, pool)); @@ -462,7 +461,7 @@ svn_cl__propget(apr_getopt_t *os, SVN_ERR(svn_client_propget5( &props, opt_state->show_inherited_props ? &inherited_props : NULL, - pname_utf8, truepath, + pname, truepath, &peg_revision, &(opt_state->start_revision), NULL, opt_state->depth, @@ -491,7 +490,7 @@ svn_cl__propget(apr_getopt_t *os, svn_error_t *err; err = svn_error_createf(SVN_ERR_PROPERTY_NOT_FOUND, NULL, _("Property '%s' not found on '%s'"), - pname_utf8, target); + pname, target); svn_handle_warning2(stderr, err, "svn: "); svn_error_clear(err); warned = TRUE; @@ -499,12 +498,12 @@ svn_cl__propget(apr_getopt_t *os, if (opt_state->xml) SVN_ERR(print_properties_xml( - pname_utf8, props, + pname, props, opt_state->show_inherited_props ? inherited_props : NULL, subpool)); else SVN_ERR(print_properties( - out, truepath, pname_utf8, + out, truepath, pname, props, opt_state->show_inherited_props ? inherited_props : NULL, print_filenames, diff --git a/subversion/svn/propset-cmd.c b/subversion/svn/propset-cmd.c index 07b9bbdb57a49..05087069005ae 100644 --- a/subversion/svn/propset-cmd.c +++ b/subversion/svn/propset-cmd.c @@ -51,7 +51,7 @@ svn_cl__propset(apr_getopt_t *os, { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; - const char *pname, *pname_utf8; + const char *pname; svn_string_t *propval = NULL; svn_boolean_t propval_came_from_cmdline; apr_array_header_t *args, *targets; @@ -62,13 +62,13 @@ svn_cl__propset(apr_getopt_t *os, SVN_ERR(svn_opt_parse_num_args(&args, os, opt_state->filedata ? 1 : 2, scratch_pool)); pname = APR_ARRAY_IDX(args, 0, const char *); - SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, scratch_pool)); - if (! svn_prop_name_is_valid(pname_utf8)) + SVN_ERR(svn_utf_cstring_to_utf8(&pname, pname, scratch_pool)); + if (! svn_prop_name_is_valid(pname)) return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, _("'%s' is not a valid Subversion property name"), - pname_utf8); + pname); if (!opt_state->force) - SVN_ERR(svn_cl__check_svn_prop_name(pname_utf8, opt_state->revprop, + SVN_ERR(svn_cl__check_svn_prop_name(pname, opt_state->revprop, svn_cl__prop_use_set, scratch_pool)); /* Get the PROPVAL from either an external file, or from the command @@ -87,7 +87,7 @@ svn_cl__propset(apr_getopt_t *os, /* We only want special Subversion property values to be in UTF-8 and LF line endings. All other propvals are taken literally. */ - if (svn_prop_needs_translation(pname_utf8)) + if (svn_prop_needs_translation(pname)) SVN_ERR(svn_subst_translate_string2(&propval, NULL, NULL, propval, opt_state->encoding, FALSE, scratch_pool, scratch_pool)); @@ -120,7 +120,7 @@ svn_cl__propset(apr_getopt_t *os, &URL, ctx, scratch_pool)); /* Let libsvn_client do the real work. */ - SVN_ERR(svn_client_revprop_set2(pname_utf8, propval, NULL, + SVN_ERR(svn_client_revprop_set2(pname, propval, NULL, URL, &(opt_state->start_revision), &rev, opt_state->force, ctx, scratch_pool)); @@ -174,17 +174,17 @@ svn_cl__propset(apr_getopt_t *os, } SVN_ERR(svn_cl__propset_print_binary_mime_type_warning(targets, - pname_utf8, + pname, propval, scratch_pool)); - SVN_ERR(svn_client_propset_local(pname_utf8, propval, targets, + SVN_ERR(svn_client_propset_local(pname, propval, targets, opt_state->depth, opt_state->force, opt_state->changelists, ctx, scratch_pool)); if (! opt_state->quiet) - svn_cl__check_boolean_prop_val(pname_utf8, propval->data, scratch_pool); + svn_cl__check_boolean_prop_val(pname, propval->data, scratch_pool); } return SVN_NO_ERROR; diff --git a/subversion/svn/resolve-cmd.c b/subversion/svn/resolve-cmd.c index 035bf29aff5ab..bbf9ff548ef5a 100644 --- a/subversion/svn/resolve-cmd.c +++ b/subversion/svn/resolve-cmd.c @@ -30,6 +30,7 @@ #include "svn_client.h" #include "svn_error.h" #include "svn_pools.h" +#include "svn_hash.h" #include "cl.h" #include "svn_private_config.h" @@ -38,6 +39,127 @@ /*** Code. ***/ +struct conflict_walker_baton +{ + svn_client_ctx_t *ctx; + svn_cl__accept_t accept_which; + svn_boolean_t quit; + svn_boolean_t external_failed; + svn_boolean_t printed_summary; + const char *editor_cmd; + const char *path_prefix; + svn_cmdline_prompt_baton_t *pb; + svn_cl__conflict_stats_t *conflict_stats; +}; + +/* Implements svn_client_conflict_walk_func_t. */ +static svn_error_t * +conflict_walker(void *baton, svn_client_conflict_t *conflict, + apr_pool_t *scratch_pool) +{ + struct conflict_walker_baton *cwb = baton; + + SVN_ERR(svn_cl__resolve_conflict(&cwb->quit, &cwb->external_failed, + &cwb->printed_summary, conflict, + cwb->accept_which, cwb->editor_cmd, + cwb->path_prefix, cwb->pb, + cwb->conflict_stats, + cwb->ctx, scratch_pool)); + if (cwb->quit) + return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_cl__walk_conflicts(apr_array_header_t *targets, + svn_cl__conflict_stats_t *conflict_stats, + svn_cl__opt_state_t *opt_state, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_boolean_t had_error = FALSE; + svn_cmdline_prompt_baton_t *pb = apr_palloc(scratch_pool, sizeof(*pb)); + struct conflict_walker_baton cwb = { 0 }; + const char *path_prefix; + svn_error_t *err; + int i; + apr_pool_t *iterpool; + + SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", scratch_pool)); + + pb->cancel_func = ctx->cancel_func; + pb->cancel_baton = ctx->cancel_baton; + + cwb.ctx = ctx; + cwb.accept_which = opt_state->accept_which; + cwb.quit = FALSE; + cwb.external_failed = FALSE; + cwb.printed_summary = FALSE; + cwb.editor_cmd = opt_state->editor_cmd; + cwb.path_prefix = path_prefix; + cwb.pb = pb; + cwb.conflict_stats = conflict_stats; + + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *local_abspath; + svn_client_conflict_t *conflict; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); + + if (opt_state->depth == svn_depth_empty) + { + SVN_ERR(svn_client_conflict_get(&conflict, local_abspath, ctx, + iterpool, iterpool)); + err = svn_cl__resolve_conflict(&cwb.quit, &cwb.external_failed, + &cwb.printed_summary, + conflict, opt_state->accept_which, + opt_state->editor_cmd, + path_prefix, pb, conflict_stats, + ctx, iterpool); + } + else + err = svn_client_conflict_walk(local_abspath, opt_state->depth, + conflict_walker, &cwb, ctx, iterpool); + + if (err) + { + svn_error_t *root = svn_error_root_cause(err); + + if (root->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + /* ### Ignore. These errors can happen due to the working copy + * ### being re-arranged during tree conflict resolution. */ + svn_error_clear(err); + continue; + } + else if (root->apr_err == SVN_ERR_CANCELLED) + { + svn_error_clear(err); + break; + } + + svn_handle_warning2(stderr, svn_error_root_cause(err), "svn: "); + svn_error_clear(err); + had_error = TRUE; + } + } + svn_pool_destroy(iterpool); + + if (had_error) + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Failure occurred resolving one or more " + "conflicts")); + return SVN_NO_ERROR; +} + /* This implements the `svn_opt_subcommand_t' interface. */ svn_error_t * svn_cl__resolve(apr_getopt_t *os, @@ -45,44 +167,10 @@ svn_cl__resolve(apr_getopt_t *os, apr_pool_t *scratch_pool) { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_cl__conflict_stats_t *conflict_stats = + ((svn_cl__cmd_baton_t *) baton)->conflict_stats; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; - svn_wc_conflict_choice_t conflict_choice; - svn_error_t *err; apr_array_header_t *targets; - int i; - apr_pool_t *iterpool; - svn_boolean_t had_error = FALSE; - - switch (opt_state->accept_which) - { - case svn_cl__accept_working: - conflict_choice = svn_wc_conflict_choose_merged; - break; - case svn_cl__accept_base: - conflict_choice = svn_wc_conflict_choose_base; - break; - case svn_cl__accept_theirs_conflict: - conflict_choice = svn_wc_conflict_choose_theirs_conflict; - break; - case svn_cl__accept_mine_conflict: - conflict_choice = svn_wc_conflict_choose_mine_conflict; - break; - case svn_cl__accept_theirs_full: - conflict_choice = svn_wc_conflict_choose_theirs_full; - break; - case svn_cl__accept_mine_full: - conflict_choice = svn_wc_conflict_choose_mine_full; - break; - case svn_cl__accept_unspecified: - if (opt_state->non_interactive) - return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - _("missing --accept option")); - conflict_choice = svn_wc_conflict_choose_unspecified; - break; - default: - return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - _("invalid 'accept' ARG")); - } SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, @@ -103,29 +191,22 @@ svn_cl__resolve(apr_getopt_t *os, SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < targets->nelts; i++) + if (opt_state->accept_which == svn_cl__accept_unspecified && + opt_state->non_interactive) { - const char *target = APR_ARRAY_IDX(targets, i, const char *); - svn_pool_clear(iterpool); - SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); - err = svn_client_resolve(target, - opt_state->depth, conflict_choice, - ctx, - iterpool); - if (err) - { - svn_handle_warning2(stderr, err, "svn: "); - svn_error_clear(err); - had_error = TRUE; - } + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("missing --accept option")); + } + else if (opt_state->accept_which == svn_cl__accept_postpone || + opt_state->accept_which == svn_cl__accept_edit || + opt_state->accept_which == svn_cl__accept_launch) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("invalid 'accept' ARG")); } - svn_pool_destroy(iterpool); - if (had_error) - return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Failure occurred resolving one or more " - "conflicts")); + SVN_ERR(svn_cl__walk_conflicts(targets, conflict_stats, + opt_state, ctx, scratch_pool)); return SVN_NO_ERROR; } diff --git a/subversion/svn/shelve-cmd.c b/subversion/svn/shelve-cmd.c new file mode 100644 index 0000000000000..df88a651d9c25 --- /dev/null +++ b/subversion/svn/shelve-cmd.c @@ -0,0 +1,369 @@ +/* + * shelve-cmd.c -- Shelve commands. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL + +#include "svn_client.h" +#include "svn_error_codes.h" +#include "svn_error.h" +#include "svn_path.h" +#include "svn_utf.h" + +#include "cl.h" + +#include "svn_private_config.h" +#include "private/svn_sorts_private.h" + + +/* First argument should be the name of a shelved change. */ +static svn_error_t * +get_name(const char **name, + apr_getopt_t *os, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *args; + + SVN_ERR(svn_opt_parse_num_args(&args, os, 1, scratch_pool)); + SVN_ERR(svn_utf_cstring_to_utf8(name, + APR_ARRAY_IDX(args, 0, const char *), + result_pool)); + return SVN_NO_ERROR; +} + +/* A comparison function for svn_sort__hash(), comparing the mtime of two + svn_client_shelved_patch_info_t's. */ +static int +compare_shelved_patch_infos_by_mtime(const svn_sort__item_t *a, + const svn_sort__item_t *b) +{ + svn_client_shelved_patch_info_t *a_val = a->value; + svn_client_shelved_patch_info_t *b_val = b->value; + + return (a_val->dirent->mtime < b_val->dirent->mtime) + ? -1 : (a_val->dirent->mtime > b_val->dirent->mtime) ? 1 : 0; +} + +/* Return a list of shelved changes sorted by patch file mtime, oldest first. + */ +static svn_error_t * +list_sorted_by_date(apr_array_header_t **list, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + apr_hash_t *shelved_patch_infos; + + SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath, + ctx, scratch_pool, scratch_pool)); + *list = svn_sort__hash(shelved_patch_infos, + compare_shelved_patch_infos_by_mtime, + scratch_pool); + return SVN_NO_ERROR; +} + +#ifndef WIN32 +/* Run CMD with ARGS. + * Send its stdout to the parent's stdout. Disconnect its stdin and stderr. + */ +static svn_error_t * +run_cmd(const char *cmd, + const char *const *args, + apr_pool_t *scratch_pool) +{ + apr_status_t apr_err; + apr_file_t *outfile; + svn_error_t *err; + int exitcode; + + apr_err = apr_file_open_stdout(&outfile, scratch_pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, "Can't open stdout"); + + err = svn_io_run_cmd(NULL /*path*/, cmd, args, + &exitcode, NULL /*exitwhy*/, + TRUE /*inherit*/, + NULL /*infile*/, outfile, NULL /*errfile*/, + scratch_pool); + if (err || exitcode) + return svn_error_createf(SVN_ERR_EXTERNAL_PROGRAM, err, + _("Could not run external command '%s'"), cmd); + return SVN_NO_ERROR; +} +#endif + +/* Display a list of shelved changes */ +static svn_error_t * +shelves_list(const char *local_abspath, + svn_boolean_t diffstat, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *list; + int i; + + SVN_ERR(list_sorted_by_date(&list, + local_abspath, ctx, scratch_pool)); + + for (i = 0; i < list->nelts; i++) + { + const svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t); + const char *name = item->key; + svn_client_shelved_patch_info_t *info = item->value; + int age = (int)((apr_time_now() - info->mtime) / 1000000 / 60); + apr_hash_t *paths; + + SVN_ERR(svn_client_shelf_get_paths(&paths, + name, local_abspath, ctx, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("%-30s %6d mins old %10ld bytes %4d paths changed\n"), + name, age, (long)info->dirent->filesize, + apr_hash_count(paths))); + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" %.50s\n"), + info->message)); + + if (diffstat) + { +#ifndef WIN32 + const char *args[4]; + svn_error_t *err; + + args[0] = "diffstat"; + args[1] = "-p0"; + args[2] = info->patch_path; + args[3] = NULL; + err = run_cmd("diffstat", args, scratch_pool); + if (err) + svn_error_clear(err); + else + SVN_ERR(svn_cmdline_printf(scratch_pool, + "\n")); +#endif + } + } + + return SVN_NO_ERROR; +} + +/* Find the name of the youngest shelved change. + */ +static svn_error_t * +name_of_youngest(const char **name_p, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *list; + const svn_sort__item_t *youngest_item; + + SVN_ERR(list_sorted_by_date(&list, + local_abspath, ctx, scratch_pool)); + if (list->nelts == 0) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, + _("No shelved changes found")); + + youngest_item = &APR_ARRAY_IDX(list, list->nelts - 1, svn_sort__item_t); + *name_p = youngest_item->key; + return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__shelve(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *local_abspath; + const char *name; + apr_array_header_t *targets; + svn_boolean_t has_changes; + + if (opt_state->quiet) + ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */ + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool)); + + if (opt_state->list) + { + if (os->ind < os->argc) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + SVN_ERR(shelves_list(local_abspath, + ! opt_state->quiet /*diffstat*/, + ctx, pool)); + return SVN_NO_ERROR; + } + + SVN_ERR(get_name(&name, os, pool, pool)); + + if (opt_state->remove) + { + if (os->ind < os->argc) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + SVN_ERR(svn_client_shelves_delete(name, local_abspath, + opt_state->dry_run, + ctx, pool)); + if (! opt_state->quiet) + SVN_ERR(svn_cmdline_printf(pool, "deleted '%s'\n", name)); + return SVN_NO_ERROR; + } + + /* Parse the remaining arguments as paths. */ + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + svn_opt_push_implicit_dot_target(targets, pool); + + { + svn_depth_t depth = opt_state->depth; + svn_error_t *err; + + /* shelve has no implicit dot-target `.', so don't you put that + code here! */ + if (!targets->nelts) + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + + SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + + if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, pool)); + + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__make_log_msg_baton(&ctx->log_msg_baton3, + opt_state, NULL, ctx->config, + pool)); + err = svn_client_shelve(name, + targets, depth, opt_state->changelists, + opt_state->keep_local, opt_state->dry_run, + ctx, pool); + if (ctx->log_msg_func3) + SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, + err, pool)); + else + SVN_ERR(err); + } + + /* If no modifications were shelved, throw an error. */ + SVN_ERR(svn_client_shelf_has_changes(&has_changes, + name, local_abspath, ctx, pool)); + if (! has_changes) + { + SVN_ERR(svn_client_shelves_delete(name, local_abspath, + opt_state->dry_run, ctx, pool)); + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("No changes were shelved")); + } + + if (! opt_state->quiet) + SVN_ERR(svn_cmdline_printf(pool, "shelved '%s'\n", name)); + + return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__unshelve(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *local_abspath; + const char *name; + apr_array_header_t *targets; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool)); + + if (opt_state->list) + { + if (os->ind < os->argc) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + SVN_ERR(shelves_list(local_abspath, + ! opt_state->quiet /*diffstat*/, + ctx, pool)); + return SVN_NO_ERROR; + } + + if (os->ind < os->argc) + { + SVN_ERR(get_name(&name, os, pool, pool)); + } + else + { + SVN_ERR(name_of_youngest(&name, local_abspath, ctx, pool, pool)); + SVN_ERR(svn_cmdline_printf(pool, + _("unshelving the youngest change, '%s'\n"), + name)); + } + + /* There should be no remaining arguments. */ + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, FALSE, pool)); + if (targets->nelts) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + if (opt_state->quiet) + ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */ + + SVN_ERR(svn_client_unshelve(name, local_abspath, + opt_state->keep_local, opt_state->dry_run, + ctx, pool)); + if (! opt_state->quiet) + SVN_ERR(svn_cmdline_printf(pool, "unshelved '%s'\n", name)); + + return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +svn_error_t * +svn_cl__shelves(apr_getopt_t *os, + void *baton, + apr_pool_t *pool) +{ + svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; + const char *local_abspath; + + /* There should be no remaining arguments. */ + if (os->ind < os->argc) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool)); + SVN_ERR(shelves_list(local_abspath, ! opt_state->quiet /*diffstat*/, + ctx, pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/svn/status.c b/subversion/svn/status.c index 9ab9c5926c55f..409540267b4f2 100644 --- a/subversion/svn/status.c +++ b/subversion/svn/status.c @@ -282,11 +282,10 @@ print_status(const char *target_abspath, if (tree_conflicted) { - const svn_wc_conflict_description2_t *tree_conflict; - SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx, - local_abspath, pool, pool)); - SVN_ERR_ASSERT(tree_conflict != NULL); + svn_client_conflict_t *tree_conflict; + SVN_ERR(svn_client_conflict_get(&tree_conflict, local_abspath, + ctx, pool, pool)); tree_status_code = 'C'; SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( &desc, tree_conflict, pool)); diff --git a/subversion/svn/svn.c b/subversion/svn/svn.c index 058be707d074a..48b894f499831 100644 --- a/subversion/svn/svn.c +++ b/subversion/svn/svn.c @@ -33,7 +33,6 @@ #include <apr_strings.h> #include <apr_tables.h> #include <apr_general.h> -#include <apr_signal.h> #include "svn_cmdline.h" #include "svn_pools.h" @@ -57,6 +56,7 @@ #include "private/svn_opt_private.h" #include "private/svn_cmdline_private.h" #include "private/svn_subr_private.h" +#include "private/svn_utf_private.h" #include "svn_private_config.h" @@ -68,6 +68,7 @@ use the short option letter as identifier. */ typedef enum svn_cl__longopt_t { opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID, + opt_auth_password_from_stdin, opt_auth_username, opt_autoprops, opt_changelist, @@ -143,6 +144,11 @@ typedef enum svn_cl__longopt_t { opt_show_passwords, opt_pin_externals, opt_show_item, + opt_adds_as_modification, + opt_vacuum_pristines, + opt_delete, + opt_keep_shelved, + opt_list } svn_cl__longopt_t; @@ -198,6 +204,9 @@ const apr_getopt_option_t svn_cl__options[] = N_("specify a password ARG (caution: on many operating\n" " " "systems, other users will be able to see this)")}, + {"password-from-stdin", + opt_auth_password_from_stdin, 0, + N_("read password from stdin")}, {"extensions", 'x', 1, N_("Specify differencing options for external diff or\n" " " @@ -327,9 +336,9 @@ const apr_getopt_option_t svn_cl__options[] = " " "'theirs-conflict', 'mine-full', 'theirs-full',\n" " " - "'edit', 'launch')\n" + "'edit', 'launch', 'recommended') (shorthand:\n" " " - "(shorthand: 'p', 'mc', 'tc', 'mf', 'tf', 'e', 'l')" + "'p', 'mc', 'tc', 'mf', 'tf', 'e', 'l', 'r')" )}, {"show-revs", opt_show_revs, 1, N_("specify which collection of revisions to display\n" @@ -400,7 +409,11 @@ const apr_getopt_option_t svn_cl__options[] = {"show-inherited-props", opt_show_inherited_props, 0, N_("retrieve properties set on parents of the target")}, {"search", opt_search, 1, - N_("use ARG as search pattern (glob syntax)")}, + N_("use ARG as search pattern (glob syntax, case-\n" + " " + "and accent-insensitive, may require quotation marks\n" + " " + "to prevent shell expansion)")}, {"search-and", opt_search_and, 1, N_("combine ARG with the previous search pattern")}, {"log", opt_mergeinfo_log, 0, @@ -415,15 +428,55 @@ const apr_getopt_option_t svn_cl__options[] = " " "current revision (recommended when tagging)")}, {"show-item", opt_show_item, 1, - N_("print only the item identified by ARG ('kind',\n" - " " - "'url', 'relative-url', 'repos-root-url',\n" + N_("print only the item identified by ARG:\n" + " " + " 'kind' node kind of TARGET\n" + " " + " 'url' URL of TARGET in the repository\n" + " " + " 'relative-url'\n" + " " + " repository-relative URL of TARGET\n" + " " + " 'repos-root-url'\n" + " " + " root URL of repository\n" + " " + " 'repos-uuid' UUID of repository\n" + " " + " 'revision' specified or implied revision\n" + " " + " 'last-changed-revision'\n" + " " + " last change of TARGET at or before\n" + " " + " 'revision'\n" + " " + " 'last-changed-date'\n" + " " + " date of 'last-changed-revision'\n" + " " + " 'last-changed-author'\n" + " " + " author of 'last-changed-revision'\n" + " " + " 'wc-root' root of TARGET's working copy")}, + + {"adds-as-modification", opt_adds_as_modification, 0, + N_("Local additions are merged with incoming additions\n" " " - "'repos-uuid', 'revision', 'last-changed-revision',\n" + "instead of causing a tree conflict. Use of this\n" " " - "'last-changed-date', 'last-changed-author',\n" + "option is not recommended! Use 'svn resolve' to\n" " " - "'wc-root')")}, + "resolve tree conflicts instead.")}, + + {"vacuum-pristines", opt_vacuum_pristines, 0, + N_("remove unreferenced pristines from .svn directory")}, + + {"list", opt_list, 0, N_("list shelved patches")}, + {"keep-shelved", opt_keep_shelved, 0, N_("do not delete the shelved patch")}, + {"delete", opt_delete, 0, N_("delete the shelved patch")}, /* Long-opt Aliases * @@ -455,7 +508,8 @@ const apr_getopt_option_t svn_cl__options[] = command to take these arguments allows scripts to just pass them willy-nilly to every invocation of 'svn') . */ const int svn_cl__global_options[] = -{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive, +{ opt_auth_username, opt_auth_password, opt_auth_password_from_stdin, + opt_no_auth_cache, opt_non_interactive, opt_force_interactive, opt_trust_server_cert, opt_trust_server_cert_failures, opt_config_dir, opt_config_options, 0 @@ -491,7 +545,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " or more patterns. With the --remove option, remove cached authentication\n" " credentials matching one or more patterns.\n" "\n" - " If more than one pattern is specified credentials are considered only they\n" + " If more than one pattern is specified credentials are considered only if they\n" " match all specified patterns. Patterns are matched case-sensitively and may\n" " contain glob wildcards:\n" " ? matches any single character\n" @@ -571,38 +625,45 @@ 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 write locks, resuming\n" - "unfinished operations, etc.\n" - "usage: cleanup [WCPATH...]\n" - "\n" - " By default, finish any unfinished business in the working copy at WCPATH,\n" - " and remove write locks (shown as 'L' by the 'svn status' command) from\n" - " the working copy. Usually, this is only necessary if a Subversion client\n" - " has crashed 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" - "\n" - " If the --remove-unversioned option or the --remove-ignored option\n" - " is given, remove any unversioned or ignored items within WCPATH.\n" - " To prevent accidental working copy corruption, unversioned or ignored\n" - " items can only be removed if the working copy is not already locked\n" - " for writing by another Subversion client.\n" - " Note that the 'svn status' command shows unversioned items as '?',\n" - " and ignored items as 'I' if the --no-ignore option is given to it.\n"), - {opt_merge_cmd, opt_remove_unversioned, opt_remove_ignored, - opt_include_externals, 'q'} }, - + ("Either recover from an interrupted operation that left the working copy locked,\n" + "or remove unwanted files.\n" + "usage: 1. cleanup [WCPATH...]\n" + " 2. cleanup --remove-unversioned [WCPATH...]\n" + " cleanup --remove-ignored [WCPATH...]\n" + " 3. cleanup --vacuum-pristines [WCPATH...]\n" + "\n" + " 1. When none of the options --remove-unversioned, --remove-ignored, and\n" + " --vacuum-pristines is specified, remove all write locks (shown as 'L' by\n" + " the 'svn status' command) from the working copy. Usually, this is only\n" + " necessary if a Subversion client has crashed while using the working copy,\n" + " 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" + " without any options while another client is using the working\n" + " copy can corrupt the working copy beyond repair!\n" + "\n" + " 2. If the --remove-unversioned option or the --remove-ignored option\n" + " is given, remove any unversioned or ignored items within WCPATH.\n" + " Note that the 'svn status' command shows unversioned items as '?',\n" + " and ignored items as 'I' if the --no-ignore option is given to it.\n" + "\n" + " 3. If the --vacuum-pristines option is given, remove pristine copies of\n" + " files which are stored inside the .svn directory and which are no longer\n" + " referenced by any file in the working copy.\n"), + { opt_remove_unversioned, opt_remove_ignored, opt_vacuum_pristines, + opt_include_externals, 'q', opt_merge_cmd }, + { { opt_merge_cmd, N_("deprecated and ignored") } } }, + { "commit", svn_cl__commit, {"ci"}, N_("Send changes from your working copy to the repository.\n" "usage: commit [PATH...]\n" "\n" " A log message must be provided, but it can be empty. If it is not\n" " given by a --message or --file option, an editor will be started.\n" + "\n" " If any targets are (or contain) locked items, those will be\n" - " unlocked after a successful commit.\n" + " unlocked after a successful commit, unless --no-unlock is given.\n" "\n" " If --include-externals is given, also commit file and directory\n" " externals reached by recursion. Do not commit externals with a\n" @@ -619,8 +680,9 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " WC -> URL: immediately commit a copy of WC to URL\n" " URL -> WC: check out URL into WC, schedule for addition\n" " URL -> URL: complete server-side copy; used to branch and tag\n" - " All the SRCs must be of the same type. When copying multiple sources,\n" - " they will be added as children of DST, which must be a directory.\n" + " All the SRCs must be of the same type. If DST is an existing directory,\n" + " the sources will be added as children of DST. When copying multiple\n" + " sources, DST must be an existing directory.\n" "\n" " WARNING: For compatibility with previous versions of Subversion,\n" " copies performed using two working copy paths (WC -> WC) will not\n" @@ -734,28 +796,44 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = "usage: info [TARGET[@REV]...]\n" "\n" " Print information about each TARGET (default: '.').\n" - " TARGET may be either a working-copy path or URL. If specified, REV\n" - " determines in which revision the target is first looked up.\n" + " TARGET may be either a working-copy path or a URL. If specified, REV\n" + " determines in which revision the target is first looked up; the default\n" + " is HEAD for a URL or BASE for a WC path.\n" "\n" " With --show-item, print only the value of one item of information\n" - " about TARGET. One of the following items can be selected:\n" - " kind the kind of TARGET\n" - " url the URL of TARGET in the repository\n" - " relative-url the repository-relative URL\n" - " repos-root-url the repository root URL\n" - " repos-uuid the repository UUID\n" - " revision the revision of TARGET (defaults to BASE\n" - " for working copy paths and HEAD for URLs)\n" - " last-changed-revision the most recent revision in which TARGET\n" - " was changed\n" - " last-changed-date the date of the last-changed revision\n" - " last-changed-author the author of the last-changed revision\n" - " wc-root the root of TARGET's working copy\n"), + " about TARGET.\n"), {'r', 'R', opt_depth, opt_targets, opt_incremental, opt_xml, opt_changelist, opt_include_externals, opt_show_item, opt_no_newline} }, - { "list", svn_cl__list, {"ls"}, N_ + { "list", svn_cl__list, {"ls"}, +#if defined(WIN32) + N_ + ("List directory entries in the repository.\n" + "usage: list [TARGET[@REV]...]\n" + "\n" + " List each TARGET file and the contents of each TARGET directory as\n" + " they exist in the repository. If TARGET is a working copy path, the\n" + " corresponding repository URL will be used. If specified, REV determines\n" + " in which revision the target is first looked up.\n" + "\n" + " The default TARGET is '.', meaning the repository URL of the current\n" + " working directory.\n" + "\n" + " Multiple --search patterns may be specified and the output will be\n" + " reduced to those paths whose last segment - i.e. the file or directory\n" + " name - contains a sub-string matching at least one of these patterns\n" + " (Windows only).\n" + "\n" + " With --verbose, the following fields will be shown for each item:\n" + "\n" + " Revision number of the last commit\n" + " Author of the last commit\n" + " If locked, the letter 'O'. (Use 'svn info URL' to see details)\n" + " Size (in bytes)\n" + " Date and time of the last commit\n"), +#else + N_ ("List directory entries in the repository.\n" "usage: list [TARGET[@REV]...]\n" "\n" @@ -767,6 +845,10 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " The default TARGET is '.', meaning the repository URL of the current\n" " working directory.\n" "\n" + " Multiple --search patterns may be specified and the output will be\n" + " reduced to those paths whose last segment - i.e. the file or directory\n" + " name - matches at least one of these patterns.\n" + "\n" " With --verbose, the following fields will be shown for each item:\n" "\n" " Revision number of the last commit\n" @@ -774,19 +856,21 @@ 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"), +#endif {'r', 'v', 'R', opt_depth, opt_incremental, opt_xml, - opt_include_externals}, }, + opt_include_externals, opt_search}, }, { "lock", svn_cl__lock, {0}, N_ ("Lock working copy paths or URLs in the repository, so that\n" "no other user can commit changes to them.\n" "usage: lock TARGET...\n" "\n" - " Use --force to steal the lock from another user or working copy.\n"), - { opt_targets, 'm', 'F', opt_force_log, opt_encoding, opt_force }, + " Use --force to steal a lock from another user or working copy.\n"), + { opt_targets, 'm', 'F', opt_force_log, opt_encoding, opt_force, 'q' }, {{'F', N_("read lock comment from file ARG")}, {'m', N_("specify lock comment ARG")}, - {opt_force_log, N_("force validity of lock comment source")}} }, + {opt_force_log, N_("force validity of lock comment source")}, + {opt_force, N_("steal locks")}} }, { "log", svn_cl__log, {0}, N_ ("Show the log messages for a set of revision(s) and/or path(s).\n" @@ -810,6 +894,17 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " reverse ranges is allowed.\n" "\n" " With -v, also print all affected paths with each log message.\n" + " Each changed path is preceded with a symbol describing the change:\n" + " A: The path was added or copied.\n" + " D: The path was deleted.\n" + " R: The path was replaced (deleted and re-added in the same revision).\n" + " M: The path's file and/or property content was modified.\n" + " If an added or replaced path was copied from somewhere else, the copy\n" + " source path and revision are shown in parentheses.\n" + " If a file or directory was moved from one path to another with 'svn move'\n" + " the old path will be listed as deleted and the new path will be listed\n" + " as copied from the old path at a prior revision.\n" + "\n" " With -q, don't print the log message body itself (note that this is\n" " compatible with -v).\n" "\n" @@ -862,7 +957,12 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = "\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"), + " svn log --stop-on-copy --limit 1 -r0:HEAD ^/branches/foo\n" + "\n" + " If ^/trunk/foo.c was moved to ^/trunk/bar.c' in revision 22, 'svn log -v'\n" + " shows a deletion and a copy in its changed paths list, such as:\n" + " D /trunk/foo.c\n" + " A /trunk/bar.c (from /trunk/foo.c:21)\n"), {'r', 'c', 'q', 'v', 'g', 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, @@ -1267,18 +1367,18 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " 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" + " All the SRCs must be of the same type. If DST is an existing directory,\n" + " the sources will be added as children of DST. When moving multiple\n" + " sources, DST must be an existing 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} }, + " The --allow-mixed-revisions option is provided for backward compatibility.\n"), + {'q', opt_force, opt_parents, opt_allow_mixed_revisions, + SVN_CL__LOG_MSG_OPTIONS, 'r'}, + {{'r', "deprecated and ignored"}} }, { "patch", svn_cl__patch, {0}, N_ ("Apply a patch to a working copy.\n" @@ -1519,7 +1619,48 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = "\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"), + " In this mode, the command is not recursive by default (depth 'empty').\n" + "\n" + " A conflicted path cannot be committed with 'svn commit' until it\n" + " has been marked as resolved with 'svn resolve'.\n" + "\n" + " Subversion knows three types of conflicts:\n" + " Text conflicts, Property conflicts, and Tree conflicts.\n" + "\n" + " Text conflicts occur when overlapping changes to file contents were\n" + " made. Text conflicts are usually resolved by editing the conflicted\n" + " file or by using a merge tool (which may be an external program).\n" + " 'svn resolve' provides options which can be used to automatically\n" + " edit files (such as 'mine-full' or 'theirs-conflict'), but these are\n" + " only useful in situations where it is acceptable to discard local or\n" + " incoming changes altogether.\n" + "\n" + " Property conflicts are usually resolved by editing the value of the\n" + " conflicted property (either from the interactive prompt, or with\n" + " 'svn propedit'). As with text conflicts, options exist to edit a\n" + " property automatically, discarding some changes in favour of others.\n" + "\n" + " Tree conflicts occur when a change to the directory structure was\n" + " made, and when this change cannot be applied to the working copy\n" + " without affecting other changes (text changes, property changes,\n" + " or other changes to the directory structure). Brief information about\n" + " tree conflicts is shown by the 'svn status' and 'svn info' commands.\n" + " In interactive mode, 'svn resolve' will attempt to describe tree conflicts\n" + " in detail, and may offer options to resolve the conflict automatically.\n" + " It is recommended to use these automatic options whenever possible,\n" + " rather than attempting manual tree conflict resolution.\n" + "\n" + " If a tree conflict cannot be resolved automatically, it is recommended\n" + " to figure out why the conflict occurred before attempting to resolve it.\n" + " The 'svn log -v' command can be used to inspect structural changes\n" + " made in past revisions, and perhaps even on other branches.\n" + " 'svn help log' describes how these structural changes are presented.\n" + " Once the conflicting \"incoming\" change has been identified with 'svn log'\n" + " the current \"local\" working copy state should be examined and adjusted\n" + " in a way such that the conflict is resolved. This may involve editing\n" + " files manually or with 'svn merge'. It may be necessary to discard some\n" + " local changes with 'svn revert'. Files or directories might have to be\n" + " copied, deleted, or moved.\n"), {opt_targets, 'R', opt_depth, 'q', opt_accept}, {{opt_accept, N_("specify automatic conflict resolution source\n" " " @@ -1685,20 +1826,23 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " svn switch --relocate http:// svn://\n" " 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}, + { 'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, + opt_ignore_externals, opt_ignore_ancestry, opt_force, opt_accept, + opt_relocate }, {{opt_ignore_ancestry, N_("allow switching to a node with no common ancestor")}, {opt_force, - N_("handle unversioned obstructions as changes")}} + N_("handle unversioned obstructions as changes")}, + {opt_relocate,N_("deprecated; use 'svn relocate'")}} }, { "unlock", svn_cl__unlock, {0}, N_ ("Unlock working copy paths or URLs.\n" "usage: unlock TARGET...\n" "\n" - " Use --force to break the lock.\n"), - { opt_targets, opt_force } }, + " Use --force to break a lock held by another user or working copy.\n"), + { opt_targets, opt_force, 'q' }, + {{opt_force, N_("break locks")}} }, { "update", svn_cl__update, {"up"}, N_ ("Bring changes from the repository into the working copy.\n" @@ -1748,7 +1892,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " targets of this operation.\n"), {'r', 'N', opt_depth, opt_set_depth, 'q', opt_merge_cmd, opt_force, opt_ignore_externals, opt_changelist, opt_editor_cmd, opt_accept, - opt_parents}, + opt_parents, opt_adds_as_modification}, { {opt_force, N_("handle unversioned obstructions as changes")} } }, @@ -1759,6 +1903,74 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = " Local modifications are preserved.\n"), { 'q' } }, + { "x-shelve", svn_cl__shelve, {"shelve"}, N_ + ("Put a local change aside, as if putting it on a shelf.\n" + "usage: 1. x-shelve [--keep-local] NAME [PATH...]\n" + " 2. x-shelve --delete NAME\n" + " 3. x-shelve --list\n" + "\n" + " 1. Save the local change in the given PATHs to a patch file, and\n" + " revert that change from the WC unless '--keep-local' is given.\n" + " If a log message is given with '-m' or '-F', include it at the\n" + " beginning of the patch file.\n" + "\n" + " 2. Delete the shelved change NAME.\n" + " (A backup is kept, named with a '.bak' extension.)\n" + "\n" + " 3. List shelved changes. Include the first line of any log message\n" + " and some details about the contents of the change, unless '-q' is\n" + " given.\n" + "\n" + " The kinds of change you can shelve are those supported by 'svn diff'\n" + " and 'svn patch'. The following are currently NOT supported:\n" + " mergeinfo changes, copies, moves, mkdir, rmdir,\n" + " 'binary' content, uncommittable states\n" + "\n" + " To bring back a shelved change, use 'svn x-unshelve NAME'.\n" + "\n" + " Shelved changes are stored in <WC>/.svn/shelves/\n" + "\n" + " The shelving feature is EXPERIMENTAL. This command is likely to change\n" + " in the next release, and there is no promise of backward compatibility.\n" + ), + {opt_delete, opt_list, 'q', opt_dry_run, opt_keep_local, + opt_depth, opt_targets, opt_changelist, + /* almost SVN_CL__LOG_MSG_OPTIONS but not currently opt_with_revprop: */ + 'm', 'F', opt_force_log, opt_editor_cmd, opt_encoding, + } }, + + { "x-unshelve", svn_cl__unshelve, {"unshelve"}, N_ + ("Bring a shelved change back to a local change in the WC.\n" + "usage: 1. x-unshelve [--keep-shelved] [NAME]\n" + " 2. x-unshelve --list\n" + "\n" + " 1. Apply the shelved change NAME to the working copy.\n" + " Delete the patch unless the '--keep-shelved' option is given.\n" + " (A backup is kept, named with a '.bak' extension.)\n" + " NAME defaults to the most recent shelved change.\n" + "\n" + " 2. List shelved changes. Include the first line of any log message\n" + " and some details about the contents of the change, unless '-q' is\n" + " given.\n" + "\n" + " Any conflict between the change being unshelved and a change\n" + " already in the WC is handled the same way as by 'svn patch',\n" + " creating a 'reject' file.\n" + "\n" + " The shelving feature is EXPERIMENTAL. This command is likely to change\n" + " in the next release, and there is no promise of backward compatibility.\n" + ), + {opt_keep_shelved, opt_list, 'q', opt_dry_run} }, + + { "x-shelves", svn_cl__shelves, {"shelves"}, N_ + ("List shelved changes.\n" + "usage: x-shelves\n" + "\n" + " The shelving feature is EXPERIMENTAL. This command is likely to change\n" + " in the next release, and there is no promise of backward compatibility.\n" + ), + {'q'} }, + { NULL, NULL, {0}, NULL, {0} } }; @@ -1782,29 +1994,8 @@ check_lib_versions(void) return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); } - -/* A flag to see if we've been cancelled by the client or not. */ -static volatile sig_atomic_t cancelled = FALSE; - -/* A signal handler to support cancellation. */ -static void -signal_handler(int signum) -{ - apr_signal(signum, SIG_IGN); - cancelled = TRUE; -} - -/* Our cancellation callback. */ -svn_error_t * -svn_cl__check_cancel(void *baton) -{ - /* Cancel baton should be always NULL in command line client. */ - SVN_ERR_ASSERT(baton == NULL); - if (cancelled) - return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); - else - return SVN_NO_ERROR; -} +/* The cancelation handler setup by the cmdline library. */ +svn_cancel_func_t svn_cl__check_cancel = NULL; /* Add a --search argument to OPT_STATE. * These options start a new search pattern group. */ @@ -1877,6 +2068,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) svn_boolean_t reading_file_from_stdin = FALSE; apr_hash_t *changelists; apr_hash_t *cfg_hash; + svn_membuf_t buf; + svn_boolean_t read_pass_from_stdin = FALSE; received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); @@ -1897,6 +2090,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) /* Init our changelists hash. */ changelists = apr_hash_make(pool); + /* Init the temporary buffer. */ + svn_membuf__create(&buf, 0, pool); + /* Begin processing arguments. */ opt_state.start_revision.kind = svn_opt_revision_unspecified; opt_state.end_revision.kind = svn_opt_revision_unspecified; @@ -2112,6 +2308,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) case opt_dry_run: opt_state.dry_run = TRUE; break; + case opt_list: + opt_state.list = TRUE; + break; case opt_revprop: opt_state.revprop = TRUE; break; @@ -2166,6 +2365,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password, opt_arg, pool)); break; + case opt_auth_password_from_stdin: + read_pass_from_stdin = TRUE; + break; case opt_encoding: opt_state.encoding = apr_pstrdup(pool, opt_arg); break; @@ -2292,6 +2494,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) opt_state.diff.summarize = TRUE; break; case opt_remove: + case opt_delete: opt_state.remove = TRUE; break; case opt_changelist: @@ -2307,6 +2510,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) opt_state.keep_changelists = TRUE; break; case opt_keep_local: + case opt_keep_shelved: opt_state.keep_local = TRUE; break; case opt_with_all_revprops: @@ -2401,11 +2605,20 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) break; case opt_search: SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); - add_search_pattern_group(&opt_state, utf8_opt_arg, pool); + SVN_ERR(svn_utf__xfrm(&utf8_opt_arg, utf8_opt_arg, + strlen(utf8_opt_arg), TRUE, TRUE, &buf)); + add_search_pattern_group(&opt_state, + apr_pstrdup(pool, utf8_opt_arg), + pool); break; case opt_search_and: SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); - add_search_pattern_to_latest_group(&opt_state, utf8_opt_arg, pool); + SVN_ERR(svn_utf__xfrm(&utf8_opt_arg, utf8_opt_arg, + strlen(utf8_opt_arg), TRUE, TRUE, &buf)); + add_search_pattern_to_latest_group(&opt_state, + apr_pstrdup(pool, utf8_opt_arg), + pool); + break; case opt_remove_unversioned: opt_state.remove_unversioned = TRUE; break; @@ -2426,6 +2639,12 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); opt_state.show_item = utf8_opt_arg; break; + case opt_adds_as_modification: + opt_state.adds_as_modification = TRUE; + break; + case opt_vacuum_pristines: + opt_state.vacuum_pristines = TRUE; + break; default: /* Hmmm. Perhaps this would be a good place to squirrel away opts that commands like svn diff might need. Hmmm indeed. */ @@ -2499,22 +2718,22 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) } else { - const char *first_arg = os->argv[os->ind++]; + const char *first_arg; + + SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++], + pool)); subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, first_arg); if (subcommand == NULL) { - const char *first_arg_utf8; - SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, - first_arg, pool)); svn_error_clear (svn_cmdline_fprintf(stderr, pool, _("Unknown subcommand: '%s'\n"), - first_arg_utf8)); + first_arg)); svn_error_clear(svn_cl__help(NULL, NULL, pool)); /* Be kind to people who try 'svn undo'. */ - if (strcmp(first_arg_utf8, "undo") == 0) + if (strcmp(first_arg, "undo") == 0) { svn_error_clear (svn_cmdline_fprintf(stderr, pool, @@ -2645,6 +2864,14 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) "--non-interactive")); } + /* --password-from-stdin can only be used with --non-interactive */ + if (read_pass_from_stdin && !opt_state.non_interactive) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--password-from-stdin requires " + "--non-interactive")); + } + /* Disallow simultaneous use of both --diff-cmd and --internal-diff. */ if (opt_state.diff.diff_cmd && opt_state.diff.internal_diff) @@ -2780,6 +3007,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) /* Create a client context object. */ command_baton.opt_state = &opt_state; + command_baton.conflict_stats = conflict_stats; SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool)); command_baton.ctx = ctx; @@ -2795,7 +3023,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) || subcommand->cmd_func == svn_cl__mkdir || subcommand->cmd_func == svn_cl__move || subcommand->cmd_func == svn_cl__lock - || subcommand->cmd_func == svn_cl__propedit)) + || subcommand->cmd_func == svn_cl__propedit + || subcommand->cmd_func == svn_cl__shelve)) { /* If the -F argument is a file that's under revision control, that's probably not what the user intended. */ @@ -2803,9 +3032,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) { svn_node_kind_t kind; const char *local_abspath; - const char *fname_utf8 = svn_dirent_internal_style(dash_F_arg, pool); + const char *fname = svn_dirent_internal_style(dash_F_arg, pool); - err = svn_dirent_get_absolute(&local_abspath, fname_utf8, pool); + err = svn_dirent_get_absolute(&local_abspath, fname, pool); if (!err) { @@ -2933,31 +3162,15 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) conflict_stats, pool)); } + /* Get password from stdin if necessary */ + if (read_pass_from_stdin) + { + SVN_ERR(svn_cmdline__stdin_readline(&opt_state.auth_password, pool, pool)); + } + /* Set up our cancellation support. */ + svn_cl__check_cancel = svn_cmdline__setup_cancellation_handler(); ctx->cancel_func = svn_cl__check_cancel; - apr_signal(SIGINT, signal_handler); -#ifdef SIGBREAK - /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ - apr_signal(SIGBREAK, signal_handler); -#endif -#ifdef SIGHUP - apr_signal(SIGHUP, signal_handler); -#endif -#ifdef SIGTERM - apr_signal(SIGTERM, signal_handler); -#endif - -#ifdef SIGPIPE - /* Disable SIGPIPE generation for the platforms that have it. */ - apr_signal(SIGPIPE, SIG_IGN); -#endif - -#ifdef SIGXFSZ - /* Disable SIGXFSZ generation for the platforms that have it, otherwise - * working with large files when compiled against an APR that doesn't have - * large file support will crash the program, which is uncool. */ - apr_signal(SIGXFSZ, SIG_IGN); -#endif /* Set up Authentication stuff. */ SVN_ERR(svn_cmdline_create_auth_baton2( @@ -2996,10 +3209,11 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) SVN_CL__ACCEPT_LAUNCH); } - /* The default action when we're non-interactive is to postpone - * conflict resolution. */ + /* The default action when we're non-interactive is to use the + * recommended conflict resolution (this will postpone conflicts + * for which no recommended resolution is available). */ if (opt_state.accept_which == svn_cl__accept_unspecified) - opt_state.accept_which = svn_cl__accept_postpone; + opt_state.accept_which = svn_cl__accept_recommended; } /* Check whether interactive conflict resolution is disabled by @@ -3021,20 +3235,12 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) opt_state.accept_which = svn_cl__accept_postpone; } - /* Install the default conflict handler. */ + /* We don't use legacy libsvn_wc conflict handlers by default. */ { - svn_cl__interactive_conflict_baton_t *b; - ctx->conflict_func = NULL; ctx->conflict_baton = NULL; - - ctx->conflict_func2 = svn_cl__conflict_func_interactive; - SVN_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; + ctx->conflict_func2 = NULL; + ctx->conflict_baton2 = NULL; } /* And now we finally run the subcommand. */ @@ -3136,5 +3342,8 @@ main(int argc, const char *argv[]) } svn_pool_destroy(pool); + + svn_cmdline__cancellation_exit(); + return exit_code; } diff --git a/subversion/svn/switch-cmd.c b/subversion/svn/switch-cmd.c index aaef2b56ae8b1..7cab8d980a593 100644 --- a/subversion/svn/switch-cmd.c +++ b/subversion/svn/switch-cmd.c @@ -96,8 +96,11 @@ svn_cl__switch(apr_getopt_t *os, 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_cl__conflict_stats_t *conflict_stats = + ((svn_cl__cmd_baton_t *) baton)->conflict_stats; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; apr_array_header_t *targets; + apr_array_header_t *conflicted_paths; const char *target, *switch_url; svn_opt_revision_t peg_revision; svn_depth_t depth; @@ -188,6 +191,13 @@ svn_cl__switch(apr_getopt_t *os, _("Failure occurred processing one or " "more externals definitions")); + /* Run the interactive resolver if conflicts were raised. */ + SVN_ERR(svn_cl__conflict_stats_get_paths(&conflicted_paths, conflict_stats, + scratch_pool, scratch_pool)); + if (conflicted_paths) + SVN_ERR(svn_cl__walk_conflicts(conflicted_paths, conflict_stats, + opt_state, ctx, scratch_pool)); + if (! opt_state->quiet) { err = svn_cl__notifier_print_conflict_stats(nwb.wrapped_baton, scratch_pool); diff --git a/subversion/svn/update-cmd.c b/subversion/svn/update-cmd.c index 77c28f97ca408..2820615ea8c27 100644 --- a/subversion/svn/update-cmd.c +++ b/subversion/svn/update-cmd.c @@ -111,12 +111,15 @@ svn_cl__update(apr_getopt_t *os, apr_pool_t *scratch_pool) { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_cl__conflict_stats_t *conflict_stats = + ((svn_cl__cmd_baton_t *) baton)->conflict_stats; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; apr_array_header_t *targets; svn_depth_t depth; svn_boolean_t depth_is_sticky; struct svn_cl__check_externals_failed_notify_baton nwb; apr_array_header_t *result_revs; + apr_array_header_t *conflicted_paths; svn_error_t *err = SVN_NO_ERROR; svn_error_t *externals_err = SVN_NO_ERROR; @@ -167,7 +170,8 @@ svn_cl__update(apr_getopt_t *os, &(opt_state->start_revision), depth, depth_is_sticky, opt_state->ignore_externals, - opt_state->force, TRUE /* adds_as_modification */, + opt_state->force, + opt_state->adds_as_modification, opt_state->parents, ctx, scratch_pool)); @@ -177,6 +181,13 @@ svn_cl__update(apr_getopt_t *os, _("Failure occurred processing one or " "more externals definitions")); + /* Run the interactive resolver if conflicts were raised. */ + SVN_ERR(svn_cl__conflict_stats_get_paths(&conflicted_paths, conflict_stats, + scratch_pool, scratch_pool)); + if (conflicted_paths) + SVN_ERR(svn_cl__walk_conflicts(conflicted_paths, conflict_stats, + opt_state, ctx, scratch_pool)); + if (! opt_state->quiet) { err = print_update_summary(targets, result_revs, scratch_pool); diff --git a/subversion/svn/util.c b/subversion/svn/util.c index 88ae27b148242..a18e5f37c9f7a 100644 --- a/subversion/svn/util.c +++ b/subversion/svn/util.c @@ -614,6 +614,7 @@ svn_cl__try(svn_error_t *err, if (! quiet) svn_handle_warning2(stderr, err, "svn: "); svn_error_clear(err); + va_end(ap); return SVN_NO_ERROR; } } @@ -907,14 +908,17 @@ svn_cl__time_cstring_to_human_cstring(const char **human_cstring, } const char * -svn_cl__node_description(const svn_wc_conflict_version_t *node, +svn_cl__node_description(const char *repos_root_url, + const char *repos_relpath, + svn_revnum_t peg_rev, + svn_node_kind_t node_kind, const char *wc_repos_root_URL, apr_pool_t *pool) { const char *root_str = "^"; const char *path_str = "..."; - if (!node) + if (!repos_root_url || !repos_relpath || !SVN_IS_VALID_REVNUM(peg_rev)) /* Printing "(none)" the harder way to ensure conformity (mostly with * translations). */ return apr_psprintf(pool, "(%s)", @@ -923,18 +927,18 @@ svn_cl__node_description(const svn_wc_conflict_version_t *node, /* Construct a "caret notation" ^/URL if NODE matches WC_REPOS_ROOT_URL. * Otherwise show the complete URL, and if we can't, show dots. */ - if (node->repos_url && + if (repos_root_url && (wc_repos_root_URL == NULL || - strcmp(node->repos_url, wc_repos_root_URL) != 0)) - root_str = node->repos_url; + strcmp(repos_root_url, wc_repos_root_URL) != 0)) + root_str = repos_root_url; - if (node->path_in_repos) - path_str = node->path_in_repos; + if (repos_relpath) + path_str = repos_relpath; return apr_psprintf(pool, "(%s) %s@%ld", - svn_cl__node_kind_str_human_readable(node->node_kind), + svn_cl__node_kind_str_human_readable(node_kind), svn_path_url_add_component2(root_str, path_str, pool), - node->peg_rev); + peg_rev); } svn_error_t * |