diff options
Diffstat (limited to 'subversion/svn')
| -rw-r--r-- | subversion/svn/auth-cmd.c | 17 | ||||
| -rw-r--r-- | subversion/svn/blame-cmd.c | 42 | ||||
| -rw-r--r-- | subversion/svn/cl.h | 158 | ||||
| -rw-r--r-- | subversion/svn/conflict-callbacks.c | 162 | ||||
| -rw-r--r-- | subversion/svn/diff-cmd.c | 48 | ||||
| -rw-r--r-- | subversion/svn/filesize.c | 221 | ||||
| -rw-r--r-- | subversion/svn/help-cmd.c | 2 | ||||
| -rw-r--r-- | subversion/svn/info-cmd.c | 389 | ||||
| -rw-r--r-- | subversion/svn/list-cmd.c | 89 | ||||
| -rw-r--r-- | subversion/svn/log-cmd.c | 9 | ||||
| -rw-r--r-- | subversion/svn/merge-cmd.c | 40 | ||||
| -rw-r--r-- | subversion/svn/notify.c | 2 | ||||
| -rw-r--r-- | subversion/svn/propset-cmd.c | 2 | ||||
| -rw-r--r-- | subversion/svn/resolve-cmd.c | 2 | ||||
| -rw-r--r-- | subversion/svn/revert-cmd.c | 3 | ||||
| -rw-r--r-- | subversion/svn/shelf-cmd.c | 1405 | ||||
| -rw-r--r-- | subversion/svn/shelf-cmd.h | 49 | ||||
| -rw-r--r-- | subversion/svn/shelf2-cmd.c | 1369 | ||||
| -rw-r--r-- | subversion/svn/shelf2-cmd.h | 49 | ||||
| -rw-r--r-- | subversion/svn/shelve-cmd.c | 369 | ||||
| -rw-r--r-- | subversion/svn/svn.c | 1163 | ||||
| -rw-r--r-- | subversion/svn/util.c | 2 | 
22 files changed, 4477 insertions, 1115 deletions
| diff --git a/subversion/svn/auth-cmd.c b/subversion/svn/auth-cmd.c index 68ca067e736b7..b064b8a4d5252 100644 --- a/subversion/svn/auth-cmd.c +++ b/subversion/svn/auth-cmd.c @@ -455,12 +455,15 @@ svn_cl__auth(apr_getopt_t *os, void *baton, apr_pool_t *pool)          {            if (b.patterns->nelts == 0)              SVN_ERR(svn_cmdline_printf(pool, -                      _("Credentials cache in '%s' contains %d credentials\n"), +                      Q_("Credentials cache in '%s' contains %d credential\n", +                         "Credentials cache in '%s' contains %d credentials\n", +                         b.matches),                        svn_dirent_local_style(config_path, pool), b.matches));            else              SVN_ERR(svn_cmdline_printf(pool, -                      _("Credentials cache in '%s' contains %d matching " -                        "credentials\n"), +                      Q_("Credentials cache in '%s' contains %d matching credential\n", +                         "Credentials cache in '%s' contains %d matching credentials\n", +                         b.matches),                        svn_dirent_local_style(config_path, pool), b.matches));          } @@ -474,9 +477,11 @@ svn_cl__auth(apr_getopt_t *os, void *baton, apr_pool_t *pool)                                     "no matching credentials"),                                   svn_dirent_local_style(config_path, pool));        else -        SVN_ERR(svn_cmdline_printf(pool, _("Deleted %d matching credentials " -                                   "from '%s'\n"), b.matches, -                                   svn_dirent_local_style(config_path, pool))); +        SVN_ERR(svn_cmdline_printf(pool, +                  Q_("Deleted %d matching credential from '%s'\n", +                     "Deleted %d matching credentials from '%s'\n", +                     b.matches), +                  b.matches, svn_dirent_local_style(config_path, pool)));      }    return SVN_NO_ERROR; diff --git a/subversion/svn/blame-cmd.c b/subversion/svn/blame-cmd.c index 3911a64258f8f..8f955766b7c32 100644 --- a/subversion/svn/blame-cmd.c +++ b/subversion/svn/blame-cmd.c @@ -44,6 +44,7 @@ typedef struct blame_baton_t    svn_stream_t *out;    svn_stringbuf_t *sbuf; +  svn_revnum_t start_revnum, end_revnum;    int rev_maxlength;  } blame_baton_t; @@ -54,15 +55,13 @@ typedef struct blame_baton_t     XML to stdout. */  static svn_error_t *  blame_receiver_xml(void *baton, -                   svn_revnum_t start_revnum, -                   svn_revnum_t end_revnum,                     apr_int64_t line_no,                     svn_revnum_t revision,                     apr_hash_t *rev_props,                     svn_revnum_t merged_revision,                     apr_hash_t *merged_rev_props,                     const char *merged_path, -                   const char *line, +                   const svn_string_t *line,                     svn_boolean_t local_change,                     apr_pool_t *pool)  { @@ -170,15 +169,13 @@ print_line_info(svn_stream_t *out,  /* This implements the svn_client_blame_receiver3_t interface. */  static svn_error_t *  blame_receiver(void *baton, -               svn_revnum_t start_revnum, -               svn_revnum_t end_revnum,                 apr_int64_t line_no,                 svn_revnum_t revision,                 apr_hash_t *rev_props,                 svn_revnum_t merged_revision,                 apr_hash_t *merged_rev_props,                 const char *merged_path, -               const char *line, +               const svn_string_t *line,                 svn_boolean_t local_change,                 apr_pool_t *pool)  { @@ -188,19 +185,19 @@ blame_receiver(void *baton,    svn_boolean_t use_merged = FALSE;    if (!bb->rev_maxlength) -    { -      svn_revnum_t max_revnum = MAX(start_revnum, end_revnum); -      /* The standard column width for the revision number is 6 characters. -         If the revision number can potentially be larger (i.e. if the end_revnum -          is larger than 1000000), we increase the column width as needed. */ - -      bb->rev_maxlength = 6; -      while (max_revnum >= 1000000) -        { -          bb->rev_maxlength++; -          max_revnum = max_revnum / 10; -        } -    } +  { +    svn_revnum_t max_revnum = MAX(bb->start_revnum, bb->end_revnum); +    /* The standard column width for the revision number is 6 characters. +       If the revision number can potentially be larger (i.e. if the end_revnum +        is larger than 1000000), we increase the column width as needed. */ + +    bb->rev_maxlength = 6; +    while (max_revnum >= 1000000) +      { +        bb->rev_maxlength++; +        max_revnum = max_revnum / 10; +      } +  }    if (opt_state->use_merge_history)      { @@ -237,7 +234,7 @@ blame_receiver(void *baton,                              bb->rev_maxlength,                              pool)); -  return svn_stream_printf(out, pool, "%s%s", line, APR_EOL_STR); +  return svn_stream_printf(out, pool, "%s%s", line->data, APR_EOL_STR);  } @@ -333,7 +330,7 @@ svn_cl__blame(apr_getopt_t *os,        const char *target = APR_ARRAY_IDX(targets, i, const char *);        const char *truepath;        svn_opt_revision_t peg_revision; -      svn_client_blame_receiver3_t receiver; +      svn_client_blame_receiver4_t receiver;        svn_pool_clear(subpool);        SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); @@ -368,7 +365,8 @@ svn_cl__blame(apr_getopt_t *os,        else          receiver = blame_receiver; -      err = svn_client_blame5(truepath, +      err = svn_client_blame6(&bl.start_revnum, &bl.end_revnum, +                              truepath,                                &peg_revision,                                &opt_state->start_revision,                                &opt_state->end_revision, diff --git a/subversion/svn/cl.h b/subversion/svn/cl.h index a5b1d9b948ae3..955e3ea8720c3 100644 --- a/subversion/svn/cl.h +++ b/subversion/svn/cl.h @@ -32,6 +32,7 @@  #include <apr_tables.h>  #include <apr_getopt.h> +#include "svn_types.h"  #include "svn_wc.h"  #include "svn_client.h"  #include "svn_string.h" @@ -126,6 +127,16 @@ typedef enum svn_cl__show_revs_t {  svn_cl__show_revs_t  svn_cl__show_revs_from_word(const char *word); + +/* Unit types for file size conversion. */ +typedef enum svn_cl__size_unit_t +  { +    SVN_CL__SIZE_UNIT_NONE = 0,       /* Default, no conversion. */ +    SVN_CL__SIZE_UNIT_XML = -1,       /* Conversion for XML output. */ +    SVN_CL__SIZE_UNIT_BASE_10 = 1000, /* Use base-10 SI units. */ +    SVN_CL__SIZE_UNIT_BASE_2 = 1024   /* Use base-2 SI units. */ +  } svn_cl__size_unit_t; +  /*** Command dispatch. ***/ @@ -250,13 +261,20 @@ typedef struct svn_cl__opt_state_t    svn_boolean_t mergeinfo_log;     /* show log message in mergeinfo command */    svn_boolean_t remove_unversioned;/* remove unversioned items */    svn_boolean_t remove_ignored;    /* remove ignored items */ +  svn_boolean_t remove_added;      /* reverting added item also removes it */    svn_boolean_t no_newline;        /* do not output the trailing newline */    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_boolean_t drop;             /* drop shelf after successful unshelve */ +  svn_cl__size_unit_t file_size_unit; /* file size format */ +  enum svn_cl__viewspec_t { +      svn_cl__viewspec_unspecified = 0 /* default */, +      svn_cl__viewspec_classic, +      svn_cl__viewspec_svn11 +  } viewspec;                     /* value of --x-viewspec */  } svn_cl__opt_state_t;  /* Conflict stats for operations such as update and merge. */ @@ -270,6 +288,103 @@ typedef struct svn_cl__cmd_baton_t  } svn_cl__cmd_baton_t; +/* Add an identifier here for long options that don't have a short +   option. Options that have both long and short options should just +   use the short option letter as identifier.  */ +typedef enum svn_cl__longopt_t { +  opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID, +  opt_auth_password_from_stdin, +  opt_auth_username, +  opt_autoprops, +  opt_changelist, +  opt_config_dir, +  opt_config_options, +  /* diff options */ +  opt_diff_cmd, +  opt_internal_diff, +  opt_no_diff_added, +  opt_no_diff_deleted, +  opt_show_copies_as_adds, +  opt_notice_ancestry, +  opt_summarize, +  opt_use_git_diff_format, +  opt_ignore_properties, +  opt_properties_only, +  opt_patch_compatible, +  /* end of diff options */ +  opt_dry_run, +  opt_editor_cmd, +  opt_encoding, +  opt_force_log, +  opt_force, +  opt_keep_changelists, +  opt_ignore_ancestry, +  opt_ignore_externals, +  opt_incremental, +  opt_merge_cmd, +  opt_native_eol, +  opt_new_cmd, +  opt_no_auth_cache, +  opt_no_autoprops, +  opt_no_ignore, +  opt_no_unlock, +  opt_non_interactive, +  opt_force_interactive, +  opt_old_cmd, +  opt_record_only, +  opt_relocate, +  opt_remove, +  opt_revprop, +  opt_stop_on_copy, +  opt_strict,                   /* ### DEPRECATED */ +  opt_targets, +  opt_depth, +  opt_set_depth, +  opt_version, +  opt_xml, +  opt_keep_local, +  opt_with_revprop, +  opt_with_all_revprops, +  opt_with_no_revprops, +  opt_parents, +  opt_accept, +  opt_show_revs, +  opt_reintegrate, +  opt_trust_server_cert, +  opt_trust_server_cert_failures, +  opt_strip, +  opt_ignore_keywords, +  opt_reverse_diff, +  opt_ignore_whitespace, +  opt_diff, +  opt_allow_mixed_revisions, +  opt_include_externals, +  opt_show_inherited_props, +  opt_search, +  opt_search_and, +  opt_mergeinfo_log, +  opt_remove_unversioned, +  opt_remove_ignored, +  opt_remove_added, +  opt_no_newline, +  opt_show_passwords, +  opt_pin_externals, +  opt_show_item, +  opt_adds_as_modification, +  opt_vacuum_pristines, +  opt_drop, +  opt_viewspec, +} svn_cl__longopt_t; + +/* Options for giving a log message.  (Some of these also have other uses.) + */ +#define SVN_CL__LOG_MSG_OPTIONS 'm', 'F', \ +                                opt_force_log, \ +                                opt_editor_cmd, \ +                                opt_encoding, \ +                                opt_with_revprop + +  /* Declare all the command procedures */  svn_opt_subcommand_t    svn_cl__add, @@ -304,9 +419,6 @@ 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, @@ -315,7 +427,7 @@ svn_opt_subcommand_t  /* See definition in svn.c for documentation. */ -extern const svn_opt_subcommand_desc2_t svn_cl__cmd_table[]; +extern const svn_opt_subcommand_desc3_t *svn_cl__cmd_table;  /* See definition in svn.c for documentation. */  extern const int svn_cl__global_options[]; @@ -389,7 +501,7 @@ svn_error_t *  svn_cl__print_conflict_stats(svn_cl__conflict_stats_t *conflict_stats,                               apr_pool_t *scratch_pool); -/*  +/*   * Interactively resolve the conflict a @a CONFLICT.   * TODO: more docs   */ @@ -406,7 +518,7 @@ svn_cl__resolve_conflict(svn_boolean_t *quit,                           svn_client_ctx_t *ctx,                           apr_pool_t *scratch_pool); -/*  +/*   * Interactively resolve conflicts for all TARGETS.   * TODO: more docs   */ @@ -711,6 +823,24 @@ svn_cl__node_kind_str_xml(svn_node_kind_t kind);  const char *  svn_cl__node_kind_str_human_readable(svn_node_kind_t kind); +/* Set *RESULT to the size of a file, formatted according to BASE. +   For base-10 and base-2 units, the size is constrained to at most +   three significant digits. + +   If LONG_UNITS is TRUE, any unit suffixes will be the whole SI symbol, +   e.g., KiB, MiB, etc; otherwise only the first letters will be used. + +   File sizes are never negative, so we don't handle that case other than +   making sure that the scale adjustment will work. + +   The result will be allocated from RESULT_POOL. */ +svn_error_t * +svn_cl__format_file_size(const char **result, +                         svn_filesize_t size, +                         svn_cl__size_unit_t base, +                         svn_boolean_t long_units, +                         apr_pool_t *result_pool); +  /** Provides an XML name for a given OPERATION.   * Note: POOL is currently not used. @@ -915,6 +1045,20 @@ svn_cl__similarity_check(const char *key,                           apr_size_t token_count,                           apr_pool_t *scratch_pool); +/* Return in FUNC_P and BATON_P a callback that prints a summary diff, + * according to the options XML and IGNORE_PROPERTIES. + * + * ANCHOR is a URL or local path to be prefixed to the printed paths. + */ +svn_error_t * +svn_cl__get_diff_summary_writer(svn_client_diff_summarize_func_t *func_p, +                                void **baton_p, +                                svn_boolean_t xml, +                                svn_boolean_t ignore_properties, +                                const char *anchor, +                                apr_pool_t *result_pool, +                                apr_pool_t *scratch_pool); +  #ifdef __cplusplus  }  #endif /* __cplusplus */ diff --git a/subversion/svn/conflict-callbacks.c b/subversion/svn/conflict-callbacks.c index 278fffcdb8826..1c073e6b1c6a4 100644 --- a/subversion/svn/conflict-callbacks.c +++ b/subversion/svn/conflict-callbacks.c @@ -234,7 +234,7 @@ merge_prop_conflict(svn_stream_t *output,      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_mem_string_diff3(&diff, base_propval,                                      merged_propval ? @@ -361,7 +361,7 @@ edit_prop_conflict(const svn_string_t **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);  +      *merged_propval = svn_string_create_from_buf(buf, result_pool);      }    return SVN_NO_ERROR; @@ -440,6 +440,17 @@ static const resolver_option_t builtin_resolver_options[] =    /* Options for local move vs incoming edit. */    { "m", svn_client_conflict_option_local_move_file_text_merge }, +  { "m", svn_client_conflict_option_local_move_dir_merge }, + +  /* Options for local missing vs incoming edit. */ +  { "m", svn_client_conflict_option_sibling_move_file_text_merge }, +  { "m", svn_client_conflict_option_sibling_move_dir_merge }, + +  /* Options for incoming move vs local move. */ +  { "m", svn_client_conflict_option_both_moved_file_merge }, +  { "M", svn_client_conflict_option_both_moved_file_move_merge }, +  { "m", svn_client_conflict_option_both_moved_dir_merge }, +  { "M", svn_client_conflict_option_both_moved_dir_move_merge },    { NULL }  }; @@ -889,7 +900,7 @@ handle_text_conflict(svn_boolean_t *resolved,    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;  +  svn_client_conflict_option_id_t option_id;    option_id = svn_client_conflict_option_unspecified; @@ -1529,17 +1540,16 @@ build_tree_conflict_options(            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) -        { -          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)); -        } +      if (*possible_moved_to_repos_relpaths == NULL) +        SVN_ERR( +          svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( +            possible_moved_to_repos_relpaths, builtin_option, +            result_pool, iterpool)); + +      if (*possible_moved_to_abspaths == NULL) +        SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( +                  possible_moved_to_abspaths, builtin_option, +                  result_pool, iterpool));      }    svn_pool_destroy(iterpool); @@ -1549,11 +1559,11 @@ build_tree_conflict_options(        /* 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 == NULL ||             (*possible_moved_to_repos_relpaths)->nelts <= 1))          continue;        if (strcmp(o->code, "w") == 0 && -          (*possible_moved_to_abspaths == NULL ||  +          (*possible_moved_to_abspaths == NULL ||             (*possible_moved_to_abspaths)->nelts <= 1))          continue; @@ -1654,8 +1664,8 @@ prompt_move_target_path(int *preferred_move_target_idx,          {            char buf[1024]; -          svn_cmdline_fprintf(stderr, iterpool, "%s\n", -                              svn_err_best_message(err, buf, sizeof(buf))); +          SVN_ERR(svn_cmdline_fprintf(stderr, iterpool, "%s\n", +                                      svn_err_best_message(err, buf, sizeof(buf))));            svn_error_clear(err);            continue;          } @@ -1670,6 +1680,69 @@ prompt_move_target_path(int *preferred_move_target_idx,    return SVN_NO_ERROR;  } +static svn_error_t * +find_conflict_option_with_repos_move_targets( +  svn_client_conflict_option_t **option_with_move_targets, +  apr_array_header_t *options, +  apr_pool_t *scratch_pool) +{ +  apr_pool_t *iterpool = svn_pool_create(scratch_pool); +  int i; +  apr_array_header_t *possible_moved_to_repos_relpaths = NULL; + +  *option_with_move_targets = NULL; + +  for (i = 0; i < options->nelts; i++) +    { +      svn_client_conflict_option_t *option; + +      svn_pool_clear(iterpool); +      option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); +      SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( +        &possible_moved_to_repos_relpaths, option, iterpool, iterpool)); +      if (possible_moved_to_repos_relpaths) +        { +          *option_with_move_targets = option; +          break; +        } +    } +  svn_pool_destroy(iterpool); + +  return SVN_NO_ERROR; +} + +static svn_error_t * +find_conflict_option_with_working_copy_move_targets( +  svn_client_conflict_option_t **option_with_move_targets, +  apr_array_header_t *options, +  apr_pool_t *scratch_pool) +{ +  apr_pool_t *iterpool = svn_pool_create(scratch_pool); +  int i; +  apr_array_header_t *possible_moved_to_abspaths = NULL; + +  *option_with_move_targets = NULL; + +  for (i = 0; i < options->nelts; i++) +    { +      svn_client_conflict_option_t *option; + +      svn_pool_clear(iterpool); +      option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); +      SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates2( +              &possible_moved_to_abspaths, option, scratch_pool, +              iterpool)); +      if (possible_moved_to_abspaths) +        { +          *option_with_move_targets = option; +          break; +        } +    } +  svn_pool_destroy(iterpool); + +  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. */ @@ -1797,7 +1870,7 @@ handle_tree_conflict(svn_boolean_t *resolved,          {            int preferred_move_target_idx;            apr_array_header_t *options; -          svn_client_conflict_option_t *conflict_option; +          svn_client_conflict_option_t *option;            SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,                                            possible_moved_to_repos_relpaths, @@ -1810,22 +1883,12 @@ handle_tree_conflict(svn_boolean_t *resolved,                                                                    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) +          SVN_ERR(find_conflict_option_with_repos_move_targets( +            &option, options, iterpool)); +          if (option)              { -              conflict_option = -                svn_client_conflict_option_find_by_id(  -                  options, svn_client_conflict_option_incoming_move_dir_merge); -            } - -          if (conflict_option) -            { -              SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath( -                        conflict_option, preferred_move_target_idx, -                        ctx, iterpool)); +              SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath2( +                        option, preferred_move_target_idx, ctx, iterpool));                repos_move_target_chosen = TRUE;                wc_move_target_chosen = FALSE; @@ -1851,7 +1914,7 @@ handle_tree_conflict(svn_boolean_t *resolved,          {            int preferred_move_target_idx;            apr_array_header_t *options; -          svn_client_conflict_option_t *conflict_option; +          svn_client_conflict_option_t *option;            SVN_ERR(prompt_move_target_path(&preferred_move_target_idx,                                             possible_moved_to_abspaths, TRUE, @@ -1863,22 +1926,12 @@ handle_tree_conflict(svn_boolean_t *resolved,                                                                    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) -            { -              conflict_option = -                svn_client_conflict_option_find_by_id(  -                  options, svn_client_conflict_option_incoming_move_dir_merge); -            } - -          if (conflict_option) +          SVN_ERR(find_conflict_option_with_working_copy_move_targets( +            &option, options, iterpool)); +          if (option)              { -              SVN_ERR(svn_client_conflict_option_set_moved_to_abspath( -                        conflict_option, preferred_move_target_idx, ctx, -                        iterpool)); +              SVN_ERR(svn_client_conflict_option_set_moved_to_abspath2( +                        option, preferred_move_target_idx, ctx, iterpool));                wc_move_target_chosen = TRUE;                /* Update option description. */ @@ -1930,7 +1983,6 @@ resolve_conflict_interactively(svn_boolean_t *resolved,                                 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; @@ -1964,7 +2016,7 @@ resolve_conflict_interactively(svn_boolean_t *resolved,    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)); +                                  conflict_stats, ctx, scratch_pool, scratch_pool));    if (tree_conflicted)      SVN_ERR(handle_tree_conflict(resolved, postponed, quit, printed_description,                                   conflict, path_prefix, pb, conflict_stats, ctx, @@ -2201,11 +2253,14 @@ svn_cl__resolve_conflict(svn_boolean_t *quit,        svn_boolean_t postponed = FALSE;        svn_boolean_t printed_description = FALSE;        svn_error_t *err; +      apr_pool_t *iterpool;        *quit = FALSE; +      iterpool = svn_pool_create(scratch_pool);        while (!resolved && !postponed && !*quit)          { +          svn_pool_clear(iterpool);            err = resolve_conflict_interactively(&resolved, &postponed, quit,                                                 external_failed,                                                 printed_summary, @@ -2214,7 +2269,7 @@ svn_cl__resolve_conflict(svn_boolean_t *quit,                                                 editor_cmd, ctx->config,                                                 path_prefix, pb,                                                 conflict_stats, ctx, -                                               scratch_pool, scratch_pool); +                                               iterpool);            if (err && err->apr_err == SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE)              {                /* Conflict resolution has failed. Let the user try again. @@ -2226,6 +2281,7 @@ svn_cl__resolve_conflict(svn_boolean_t *quit,              }            SVN_ERR(err);          } +      svn_pool_destroy(iterpool);      }    else if (option_id != svn_client_conflict_option_postpone)      SVN_ERR(mark_conflict_resolved(conflict, option_id, diff --git a/subversion/svn/diff-cmd.c b/subversion/svn/diff-cmd.c index 9e389ec856a69..b5f3702033f05 100644 --- a/subversion/svn/diff-cmd.c +++ b/subversion/svn/diff-cmd.c @@ -181,6 +181,24 @@ summarize_regular(const svn_client_diff_summarize_t *summary,    return svn_cmdline_fflush(stdout);  } +svn_error_t * +svn_cl__get_diff_summary_writer(svn_client_diff_summarize_func_t *func_p, +                                void **baton_p, +                                svn_boolean_t xml, +                                svn_boolean_t ignore_properties, +                                const char *anchor, +                                apr_pool_t *result_pool, +                                apr_pool_t *scratch_pool) +{ +  struct summarize_baton_t *b = apr_pcalloc(result_pool, sizeof(*b)); + +  b->anchor = anchor; +  b->ignore_properties = ignore_properties; +  *func_p = xml ? summarize_xml : summarize_regular; +  *baton_p = b; +  return SVN_NO_ERROR; +} +  /* An svn_opt_subcommand_t to handle the 'diff' command.     This implements the `svn_opt_subcommand_t' interface. */  svn_error_t * @@ -203,9 +221,6 @@ svn_cl__diff(apr_getopt_t *os,    svn_boolean_t ignore_properties =      opt_state->diff.patch_compatible || opt_state->diff.ignore_properties;    int i; -  struct summarize_baton_t summarize_baton; -  const svn_client_diff_summarize_func_t summarize_func = -    (opt_state->xml ? summarize_xml : summarize_regular);    if (opt_state->extensions)      options = svn_cstring_split(opt_state->extensions, " \t\n\r", TRUE, pool); @@ -448,9 +463,13 @@ svn_cl__diff(apr_getopt_t *os,            if (opt_state->diff.summarize)              { -              summarize_baton.anchor = target1; -              summarize_baton.ignore_properties = ignore_properties; +              svn_client_diff_summarize_func_t summarize_func; +              void *summarize_baton; +              SVN_ERR(svn_cl__get_diff_summary_writer( +                                &summarize_func, &summarize_baton, +                                opt_state->xml, ignore_properties, target1, +                                iterpool, iterpool));                SVN_ERR(svn_client_diff_summarize2(                                  target1,                                  &opt_state->start_revision, @@ -459,11 +478,11 @@ svn_cl__diff(apr_getopt_t *os,                                  opt_state->depth,                                  ! opt_state->diff.notice_ancestry,                                  opt_state->changelists, -                                summarize_func, &summarize_baton, +                                summarize_func, summarize_baton,                                  ctx, iterpool));              }            else -            SVN_ERR(svn_client_diff6( +            SVN_ERR(svn_client_diff7(                       options,                       target1,                       &(opt_state->start_revision), @@ -479,6 +498,7 @@ svn_cl__diff(apr_getopt_t *os,                       ignore_properties,                       opt_state->diff.properties_only,                       opt_state->diff.use_git_diff_format, +                     TRUE /*pretty_print_mergeinfo*/,                       svn_cmdline_output_encoding(pool),                       outstream,                       errstream, @@ -501,8 +521,13 @@ svn_cl__diff(apr_getopt_t *os,            if (opt_state->diff.summarize)              { -              summarize_baton.anchor = truepath; -              summarize_baton.ignore_properties = ignore_properties; +              svn_client_diff_summarize_func_t summarize_func; +              void *summarize_baton; + +              SVN_ERR(svn_cl__get_diff_summary_writer( +                                &summarize_func, &summarize_baton, +                                opt_state->xml, ignore_properties, truepath, +                                iterpool, iterpool));                SVN_ERR(svn_client_diff_summarize_peg2(                                  truepath,                                  &peg_revision, @@ -511,11 +536,11 @@ svn_cl__diff(apr_getopt_t *os,                                  opt_state->depth,                                  ! opt_state->diff.notice_ancestry,                                  opt_state->changelists, -                                summarize_func, &summarize_baton, +                                summarize_func, summarize_baton,                                  ctx, iterpool));              }            else -            SVN_ERR(svn_client_diff_peg6( +            SVN_ERR(svn_client_diff_peg7(                       options,                       truepath,                       &peg_revision, @@ -531,6 +556,7 @@ svn_cl__diff(apr_getopt_t *os,                       ignore_properties,                       opt_state->diff.properties_only,                       opt_state->diff.use_git_diff_format, +                     TRUE /*pretty_print_mergeinfo*/,                       svn_cmdline_output_encoding(pool),                       outstream,                       errstream, diff --git a/subversion/svn/filesize.c b/subversion/svn/filesize.c new file mode 100644 index 0000000000000..ba1c35626b611 --- /dev/null +++ b/subversion/svn/filesize.c @@ -0,0 +1,221 @@ +/* + * filesize.c -- Utilities for displaying file sizes + * + * ==================================================================== + *    Licensed to the Apache Software Foundation (ASF) under one + *    or more contributor license agreements.  See the NOTICE file + *    distributed with this work for additional information + *    regarding copyright ownership.  The ASF licenses this file + *    to you under the Apache License, Version 2.0 (the + *    "License"); you may not use this file except in compliance + *    with the License.  You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + *    Unless required by applicable law or agreed to in writing, + *    software distributed under the License is distributed on an + *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + *    KIND, either express or implied.  See the License for the + *    specific language governing permissions and limitations + *    under the License. + * ==================================================================== + */ + + +/*** Includes. ***/ + +#include <assert.h> +#include <math.h> +#include <stdio.h> + +#include <apr_strings.h> + +#include "cl.h" + + +/*** Code. ***/ + +/* The structure that describes the units and their magnitudes. */ +typedef struct filesize_order_t +{ +  svn_filesize_t mask; +  const char *suffix; +  const char *short_suffix; +} filesize_order_t; + + +/* Get the index of the order of magnitude of the given SIZE. +   The returned index will be within [0 .. order_size - 1]. */ +static apr_size_t +get_order_index(svn_filesize_t abs_size, +                const filesize_order_t *order, +                apr_size_t order_size) +{ +  /* It would be sexy to do a binary search here, but with only 7 elements +     in the arrays ... we should ### FIXME: do the binary search anyway. */ +  apr_size_t index = order_size; +  while (index > 0) +    { +      --index; +      if (abs_size > order[index].mask) +        break; +    } +  return index; +} + + +/* Format the adjusted size with the given units. */ +static const char * +format_size(double human_readable_size, +            svn_boolean_t long_units, +            const filesize_order_t *order, +            apr_size_t index, +            apr_pool_t *result_pool) +{ +  /* NOTE: We want to display a locale-specific decimal sepratator, but +           APR's formatter completely ignores the locale. So we use the +           good, old, standard, *dangerous* sprintf() to format the size. + +           But, on the bright side, we require that the number has no more +           than 3 non-fractional digits. So the call to sprintf() here +           should be safe. */ +  const double absolute_human_readable_size = fabs(human_readable_size); +  const char *const suffix = (long_units ? order[index].suffix +                              : order[index].short_suffix); + +  /*   3 digits (or 2 digits and 1 decimal separator) +     + 1 negative sign (which should not appear under normal circumstances) +     + 1 nul terminator +     --- +     = 5 characters of space needed in the buffer. */ +    char buffer[8]; + +    assert(absolute_human_readable_size < 1000.0); + +    /* When the adjusted size has only one significant digit left of the +       decimal point, show tenths of a unit, too. */ +    sprintf(buffer, "%.*f", +            absolute_human_readable_size < 10.0 ? 1 : 0, +            human_readable_size); +    return apr_pstrcat(result_pool, buffer, suffix, SVN_VA_NULL); +} + + +static const char * +get_base2_unit_file_size(svn_filesize_t size, +                         svn_boolean_t long_units, +                         apr_pool_t *result_pool) +{ +  static const filesize_order_t order[] = +    { +      {APR_INT64_C(0x0000000000000000), " B",   "B"}, /* byte */ +      {APR_INT64_C(0x00000000000003FF), " KiB", "K"}, /* kibi */ +      {APR_INT64_C(0x00000000000FFFFF), " MiB", "M"}, /* mibi */ +      {APR_INT64_C(0x000000003FFFFFFF), " GiB", "G"}, /* gibi */ +      {APR_INT64_C(0x000000FFFFFFFFFF), " TiB", "T"}, /* tibi */ +      {APR_INT64_C(0x0003FFFFFFFFFFFF), " EiB", "E"}, /* exbi */ +      {APR_INT64_C(0x0FFFFFFFFFFFFFFF), " PiB", "P"}  /* pibi */ +    }; +  static const apr_size_t order_size = sizeof(order) / sizeof(order[0]); + +  const svn_filesize_t abs_size = ((size < 0) ? -size : size); +  apr_size_t index = get_order_index(abs_size, order, order_size); +  double human_readable_size; + +  /* Adjust the size to the given order of magnitude. + +     This is division by (order[index].mask + 1), which is the base-2^10 +     magnitude of the size; and that is the same as an arithmetic right +     shift by (index * 10) bits. But we split it into an integer and a +     floating-point division, so that we don't overflow the mantissa at +     very large file sizes. */ +  if ((abs_size >> 10 * index) > 999) +    { +      /* This assertion should never fail, because we only have 4 binary +         digits in the petabyte (all right, "pibibyte") range and so the +         number of petabytes can't be large enough to cause the program +         flow to enter this conditional block. */ +      assert(index < order_size - 1); +      ++index; +    } +  human_readable_size = (index == 0 ? (double)size +                         : (size >> 3 * index) / 128.0 / index); + +  return format_size(human_readable_size, +                     long_units, order, index, result_pool); +} + + +static const char * +get_base10_unit_file_size(svn_filesize_t size, +                          svn_boolean_t long_units, +                          apr_pool_t *result_pool) +{ +  static const filesize_order_t order[] = +    { +      {APR_INT64_C(                 0), " B",  "B"}, /* byte */ +      {APR_INT64_C(               999), " kB", "k"}, /* kilo */ +      {APR_INT64_C(            999999), " MB", "M"}, /* mega */ +      {APR_INT64_C(         999999999), " GB", "G"}, /* giga */ +      {APR_INT64_C(      999999999999), " TB", "T"}, /* tera */ +      {APR_INT64_C(   999999999999999), " EB", "E"}, /* exa  */ +      {APR_INT64_C(999999999999999999), " PB", "P"}  /* peta */ +      /*         18446744073709551615 is the maximum value.  */ +    }; +  static const apr_size_t order_size = sizeof(order) / sizeof(order[0]); + +  const svn_filesize_t abs_size = ((size < 0) ? -size : size); +  apr_size_t index = get_order_index(abs_size, order, order_size); +  double human_readable_size; + +  /* Adjust the size to the given order of magnitude. + +     This is division by (order[index].mask + 1), which is the base-1000 +     magnitude of the size. For large file sizes, we split the operation +     into an integer and a floating-point division, so that we don't +     overflow the mantissa. */ +  if (index == 0) +    human_readable_size = (double)size; +  else if (index <= 3) +    human_readable_size = (double)size / (order[index].mask + 1); +  else +    { +      /*                             [   Keep integer division here!   ] */ +      const double divisor = (double)((order[index].mask + 1) / 1000000); +      human_readable_size = (size / 1000000) / divisor; +      /*                    [   And here!  ] */ +    } + +  return format_size(human_readable_size, +                     long_units, order, index, result_pool); +} + + +svn_error_t * +svn_cl__format_file_size(const char **result, +                         svn_filesize_t size, +                         svn_cl__size_unit_t base, +                         svn_boolean_t long_units, +                         apr_pool_t *result_pool) +{ +  switch (base) +    { +    case SVN_CL__SIZE_UNIT_NONE: +    case SVN_CL__SIZE_UNIT_XML: +      *result = apr_psprintf(result_pool, "%" SVN_FILESIZE_T_FMT, size); +      break; + +    case SVN_CL__SIZE_UNIT_BASE_2: +      *result = get_base2_unit_file_size(size, long_units, result_pool); +      break; + +    case SVN_CL__SIZE_UNIT_BASE_10: +      *result = get_base10_unit_file_size(size, long_units, result_pool); +      break; + +    default: +      SVN_ERR_MALFUNCTION(); +    } + +  return SVN_NO_ERROR; +} diff --git a/subversion/svn/help-cmd.c b/subversion/svn/help-cmd.c index 3d5375c65a3e8..f812784e77da2 100644 --- a/subversion/svn/help-cmd.c +++ b/subversion/svn/help-cmd.c @@ -179,7 +179,7 @@ svn_cl__help(apr_getopt_t *os,    svn_stringbuf_appendcstr(version_footer, "* KWallet (KDE)\n");  #endif -  return svn_opt_print_help4(os, +  return svn_opt_print_help5(os,                               "svn",   /* ### erm, derive somehow? */                               opt_state ? opt_state->version : FALSE,                               opt_state ? opt_state->quiet : FALSE, diff --git a/subversion/svn/info-cmd.c b/subversion/svn/info-cmd.c index e0c0041b468a0..44f830f34e5d4 100644 --- a/subversion/svn/info-cmd.c +++ b/subversion/svn/info-cmd.c @@ -21,6 +21,10 @@   * ====================================================================   */ +/* We define this here to remove any further warnings about the usage of +   experimental functions in this file. */ +#define SVN_EXPERIMENTAL +  /* ==================================================================== */ @@ -45,6 +49,254 @@  /*** Code. ***/ +struct layout_list_baton_t +{ +  svn_boolean_t checkout; +  const char *target; +  const char *target_abspath; +  svn_boolean_t with_revs; +  int vs_py_format; +}; + +/* Output as 'svn' command-line commands. + * + * Implements svn_client__layout_func_t + */ +static svn_error_t * +output_svn_command_line(void *layout_baton, +                        const char *local_abspath, +                        const char *repos_root_url, +                        svn_boolean_t not_present, +                        svn_boolean_t url_changed, +                        const char *url, +                        svn_boolean_t revision_changed, +                        svn_revnum_t revision, +                        svn_boolean_t depth_changed, +                        svn_depth_t depth, +                        apr_pool_t *scratch_pool) +{ +  struct layout_list_baton_t *llb = layout_baton; +  const char *relpath = svn_dirent_skip_ancestor(llb->target_abspath, +                                                 local_abspath); +  const char *cmd; +  const char *depth_str; +  const char *url_rev_str; + +  depth_str = (depth_changed +               ? apr_psprintf(scratch_pool, " --set-depth=%s", +                              svn_depth_to_word(depth)) +               : ""); + +  if (llb->checkout) +    { +      cmd = "svn checkout"; +      if (depth != svn_depth_infinity) +        depth_str = apr_psprintf(scratch_pool, +                                 " --depth=%s", svn_depth_to_word(depth)); +      url_rev_str = apr_psprintf(scratch_pool, " %s", url); +      if (llb->with_revs) +        url_rev_str = apr_psprintf(scratch_pool, "%s@%ld", +                                   url_rev_str, revision); +      llb->checkout = FALSE; +    } +  else if (not_present) +    { +      /* Easiest way to create a not present node: update to r0 */ +      cmd = "svn update"; +      url_rev_str = " -r0"; +    } +  else if (url_changed) +    { +      cmd = "svn switch"; +      url_rev_str = apr_psprintf(scratch_pool, " ^/%s", +                                 svn_uri_skip_ancestor(repos_root_url, +                                                       url, scratch_pool)); +      if (llb->with_revs) +        url_rev_str = apr_psprintf(scratch_pool, "%s@%ld", +                                   url_rev_str, revision); +    } +  else if (llb->with_revs && revision_changed) +    { +      cmd = "svn update"; +      url_rev_str = apr_psprintf(scratch_pool, " -r%ld", revision); +    } +  else if (depth_changed) +    { +      cmd = "svn update"; +      url_rev_str = ""; +    } +  else +    return SVN_NO_ERROR; + +  SVN_ERR(svn_cmdline_printf(scratch_pool, +                             "%s%-23s%-10s %s\n", +                             cmd, depth_str, url_rev_str, +                             svn_dirent_local_style( +                               svn_dirent_join(llb->target, relpath, +                                               scratch_pool), scratch_pool))); + +  return SVN_NO_ERROR; +} + +/*  */ +static const char * +depth_to_viewspec_py(svn_depth_t depth, +                     apr_pool_t *result_pool) +{ +  switch (depth) +    { +    case svn_depth_infinity: +      return "/**"; +    case svn_depth_immediates: +      return "/*"; +    case svn_depth_files: +      return "/~"; +    case svn_depth_empty: +      return ""; +    case svn_depth_exclude: +      return "!"; +    default: +      break; +    } +  return NULL; +} + +/* Output in the format used by 'tools/client-side/viewspec.py' + * + * Implements svn_client__layout_func_t + */ +static svn_error_t * +output_svn_viewspec_py(void *layout_baton, +                       const char *local_abspath, +                       const char *repos_root_url, +                       svn_boolean_t not_present, +                       svn_boolean_t url_changed, +                       const char *url, +                       svn_boolean_t revision_changed, +                       svn_revnum_t revision, +                       svn_boolean_t depth_changed, +                       svn_depth_t depth, +                       apr_pool_t *scratch_pool) +{ +  struct layout_list_baton_t *llb = layout_baton; +  const char *relpath = svn_dirent_skip_ancestor(llb->target_abspath, +                                                 local_abspath); +  const char *depth_str; +  const char *rev_str = ""; +  const char *repos_rel_url = ""; + +  depth_str = ((depth_changed || llb->checkout) +               ? depth_to_viewspec_py(depth, scratch_pool) +               : ""); +  if (! llb->with_revs) +    revision_changed = FALSE; +  if (revision_changed) +    rev_str = apr_psprintf(scratch_pool, "@%ld", revision); + +  if (llb->checkout) +    { +      SVN_ERR(svn_cmdline_printf(scratch_pool, +                                 "Format: %d\n" +                                 "Url: %s\n", +                                 llb->vs_py_format, url)); +      if (llb->with_revs) +        SVN_ERR(svn_cmdline_printf(scratch_pool, +                                   "Revision: %ld\n", +                                   revision)); +      SVN_ERR(svn_cmdline_printf(scratch_pool, "\n")); +      llb->checkout = FALSE; + +      if (depth == svn_depth_empty) +        return SVN_NO_ERROR; +      if (depth_str[0] == '/') +        depth_str++; +    } +  else if (not_present) +    { +      /* Easiest way to create a not present node: update to r0 */ +      if (llb->vs_py_format < 2) +        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, +                                 _("svn-viewspec.py format 1 does not support " +                                   "the 'not-present' state found at '%s'"), +                                 relpath); +      rev_str = "@0"; +    } +  else if (url_changed) +    { +      if (llb->vs_py_format < 2) +        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, +                                 _("svn-viewspec.py format 1 does not support " +                                   "the 'switched' state found at '%s'"), +                                 relpath); +      repos_rel_url = svn_uri_skip_ancestor(repos_root_url, url, +                                            scratch_pool); +      repos_rel_url = apr_psprintf(scratch_pool, "^/%s", repos_rel_url); +    } +  else if (!(revision_changed || depth_changed)) +    return SVN_NO_ERROR; + +  SVN_ERR(svn_cmdline_printf(scratch_pool, +                             "%s%s %s%s\n", +                             relpath, depth_str, repos_rel_url, rev_str)); + +  return SVN_NO_ERROR; +} + +/* + * Call svn_client__layout_list(), using a receiver function decided + * by VIEWSPEC. + */ +static svn_error_t * +cl_layout_list(apr_array_header_t *targets, +               enum svn_cl__viewspec_t viewspec, +               void *baton, +               svn_client_ctx_t *ctx, +               apr_pool_t *scratch_pool) +{ +  const char *list_path, *list_abspath; +  struct layout_list_baton_t llb; + +  /* Add "." if user passed 0 arguments */ +  svn_opt_push_implicit_dot_target(targets, scratch_pool); + +  if (targets->nelts > 1) +    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + +  list_path = APR_ARRAY_IDX(targets, 0, const char *); + +  SVN_ERR(svn_cl__check_target_is_local_path(list_path)); + +  SVN_ERR(svn_dirent_get_absolute(&list_abspath, list_path, +                                  scratch_pool)); + +  llb.checkout = TRUE; +  llb.target = list_path; +  llb.target_abspath = list_abspath; +  llb.with_revs = TRUE; + +  switch (viewspec) +    { +    case svn_cl__viewspec_classic: +      /* svn-viewspec.py format */ +      llb.vs_py_format = 2; + +      SVN_ERR(svn_client__layout_list(list_abspath, +                                      output_svn_viewspec_py, &llb, +                                      ctx, scratch_pool)); +      break; +    case svn_cl__viewspec_svn11: +      /* svn command-line format */ +      SVN_ERR(svn_client__layout_list(list_abspath, +                                      output_svn_command_line, &llb, +                                      ctx, scratch_pool)); +      break; +    default: +      SVN_ERR_MALFUNCTION(); +    } + +  return SVN_NO_ERROR; +} +  static svn_error_t *  svn_cl__info_print_time(apr_time_t atime,                          const char *desc, @@ -100,6 +352,7 @@ typedef enum    info_item_relative_url,    info_item_repos_root_url,    info_item_repos_uuid, +  info_item_repos_size,    /* Working copy revision or repository HEAD revision */    info_item_revision, @@ -110,7 +363,10 @@ typedef enum    info_item_last_changed_author,    /* Working copy information */ -  info_item_wc_root +  info_item_wc_root, +  info_item_schedule, +  info_item_depth, +  info_item_changelist  } info_item_t;  /* Mapping between option keywords and info_item_t. */ @@ -128,12 +384,16 @@ static const info_item_map_t info_item_map[] =      { MAKE_STRING("relative-url"),        info_item_relative_url },      { MAKE_STRING("repos-root-url"),      info_item_repos_root_url },      { MAKE_STRING("repos-uuid"),          info_item_repos_uuid }, +    { MAKE_STRING("repos-size"),          info_item_repos_size },      { MAKE_STRING("revision"),            info_item_revision },      { MAKE_STRING("last-changed-revision"),                                            info_item_last_changed_rev },      { MAKE_STRING("last-changed-date"),   info_item_last_changed_date },      { MAKE_STRING("last-changed-author"), info_item_last_changed_author }, -    { MAKE_STRING("wc-root"),             info_item_wc_root } +    { MAKE_STRING("wc-root"),             info_item_wc_root }, +    { MAKE_STRING("schedule"),            info_item_schedule }, +    { MAKE_STRING("depth"),               info_item_depth }, +    { MAKE_STRING("changelist"),          info_item_changelist },    };  #undef MAKE_STRING @@ -163,6 +423,9 @@ typedef struct print_info_baton_t    /* Did we already print a line of output? */    svn_boolean_t start_new_line; +  /* Format for file sizes */ +  svn_cl__size_unit_t file_size_unit; +    /* The client context. */    svn_client_ctx_t *ctx;  } print_info_baton_t; @@ -251,21 +514,40 @@ print_info_xml(void *baton,                 apr_pool_t *pool)  {    svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); -  const char *rev_str;    print_info_baton_t *const receiver_baton = baton; -  if (SVN_IS_VALID_REVNUM(info->rev)) -    rev_str = apr_psprintf(pool, "%ld", info->rev); -  else -    rev_str = apr_pstrdup(pool, _("Resource is not under version control.")); +  const char *const path_str = +    svn_cl__local_style_skip_ancestor( +        receiver_baton->path_prefix, target, pool); +  const char *const kind_str = svn_cl__node_kind_str_xml(info->kind); +  const char *const rev_str = +    (SVN_IS_VALID_REVNUM(info->rev) +     ? apr_psprintf(pool, "%ld", info->rev) +     : apr_pstrdup(pool, _("Resource is not under version control.")));    /* "<entry ...>" */ -  svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", -                        "path", svn_cl__local_style_skip_ancestor( -                                  receiver_baton->path_prefix, target, pool), -                        "kind", svn_cl__node_kind_str_xml(info->kind), -                        "revision", rev_str, -                        SVN_VA_NULL); +  if (info->kind == svn_node_file && info->size != SVN_INVALID_FILESIZE) +    { +      const char *size_str; +      SVN_ERR(svn_cl__format_file_size(&size_str, info->size, +                                       SVN_CL__SIZE_UNIT_XML, +                                       FALSE, pool)); + +      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", +                            "path", path_str, +                            "kind", kind_str, +                            "revision", rev_str, +                            "size", size_str, +                            SVN_VA_NULL); +    } +  else +    { +      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", +                            "path", path_str, +                            "kind", kind_str, +                            "revision", rev_str, +                            SVN_VA_NULL); +    }    /* "<url> xx </url>" */    svn_cl__xml_tagged_cdata(&sb, pool, "url", info->URL); @@ -487,6 +769,16 @@ print_info(void *baton,        break;      } +  if (info->kind == svn_node_file && info->size != SVN_INVALID_FILESIZE) +    { +      const char *sizestr; +      SVN_ERR(svn_cl__format_file_size(&sizestr, info->size, +                                       receiver_baton->file_size_unit, +                                       TRUE, pool)); +      SVN_ERR(svn_cmdline_printf(pool, _("Size in Repository: %s\n"), +                                 sizestr)); +    } +    if (info->wc_info)      {        switch (info->wc_info->schedule) @@ -827,11 +1119,12 @@ print_info_item(void *baton,                    apr_pool_t *pool)  {    print_info_baton_t *const receiver_baton = baton; +  const char *const actual_target_path = +    (!receiver_baton->target_is_path ? info->URL +     : svn_cl__local_style_skip_ancestor( +         receiver_baton->path_prefix, target, pool));    const char *const target_path = -    (!receiver_baton->multiple_targets ? NULL -     : (!receiver_baton->target_is_path ? info->URL -        : svn_cl__local_style_skip_ancestor( -            receiver_baton->path_prefix, target, pool))); +    (receiver_baton->multiple_targets ? actual_target_path : NULL);    if (receiver_baton->start_new_line)      SVN_ERR(svn_cmdline_fputs("\n", stdout, pool)); @@ -860,6 +1153,36 @@ print_info_item(void *baton,        SVN_ERR(print_info_item_string(info->repos_UUID, target_path, pool));        break; +    case info_item_repos_size: +      if (info->kind != svn_node_file) +        { +          receiver_baton->start_new_line = FALSE; +          return SVN_NO_ERROR; +        } + +      if (info->size == SVN_INVALID_FILESIZE) +        { +          if (receiver_baton->multiple_targets) +            { +              receiver_baton->start_new_line = FALSE; +              return SVN_NO_ERROR; +            } + +          return svn_error_createf( +              SVN_ERR_UNSUPPORTED_FEATURE, NULL, +              _("can't show in-repository size of working copy file '%s'"), +              actual_target_path); +        } + +      { +        const char *sizestr; +        SVN_ERR(svn_cl__format_file_size(&sizestr, info->size, +                                         receiver_baton->file_size_unit, +                                         TRUE, pool)); +        SVN_ERR(print_info_item_string(sizestr, target_path, pool)); +      } +      break; +      case info_item_revision:        SVN_ERR(print_info_item_revision(info->rev, target_path, pool));        break; @@ -888,6 +1211,27 @@ print_info_item(void *baton,                    target_path, pool));        break; +    case info_item_schedule: +      SVN_ERR(print_info_item_string( +                  (info->wc_info +                   ? schedule_str(info->wc_info->schedule) : NULL), +                  target_path, pool)); +      break; + +    case info_item_depth: +      SVN_ERR(print_info_item_string( +                  ((info->wc_info && info->kind == svn_node_dir) +                   ? svn_depth_to_word(info->wc_info->depth) : NULL), +                  target_path, pool)); +      break; + +    case info_item_changelist: +      SVN_ERR(print_info_item_string( +                  ((info->wc_info && info->wc_info->changelist) +                   ? info->wc_info->changelist : NULL), +                  target_path, pool)); +      break; +      default:        SVN_ERR_MALFUNCTION();      } @@ -918,10 +1262,17 @@ svn_cl__info(apr_getopt_t *os,                                                        opt_state->targets,                                                        ctx, FALSE, pool)); +  if (opt_state->viewspec) +    { +      SVN_ERR(cl_layout_list(targets, opt_state->viewspec, baton, ctx, pool)); +      return SVN_NO_ERROR; +    } +    /* Add "." if user passed 0 arguments. */    svn_opt_push_implicit_dot_target(targets, pool);    receiver_baton.ctx = ctx; +  receiver_baton.file_size_unit = opt_state->file_size_unit;    if (opt_state->xml)      { @@ -935,6 +1286,10 @@ svn_cl__info(apr_getopt_t *os,          return svn_error_create(              SVN_ERR_CL_ARG_PARSING_ERROR, NULL,              _("--no-newline is not valid in --xml mode")); +      if (opt_state->file_size_unit != SVN_CL__SIZE_UNIT_NONE) +        return svn_error_create( +            SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +            _("--human-readable is not valid in --xml mode"));        /* If output is not incremental, output the XML header and wrap           everything in a top-level element. This makes the output in diff --git a/subversion/svn/list-cmd.c b/subversion/svn/list-cmd.c index da18252243d90..121b08f94b6f2 100644 --- a/subversion/svn/list-cmd.c +++ b/subversion/svn/list-cmd.c @@ -40,8 +40,13 @@  /* Baton used when printing directory entries. */  struct print_baton { -  svn_boolean_t verbose;    svn_client_ctx_t *ctx; +  svn_boolean_t verbose; +  svn_cl__size_unit_t file_size_unit; + +  /* Keep track of the width of the author field. */ +  int author_width; +  int max_author_width;    /* To keep track of last seen external information. */    const char *last_external_parent_url; @@ -49,12 +54,23 @@ struct print_baton {    svn_boolean_t in_external;  }; +/* Starting and maximum width of the author field */ +static const int initial_author_width = 8; +static const int initial_human_readable_author_width = 14; +static const int maximum_author_width = 16; +static const int maximum_human_readable_author_width = 22; + +/* Width of the size field */ +static const int normal_size_width = 10; +static const int human_readable_size_width = 4; +  /* Field flags required for this function */  static const apr_uint32_t print_dirent_fields = SVN_DIRENT_KIND;  static const apr_uint32_t print_dirent_fields_verbose = (      SVN_DIRENT_KIND  | SVN_DIRENT_SIZE | SVN_DIRENT_TIME |      SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR); +  /* This implements the svn_client_list_func2_t API, printing a single     directory entry in text format. */  static svn_error_t * @@ -121,7 +137,11 @@ print_dirent(void *baton,        apr_status_t apr_err;        apr_size_t size;        char timestr[20]; -      const char *sizestr, *utf8_timestr; +      const int sizewidth = (pb->file_size_unit == SVN_CL__SIZE_UNIT_NONE +                             ? normal_size_width +                             : human_readable_size_width); +      const char *sizestr = ""; +      const char *utf8_timestr;        /* svn_time_to_human_cstring gives us something *way* too long           to use for this, so we have to roll our own.  We include @@ -146,15 +166,33 @@ print_dirent(void *baton,        /* we need it in UTF-8. */        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool)); -      sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, -                             dirent->size); +      /* We may have to adjust the width of th 'author' field. */ +      if (dirent->last_author) +        { +          const int author_width = (int)strlen(dirent->last_author); +          if (author_width > pb->author_width) +            { +              if (author_width < pb->max_author_width) +                pb->author_width = author_width; +              else +                pb->author_width = pb->max_author_width; +            } +        } + +      if (dirent->kind == svn_node_file) +        { +          SVN_ERR(svn_cl__format_file_size(&sizestr, dirent->size, +                                           pb->file_size_unit, +                                           FALSE, scratch_pool)); +        }        return svn_cmdline_printf -              (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n", +              (scratch_pool, "%7ld %-*.*s %c %*s %12s %s%s\n",                 dirent->created_rev, +               pb->author_width, pb->author_width,                 dirent->last_author ? dirent->last_author : " ? ",                 lock ? 'O' : ' ', -               (dirent->kind == svn_node_file) ? sizestr : "", +               sizewidth, sizestr,                 utf8_timestr,                 entryname,                 (dirent->kind == svn_node_dir) ? "/" : ""); @@ -238,9 +276,11 @@ print_dirent_xml(void *baton,    if (dirent->kind == svn_node_file)      { -      svn_cl__xml_tagged_cdata -        (&sb, scratch_pool, "size", -         apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size)); +      const char *sizestr; +      SVN_ERR(svn_cl__format_file_size(&sizestr, dirent->size, +                                       SVN_CL__SIZE_UNIT_XML, +                                       FALSE, scratch_pool)); +      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "size", sizestr);      }    svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit", @@ -303,11 +343,17 @@ svn_cl__list(apr_getopt_t *os,    if (opt_state->xml)      { -      /* The XML output contains all the information, so "--verbose" -         does not apply. */ +      /* The XML output contains all the information, so "--verbose" does +         not apply, and using "--human-readable" with machine-readable +         output does not make sense. */        if (opt_state->verbose) -        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, -                                _("'verbose' option invalid in XML mode")); +        return svn_error_create( +            SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +            _("--verbose is not valid in --xml mode")); +      if (opt_state->file_size_unit != SVN_CL__SIZE_UNIT_NONE) +        return svn_error_create( +            SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +            _("--human-readable is not valid in --xml mode"));        /* If output is not incremental, output the XML header and wrap           everything in a top-level element. This makes the output in @@ -318,9 +364,9 @@ svn_cl__list(apr_getopt_t *os,    else      {        if (opt_state->incremental) -        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, -                                _("'incremental' option only valid in XML " -                                  "mode")); +        return svn_error_create( +            SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +            _("--incremental is only valid in --xml mode"));      }    if (opt_state->xml) @@ -332,6 +378,17 @@ svn_cl__list(apr_getopt_t *os,    pb.ctx = ctx;    pb.verbose = opt_state->verbose; +  pb.file_size_unit = opt_state->file_size_unit; +  if (pb.file_size_unit == SVN_CL__SIZE_UNIT_NONE) +    { +      pb.author_width = initial_author_width; +      pb.max_author_width = maximum_author_width; +    } +  else +    { +      pb.author_width = initial_human_readable_author_width; +      pb.max_author_width = maximum_human_readable_author_width; +    }    if (opt_state->depth == svn_depth_unknown)      opt_state->depth = svn_depth_immediates; diff --git a/subversion/svn/log-cmd.c b/subversion/svn/log-cmd.c index 57f8415069143..87c372846f172 100644 --- a/subversion/svn/log-cmd.c +++ b/subversion/svn/log-cmd.c @@ -88,7 +88,7 @@ display_diff(const svn_log_entry_t *log_entry,    end_revision.value.number = log_entry->revision;    SVN_ERR(svn_stream_puts(outstream, "\n")); -  SVN_ERR(svn_client_diff_peg6(diff_options, +  SVN_ERR(svn_client_diff_peg7(diff_options,                                 target_path_or_url,                                 target_peg_revision,                                 &start_revision, &end_revision, @@ -102,6 +102,7 @@ display_diff(const svn_log_entry_t *log_entry,                                 FALSE /* ignore prop diff */,                                 FALSE /* properties only */,                                 FALSE /* use git diff format */, +                               TRUE  /* pretty_print_mergeinfo */,                                 svn_cmdline_output_encoding(pool),                                 outstream,                                 errstream, @@ -333,7 +334,7 @@ svn_cl__log_entry_receiver(void *baton,        return SVN_NO_ERROR;      } -  /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=807 +  /* ### See https://issues.apache.org/jira/browse/SVN-807       for more on the fallback fuzzy conversions below. */    if (author == NULL) @@ -732,10 +733,6 @@ svn_cl__log(apr_getopt_t *os,                                    "XML mode"));      } -  if (opt_state->quiet && opt_state->show_diff) -    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, -                            _("'quiet' and 'diff' options are " -                              "mutually exclusive"));    if (opt_state->diff.diff_cmd && (! opt_state->show_diff))      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,                              _("'diff-cmd' option requires 'diff' " diff --git a/subversion/svn/merge-cmd.c b/subversion/svn/merge-cmd.c index f5c19198a5b29..140a39fad73f7 100644 --- a/subversion/svn/merge-cmd.c +++ b/subversion/svn/merge-cmd.c @@ -545,27 +545,31 @@ retry:                   "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) +  if (! opt_state->dry_run)      { -      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) +      /* 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_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_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_clear(merge_err); -              goto retry; /* ### conflicts resolved; continue merging */ +              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 */ +                }              }          }      } diff --git a/subversion/svn/notify.c b/subversion/svn/notify.c index c15301e32b8bf..ea0cc15945b10 100644 --- a/subversion/svn/notify.c +++ b/subversion/svn/notify.c @@ -210,7 +210,7 @@ svn_cl__conflict_stats_get_paths(apr_array_header_t **conflicted_paths,          }      } -  svn_hash_keys(conflicted_paths, all_conflicts, result_pool); +  SVN_ERR(svn_hash_keys(conflicted_paths, all_conflicts, result_pool));    svn_sort__array(*conflicted_paths, svn_sort_compare_paths);    return SVN_NO_ERROR; diff --git a/subversion/svn/propset-cmd.c b/subversion/svn/propset-cmd.c index 05087069005ae..e7a818386be74 100644 --- a/subversion/svn/propset-cmd.c +++ b/subversion/svn/propset-cmd.c @@ -151,7 +151,7 @@ svn_cl__propset(apr_getopt_t *os,         * must always be explicitly provided when setting a versioned         * property.  See         * -       *    http://subversion.tigris.org/issues/show_bug.cgi?id=924 +       *    https://issues.apache.org/jira/browse/SVN-924         *         * for more details.         */ diff --git a/subversion/svn/resolve-cmd.c b/subversion/svn/resolve-cmd.c index bbf9ff548ef5a..efd176dc7cdd0 100644 --- a/subversion/svn/resolve-cmd.c +++ b/subversion/svn/resolve-cmd.c @@ -109,7 +109,7 @@ svn_cl__walk_conflicts(apr_array_header_t *targets,        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)); diff --git a/subversion/svn/revert-cmd.c b/subversion/svn/revert-cmd.c index 74d7ff185635a..2ff35560e5000 100644 --- a/subversion/svn/revert-cmd.c +++ b/subversion/svn/revert-cmd.c @@ -67,10 +67,11 @@ svn_cl__revert(apr_getopt_t *os,    SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); -  err = svn_client_revert3(targets, opt_state->depth, +  err = svn_client_revert4(targets, opt_state->depth,                             opt_state->changelists,                             FALSE /* clear_changelists */,                             FALSE /* metadata_only */, +                           !opt_state->remove_added /*added_keep_local*/,                             ctx, scratch_pool);    if (err        && (err->apr_err == SVN_ERR_WC_INVALID_OPERATION_DEPTH) diff --git a/subversion/svn/shelf-cmd.c b/subversion/svn/shelf-cmd.c new file mode 100644 index 0000000000000..db0675b680776 --- /dev/null +++ b/subversion/svn/shelf-cmd.c @@ -0,0 +1,1405 @@ +/* + * shelf-cmd.c -- Shelving 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_hash.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_pools.h" +#include "svn_utf.h" + +#include "shelf-cmd.h" +#include "cl.h" + +#include "svn_private_config.h" +#include "private/svn_sorts_private.h" +#include "private/svn_client_private.h" +#include "private/svn_client_shelf.h" + + +/* Open the newest version of SHELF; error if no versions found. */ +static svn_error_t * +get_newest_version_existing(svn_client__shelf_version_t **shelf_version_p, +                            svn_client__shelf_t *shelf, +                            apr_pool_t *result_pool, +                            apr_pool_t *scratch_pool) +{ +  SVN_ERR(svn_client__shelf_get_newest_version(shelf_version_p, shelf, +                                              result_pool, scratch_pool)); +  if (!*shelf_version_p) +    { +      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                               _("Shelf '%s': no versions found"), +                               shelf->name); +    } + +  return SVN_NO_ERROR; +} + +/* Fetch the next argument. */ +static svn_error_t * +get_next_argument(const char **arg, +                  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(arg, +                                  APR_ARRAY_IDX(args, 0, const char *), +                                  result_pool)); +  return SVN_NO_ERROR; +} + +/* Parse the remaining arguments as paths relative to a WC. + * + * TARGETS are relative to current working directory. + * + * Set *targets_by_wcroot to a hash mapping (char *)wcroot_abspath to + * (apr_array_header_t *)array of relpaths relative to that WC root. + */ +static svn_error_t * +targets_relative_to_wcs(apr_hash_t **targets_by_wcroot_p, +                        apr_array_header_t *targets, +                        svn_client_ctx_t *ctx, +                        apr_pool_t *result_pool, +                        apr_pool_t *scratch_pool) +{ +  apr_hash_t *targets_by_wcroot = apr_hash_make(result_pool); +  int i; + +  /* Make each target relative to the WC root. */ +  for (i = 0; i < targets->nelts; i++) +    { +      const char *target = APR_ARRAY_IDX(targets, i, const char *); +      const char *wcroot_abspath; +      apr_array_header_t *paths; + +      SVN_ERR(svn_dirent_get_absolute(&target, target, result_pool)); +      SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, target, +                                     ctx, result_pool, scratch_pool)); +      paths = svn_hash_gets(targets_by_wcroot, wcroot_abspath); +      if (! paths) +        { +          paths = apr_array_make(result_pool, 0, sizeof(char *)); +          svn_hash_sets(targets_by_wcroot, wcroot_abspath, paths); +        } +      target = svn_dirent_skip_ancestor(wcroot_abspath, target); + +      if (target) +        APR_ARRAY_PUSH(paths, const char *) = target; +    } +  *targets_by_wcroot_p = targets_by_wcroot; +  return SVN_NO_ERROR; +} + +/* Return targets relative to a WC. Error if they refer to more than one WC. */ +static svn_error_t * +targets_relative_to_a_wc(const char **wc_root_abspath_p, +                         apr_array_header_t **paths_p, +                         apr_getopt_t *os, +                         const apr_array_header_t *known_targets, +                         svn_client_ctx_t *ctx, +                         apr_pool_t *result_pool, +                         apr_pool_t *scratch_pool) +{ +  apr_array_header_t *targets; +  apr_hash_t *targets_by_wcroot; +  apr_hash_index_t *hi; + +  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, +                                                      known_targets, +                                                      ctx, FALSE, result_pool)); +  svn_opt_push_implicit_dot_target(targets, result_pool); + +  SVN_ERR(targets_relative_to_wcs(&targets_by_wcroot, targets, +                                  ctx, result_pool, scratch_pool)); +  if (apr_hash_count(targets_by_wcroot) != 1) +    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, +                            _("All targets must be in the same WC")); + +  hi = apr_hash_first(scratch_pool, targets_by_wcroot); +  *wc_root_abspath_p = apr_hash_this_key(hi); +  *paths_p = apr_hash_this_val(hi); +  return SVN_NO_ERROR; +} + +/* Return a human-friendly description of DURATION. + */ +static char * +friendly_age_str(apr_time_t mtime, +                 apr_time_t time_now, +                 apr_pool_t *result_pool) +{ +  int minutes = (int)((time_now - mtime) / 1000000 / 60); +  char *s; + +  if (minutes >= 60 * 24) +    s = apr_psprintf(result_pool, +                     Q_("%d day ago", "%d days ago", +                        minutes / 60 / 24), +                     minutes / 60 / 24); +  else if (minutes >= 60) +    s = apr_psprintf(result_pool, +                     Q_("%d hour ago", "%d hours ago", +                        minutes / 60), +                     minutes / 60); +  else +    s = apr_psprintf(result_pool, +                     Q_("%d minute ago", "%d minutes ago", +                        minutes), +                     minutes); +  return s; +} + +/* A comparison function for svn_sort__hash(), comparing the mtime of two +   svn_client_shelf_info_t's. */ +static int +compare_shelf_infos_by_mtime(const svn_sort__item_t *a, +                             const svn_sort__item_t *b) +{ +  svn_client__shelf_info_t *a_val = a->value; +  svn_client__shelf_info_t *b_val = b->value; + +  return (a_val->mtime < b_val->mtime) +    ? -1 : (a_val->mtime > b_val->mtime) ? 1 : 0; +} + +/* Return a list of shelves sorted by their 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 *shelf_infos; + +  SVN_ERR(svn_client__shelf_list(&shelf_infos, local_abspath, +                                ctx, scratch_pool, scratch_pool)); +  *list = svn_sort__hash(shelf_infos, +                         compare_shelf_infos_by_mtime, +                         scratch_pool); +  return SVN_NO_ERROR; +} + +/*  */ +static svn_error_t * +stats(svn_client__shelf_t *shelf, +      int version, +      svn_client__shelf_version_t *shelf_version, +      apr_time_t time_now, +      svn_boolean_t with_logmsg, +      apr_pool_t *scratch_pool) +{ +  char *age_str; +  char *version_str; +  apr_hash_t *paths; +  const char *paths_str = ""; + +  if (! shelf_version) +    { +      return SVN_NO_ERROR; +    } + +  age_str = friendly_age_str(shelf_version->mtime, time_now, scratch_pool); +  if (version == shelf->max_version) +    version_str = apr_psprintf(scratch_pool, +                               _("version %d"), version); +  else +    version_str = apr_psprintf(scratch_pool, +                               Q_("version %d of %d", "version %d of %d", +                                  shelf->max_version), +                               version, shelf->max_version); +  SVN_ERR(svn_client__shelf_paths_changed(&paths, shelf_version, +                                         scratch_pool, scratch_pool)); +  paths_str = apr_psprintf(scratch_pool, +                           Q_("%d path changed", "%d paths changed", +                              apr_hash_count(paths)), +                           apr_hash_count(paths)); +  SVN_ERR(svn_cmdline_printf(scratch_pool, +                             "%-30s %s, %s, %s\n", +                             shelf->name, version_str, age_str, paths_str)); + +  if (with_logmsg) +    { +      char *log_message; + +      SVN_ERR(svn_client__shelf_get_log_message(&log_message, shelf, +                                               scratch_pool)); +      if (log_message) +        { +          SVN_ERR(svn_cmdline_printf(scratch_pool, +                                     _(" %.50s\n"), +                                     log_message)); +        } +    } + +  return SVN_NO_ERROR; +} + +/* Display a list of shelves */ +static svn_error_t * +shelves_list(const char *local_abspath, +             svn_boolean_t quiet, +             svn_client_ctx_t *ctx, +             apr_pool_t *scratch_pool) +{ +  apr_time_t time_now = apr_time_now(); +  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__shelf_t *shelf; +      svn_client__shelf_version_t *shelf_version; + +      SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath, +                                             ctx, scratch_pool)); +      SVN_ERR(svn_client__shelf_get_newest_version(&shelf_version, shelf, +                                                  scratch_pool, scratch_pool)); +      if (quiet) +        SVN_ERR(svn_cmdline_printf(scratch_pool, "%s\n", shelf->name)); +      else if (!shelf_version) +        SVN_ERR(svn_cmdline_printf(scratch_pool, "%-30s no versions\n", +                                   shelf->name)); +      else +        SVN_ERR(stats(shelf, shelf->max_version, shelf_version, time_now, +                      TRUE /*with_logmsg*/, scratch_pool)); +      SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); +    } + +  return SVN_NO_ERROR; +} + +/* Print info about each checkpoint of the shelf named NAME. + */ +static svn_error_t * +shelf_log(const char *name, +          const char *local_abspath, +          svn_client_ctx_t *ctx, +          apr_pool_t *scratch_pool) +{ +  apr_time_t time_now = apr_time_now(); +  svn_client__shelf_t *shelf; +  apr_array_header_t *versions; +  int i; + +  SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath, +                                         ctx, scratch_pool)); +  SVN_ERR(svn_client__shelf_get_all_versions(&versions, shelf, +                                            scratch_pool, scratch_pool)); +  for (i = 0; i < versions->nelts; i++) +    { +      svn_client__shelf_version_t *shelf_version +        = APR_ARRAY_IDX(versions, i, void *); + +      SVN_ERR(stats(shelf, i + 1, shelf_version, time_now, +                    FALSE /*with_logmsg*/, scratch_pool)); +    } + +  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +/* Find the name of the youngest shelf. + */ +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 shelves found")); + +  youngest_item = &APR_ARRAY_IDX(list, list->nelts - 1, svn_sort__item_t); +  *name_p = apr_pstrdup(result_pool, youngest_item->key); +  return SVN_NO_ERROR; +} + +struct status_baton +{ +  /* These fields correspond to the ones in the +     svn_cl__print_status() interface. */ +  const char *target_abspath; +  const char *target_path; + +  svn_boolean_t quiet;  /* don't display statuses while shelving them */ +  int num_paths_shelved; +  int num_paths_not_shelved; +  svn_client_ctx_t *ctx; +}; + +/* A status callback function for printing STATUS for PATH. */ +static svn_error_t * +print_status(void *baton, +             const char *path, +             const svn_client_status_t *status, +             apr_pool_t *scratch_pool) +{ +  struct status_baton *sb = baton; +  unsigned int conflicts; + +  return svn_cl__print_status(sb->target_abspath, sb->target_path, +                              path, status, +                              TRUE /*suppress_externals_placeholders*/, +                              FALSE /*detailed*/, +                              FALSE /*show_last_committed*/, +                              TRUE /*skip_unrecognized*/, +                              FALSE /*repos_locks*/, +                              &conflicts, &conflicts, &conflicts, +                              sb->ctx, +                              scratch_pool); +} + +/* A callback function for shelved paths. */ +static svn_error_t * +was_shelved(void *baton, +            const char *path, +            const svn_client_status_t *status, +            apr_pool_t *scratch_pool) +{ +  struct status_baton *sb = baton; + +  if (!sb->quiet) +    { +      SVN_ERR(print_status(baton, path, status, scratch_pool)); +    } + +  ++sb->num_paths_shelved; +  return SVN_NO_ERROR; +} + +/* A callback function for not-shelved paths. */ +static svn_error_t * +was_not_shelved(void *baton, +                const char *path, +                const svn_client_status_t *status, +                apr_pool_t *scratch_pool) +{ +  struct status_baton *sb = baton; + +  SVN_ERR(print_status(baton, path, status, scratch_pool)); +  SVN_ERR(svn_cmdline_printf(scratch_pool, "      >   not shelved\n")); +  ++sb->num_paths_not_shelved; +  return SVN_NO_ERROR; +} + +/** Shelve/save a new version of changes. + * + * Shelve in shelf @a name the local modifications found by @a paths, + * @a depth, @a changelists. Revert the shelved changes from the WC + * unless @a keep_local is true. + * + * If no local modifications are found, throw an error. + * + * If @a dry_run is true, don't actually do it. + * + * Report in @a *new_version_p the new version number (or, with dry run, + * what it would be). + */ +static svn_error_t * +shelve(int *new_version_p, +       const char *name, +       const apr_array_header_t *paths, +       svn_depth_t depth, +       const apr_array_header_t *changelists, +       apr_hash_t *revprop_table, +       svn_boolean_t keep_local, +       svn_boolean_t dry_run, +       svn_boolean_t quiet, +       const char *local_abspath, +       svn_client_ctx_t *ctx, +       apr_pool_t *scratch_pool) +{ +  svn_client__shelf_t *shelf; +  svn_client__shelf_version_t *previous_version; +  svn_client__shelf_version_t *new_version; +  struct status_baton sb; + +  SVN_ERR(svn_client__shelf_open_or_create(&shelf, +                                          name, local_abspath, +                                          ctx, scratch_pool)); +  SVN_ERR(svn_client__shelf_get_newest_version(&previous_version, shelf, +                                              scratch_pool, scratch_pool)); + +  if (! quiet) +    { +      SVN_ERR(svn_cmdline_printf(scratch_pool, keep_local +                                 ? _("--- Save a new version of '%s' in WC root '%s'\n") +                                 : _("--- Shelve '%s' in WC root '%s'\n"), +                                 shelf->name, shelf->wc_root_abspath)); +      SVN_ERR(stats(shelf, shelf->max_version, previous_version, apr_time_now(), +                    TRUE /*with_logmsg*/, scratch_pool)); +    } + +  sb.target_abspath = shelf->wc_root_abspath; +  sb.target_path = ""; +  sb.quiet = quiet; +  sb.num_paths_shelved = 0; +  sb.num_paths_not_shelved = 0; +  sb.ctx = ctx; + +  if (! quiet) +    SVN_ERR(svn_cmdline_printf(scratch_pool, +                               keep_local ? _("--- Saving...\n") +                               : _("--- Shelving...\n"))); +  SVN_ERR(svn_client__shelf_save_new_version3(&new_version, shelf, +                                             paths, depth, changelists, +                                             was_shelved, &sb, +                                             was_not_shelved, &sb, +                                             scratch_pool)); +  if (sb.num_paths_not_shelved > 0) +    { +      SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, previous_version, +                                                     scratch_pool)); +      SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); +      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                               Q_("%d path could not be shelved", +                                  "%d paths could not be shelved", +                                  sb.num_paths_not_shelved), +                               sb.num_paths_not_shelved); +    } +  if (sb.num_paths_shelved == 0 +      || ! new_version) +    { +      SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); +      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                               keep_local ? _("No local modifications could be saved") +                               : _("No local modifications could be shelved")); +    } + +  /* Un-apply the changes, if required. */ +  if (!keep_local) +    { +      SVN_ERR(svn_client__shelf_unapply(new_version, +                                       dry_run, scratch_pool)); +    } + +  /* Fetch the log message and any other revprops */ +  if (ctx->log_msg_func3) +    { +      const char *tmp_file; +      apr_array_header_t *commit_items +        = apr_array_make(scratch_pool, 1, sizeof(void *)); +      const char *message = ""; + +      SVN_ERR(ctx->log_msg_func3(&message, &tmp_file, commit_items, +                                 ctx->log_msg_baton3, scratch_pool)); +      /* Abort the shelving if the log message callback requested so. */ +      if (! message) +        return SVN_NO_ERROR; + +      if (message && !dry_run) +        { +          svn_string_t *propval = svn_string_create(message, scratch_pool); + +          if (! revprop_table) +            revprop_table = apr_hash_make(scratch_pool); +          svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, propval); +        } +    } + +  SVN_ERR(svn_client__shelf_revprop_set_all(shelf, revprop_table, scratch_pool)); + +  if (new_version_p) +    *new_version_p = shelf->max_version; + +  if (dry_run) +    { +      SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, previous_version, +                                                     scratch_pool)); +    } + +  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +/* Return the single character representation of STATUS. + * (Similar to subversion/svn/status.c:generate_status_code() + * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */ +static char +status_to_char(enum svn_wc_status_kind status) +{ +  switch (status) +    { +    case svn_wc_status_none:        return '.'; +    case svn_wc_status_unversioned: return '?'; +    case svn_wc_status_normal:      return ' '; +    case svn_wc_status_added:       return 'A'; +    case svn_wc_status_missing:     return '!'; +    case svn_wc_status_deleted:     return 'D'; +    case svn_wc_status_replaced:    return 'R'; +    case svn_wc_status_modified:    return 'M'; +    case svn_wc_status_merged:      return 'G'; +    case svn_wc_status_conflicted:  return 'C'; +    case svn_wc_status_ignored:     return 'I'; +    case svn_wc_status_obstructed:  return '~'; +    case svn_wc_status_external:    return 'X'; +    case svn_wc_status_incomplete:  return ':'; +    default:                        return '*'; +    } +} + +/* Throw an error if any path affected by SHELF_VERSION gives a conflict + * when applied (as a dry-run) to the WC. */ +static svn_error_t * +test_apply(svn_client__shelf_version_t *shelf_version, +           svn_client_ctx_t *ctx, +           apr_pool_t *scratch_pool) +{ +  apr_hash_t *paths; +  apr_hash_index_t *hi; + +  SVN_ERR(svn_client__shelf_paths_changed(&paths, shelf_version, +                                         scratch_pool, scratch_pool)); +  for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi)) +    { +      const char *path = apr_hash_this_key(hi); +      svn_boolean_t conflict; + +      SVN_ERR(svn_client__shelf_test_apply_file(&conflict, shelf_version, path, +                                               scratch_pool)); +      if (conflict) +        { +          char *to_wc_abspath +            = svn_dirent_join(shelf_version->shelf->wc_root_abspath, path, +                              scratch_pool); +          svn_wc_status3_t *status; + +          SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, to_wc_abspath, +                                 scratch_pool, scratch_pool)); +          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                                   _("Shelved path '%s' already has " +                                     "status '%c' in the working copy"), +                                   path, status_to_char(status->node_status)); +        } +    } +  return SVN_NO_ERROR; +} + +/** Restore/unshelve a given or newest version of changes. + * + * Restore local modifications from shelf @a name version @a arg, + * or the newest version is @a arg is null. + * + * If @a dry_run is true, don't actually do it. + * + * Error if any path would have a conflict, unless @a force_if_conflict. + */ +static svn_error_t * +shelf_restore(const char *name, +              const char *arg, +              svn_boolean_t dry_run, +              svn_boolean_t quiet, +              svn_boolean_t force_if_conflict, +              const char *local_abspath, +              svn_client_ctx_t *ctx, +              apr_pool_t *scratch_pool) +{ +  int version, old_version; +  apr_time_t time_now = apr_time_now(); +  svn_client__shelf_t *shelf; +  svn_client__shelf_version_t *shelf_version; + +  SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath, +                                         ctx, scratch_pool)); + +  old_version = shelf->max_version; +  if (arg) +    { +      SVN_ERR(svn_cstring_atoi(&version, arg)); +      SVN_ERR(svn_client__shelf_version_open(&shelf_version, +                                            shelf, version, +                                            scratch_pool, scratch_pool)); +    } +  else +    { +      version = shelf->max_version; +      SVN_ERR(get_newest_version_existing(&shelf_version, shelf, +                                          scratch_pool, scratch_pool)); +    } + +  if (! quiet) +    { +      SVN_ERR(svn_cmdline_printf(scratch_pool, +                                 _("--- Unshelve '%s' in WC root '%s'\n"), +                                 shelf->name, shelf->wc_root_abspath)); +      SVN_ERR(stats(shelf, version, shelf_version, time_now, +                    TRUE /*with_logmsg*/, scratch_pool)); +    } +  if (! force_if_conflict) +    { +      SVN_ERR_W(test_apply(shelf_version, ctx, scratch_pool), +                _("Cannot unshelve/restore, as at least one shelved " +                  "path would conflict with a local modification " +                  "or other status in the working copy")); +    } + +  SVN_ERR(svn_client__shelf_apply(shelf_version, +                                 dry_run, scratch_pool)); + +  if (! dry_run) +    { +      SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, shelf_version, +                                                     scratch_pool)); +    } + +  if (!quiet) +    { +      if (version < old_version) +        SVN_ERR(svn_cmdline_printf(scratch_pool, +                                   Q_("restored '%s' version %d and deleted %d newer version\n", +                                      "restored '%s' version %d and deleted %d newer versions\n", +                                      old_version - version), +                                   name, version, old_version - version)); +      else +        SVN_ERR(svn_cmdline_printf(scratch_pool, +                                   _("restored '%s' version %d (the newest version)\n"), +                                   name, version)); +    } + +  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +static svn_error_t * +shelf_diff(const char *name, +           const char *arg, +           const char *local_abspath, +           svn_boolean_t summarize, +           svn_depth_t depth, +           svn_boolean_t ignore_ancestry, +           svn_client_ctx_t *ctx, +           apr_pool_t *scratch_pool) +{ +  svn_client__shelf_t *shelf; +  svn_client__shelf_version_t *shelf_version; +  svn_stream_t *stream, *errstream; +  svn_diff_tree_processor_t *diff_processor; + +  SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath, +                                         ctx, scratch_pool)); + +  if (arg) +    { +      int version; + +      SVN_ERR(svn_cstring_atoi(&version, arg)); +      SVN_ERR(svn_client__shelf_version_open(&shelf_version, +                                            shelf, version, +                                            scratch_pool, scratch_pool)); +    } +  else +    { +      SVN_ERR(get_newest_version_existing(&shelf_version, shelf, +                                          scratch_pool, scratch_pool)); +    } + +  SVN_ERR(svn_stream_for_stdout(&stream, scratch_pool)); +  errstream = svn_stream_empty(scratch_pool); + +  if (summarize) +    { +      svn_client_diff_summarize_func_t func; +      void *baton; + +      SVN_ERR(svn_cl__get_diff_summary_writer(&func, &baton, +                                              FALSE /*xml*/, +                                              FALSE /*ignore_properties*/, +                                              "" /*anchor/prefix*/, +                                              scratch_pool, scratch_pool)); +      SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, +                                                       func, baton, +                                                       scratch_pool, +                                                       scratch_pool)); +    } +  else +    { +      SVN_ERR(svn_client__get_diff_writer_svn( +                &diff_processor, +                NULL /*anchor*/, +                "", "", /*orig_path_1, orig_path_2,*/ +                NULL /*options*/, +                "" /*relative_to_dir*/, +                FALSE /*no_diff_added*/, +                FALSE /*no_diff_deleted*/, +                FALSE /*show_copies_as_adds*/, +                FALSE /*ignore_content_type*/, +                FALSE /*ignore_properties*/, +                FALSE /*properties_only*/, +                TRUE /*pretty_print_mergeinfo*/, +                svn_cmdline_output_encoding(scratch_pool), +                stream, errstream, +                ctx, scratch_pool)); +    } + +  SVN_ERR(svn_client__shelf_diff(shelf_version, "", +                                 depth, ignore_ancestry, +                                 diff_processor, scratch_pool)); +  SVN_ERR(svn_stream_close(stream)); + +  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +shelf_drop(const char *name, +           const char *local_abspath, +           svn_boolean_t dry_run, +           svn_boolean_t quiet, +           svn_client_ctx_t *ctx, +           apr_pool_t *scratch_pool) +{ +  SVN_ERR(svn_client__shelf_delete(name, local_abspath, dry_run, +                                  ctx, scratch_pool)); +  if (! quiet) +    SVN_ERR(svn_cmdline_printf(scratch_pool, +                               _("deleted '%s'\n"), +                               name)); +  return SVN_NO_ERROR; +} + +/*  */ +static svn_error_t * +shelf_shelve(int *new_version, +             const char *name, +             apr_array_header_t *targets, +             svn_depth_t depth, +             apr_array_header_t *changelists, +             apr_hash_t *revprop_table, +             svn_boolean_t keep_local, +             svn_boolean_t dry_run, +             svn_boolean_t quiet, +             svn_client_ctx_t *ctx, +             apr_pool_t *scratch_pool) +{ +  const char *local_abspath; + +  if (depth == svn_depth_unknown) +    depth = svn_depth_infinity; + +  SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + +  SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + +  svn_opt_push_implicit_dot_target(targets, scratch_pool); + +  /* ### TODO: check all paths are in same WC; for now use first path */ +  SVN_ERR(svn_dirent_get_absolute(&local_abspath, +                                  APR_ARRAY_IDX(targets, 0, char *), +                                  scratch_pool)); + +  SVN_ERR(shelve(new_version, name, +                 targets, depth, changelists, +                 revprop_table, +                 keep_local, dry_run, quiet, +                 local_abspath, ctx, scratch_pool)); + +  return SVN_NO_ERROR; +} + +static svn_error_t * +svn_cl__shelf_shelve(apr_getopt_t *os, +                     void *baton, +                     apr_pool_t *pool); + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_save(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; + +  opt_state->keep_local = TRUE; +  SVN_ERR(svn_cl__shelf_shelve(os, baton, pool)); +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_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 *name; +  apr_array_header_t *targets; + +  if (opt_state->quiet) +    ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */ + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  /* Parse the remaining arguments as paths. */ +  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, +                                                      opt_state->targets, +                                                      ctx, FALSE, pool)); +  { +    int new_version; +    svn_error_t *err; + +    if (ctx->log_msg_func3) +      SVN_ERR(svn_cl__make_log_msg_baton(&ctx->log_msg_baton3, +                                         opt_state, NULL, ctx->config, +                                         pool)); +    err = shelf_shelve(&new_version, name, +                       targets, opt_state->depth, opt_state->changelists, +                       opt_state->revprop_table, +                       opt_state->keep_local, opt_state->dry_run, +                       opt_state->quiet, 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 (! opt_state->quiet) +      { +        if (opt_state->keep_local) +          SVN_ERR(svn_cmdline_printf(pool, +                                     _("saved '%s' version %d\n"), +                                     name, new_version)); +        else +          SVN_ERR(svn_cmdline_printf(pool, +                                     _("shelved '%s' version %d\n"), +                                     name, new_version)); +      } +  } + +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_unshelve(apr_getopt_t *os, +                       void *baton, +                       apr_pool_t *scratch_pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  const char *local_abspath; +  const char *name; +  const char *arg = NULL; + +  SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", scratch_pool)); + +  if (os->ind < os->argc) +    { +      SVN_ERR(get_next_argument(&name, os, scratch_pool, scratch_pool)); +    } +  else +    { +      SVN_ERR(name_of_youngest(&name, +                               local_abspath, ctx, scratch_pool, scratch_pool)); +      SVN_ERR(svn_cmdline_printf(scratch_pool, +                                 _("unshelving the youngest shelf, '%s'\n"), +                                 name)); +    } + +  /* Which checkpoint number? */ +  if (os->ind < os->argc) +    SVN_ERR(get_next_argument(&arg, os, scratch_pool, scratch_pool)); + +  if (os->ind < os->argc) +    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +                            _("Too many arguments")); + +  if (opt_state->quiet) +    ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */ + +  SVN_ERR(shelf_restore(name, arg, +                        opt_state->dry_run, opt_state->quiet, +                        opt_state->force /*force_already_modified*/, +                        local_abspath, ctx, scratch_pool)); + +  if (opt_state->drop) +    { +      SVN_ERR(shelf_drop(name, local_abspath, +                         opt_state->dry_run, opt_state->quiet, +                         ctx, scratch_pool)); +    } +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_list(apr_getopt_t *os, +                   void *baton, +                   apr_pool_t *pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  apr_array_header_t *targets = NULL; +  apr_pool_t *iterpool = svn_pool_create(pool); +  int i; + +  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, +                                                      opt_state->targets, +                                                      ctx, FALSE, pool)); +  /* Add "." if user passed 0 arguments */ +  svn_opt_push_implicit_dot_target(targets, pool); + +  for (i = 0; i < targets->nelts; ++i) +    { +      const char *local_abspath; +      const char *target = APR_ARRAY_IDX(targets, i, const char *); + +      svn_pool_clear(iterpool); + +      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); + +      SVN_ERR(shelves_list(local_abspath, +                           opt_state->quiet, +                           ctx, iterpool)); +    } + +  svn_pool_destroy(iterpool); + +  return SVN_NO_ERROR; +} + +/* "svn shelf-list-by-paths [PATH...]" + * + * TARGET_RELPATHS are all within the same WC, relative to WC_ROOT_ABSPATH. + */ +static svn_error_t * +shelf_list_by_paths(apr_array_header_t *target_relpaths, +                    const char *wc_root_abspath, +                    svn_client_ctx_t *ctx, +                    apr_pool_t *scratch_pool) +{ +  apr_array_header_t *shelves; +  apr_hash_t *paths_to_shelf_name = apr_hash_make(scratch_pool); +  apr_array_header_t *array; +  int i; + +  SVN_ERR(list_sorted_by_date(&shelves, +                              wc_root_abspath, ctx, scratch_pool)); + +  /* Check paths are valid */ +  for (i = 0; i < target_relpaths->nelts; i++) +    { +      char *target_relpath = APR_ARRAY_IDX(target_relpaths, i, char *); + +      if (svn_path_is_url(target_relpath)) +        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                                 _("'%s' is not a local path"), target_relpath); +      SVN_ERR_ASSERT(svn_relpath_is_canonical(target_relpath)); +    } + +  /* Find the most recent shelf for each affected path */ +  for (i = 0; i < shelves->nelts; i++) +    { +      svn_sort__item_t *item = &APR_ARRAY_IDX(shelves, i, svn_sort__item_t); +      const char *name = item->key; +      svn_client__shelf_t *shelf; +      svn_client__shelf_version_t *shelf_version; +      apr_hash_t *shelf_paths; +      int j; + +      SVN_ERR(svn_client__shelf_open_existing(&shelf, +                                              name, wc_root_abspath, +                                              ctx, scratch_pool)); +      SVN_ERR(svn_client__shelf_get_newest_version(&shelf_version, shelf, +                                                   scratch_pool, scratch_pool)); +      if (!shelf_version) +        continue; +      SVN_ERR(svn_client__shelf_paths_changed(&shelf_paths, +                                              shelf_version, +                                              scratch_pool, scratch_pool)); +      for (j = 0; j < target_relpaths->nelts; j++) +        { +          char *target_relpath = APR_ARRAY_IDX(target_relpaths, j, char *); +          apr_hash_index_t *hi; + +          for (hi = apr_hash_first(scratch_pool, shelf_paths); +               hi; hi = apr_hash_next(hi)) +            { +              const char *shelf_path = apr_hash_this_key(hi); + +              if (svn_relpath_skip_ancestor(target_relpath, shelf_path)) +                { +                  if (! svn_hash_gets(paths_to_shelf_name, shelf_path)) +                    { +                      svn_hash_sets(paths_to_shelf_name, shelf_path, shelf->name); +                    } +                } +            } +        } +    } + +  /* Print the results. */ +  array = svn_sort__hash(paths_to_shelf_name, +                         svn_sort_compare_items_as_paths, +                         scratch_pool); +  for (i = 0; i < array->nelts; i++) +    { +      svn_sort__item_t *item = &APR_ARRAY_IDX(array, i, svn_sort__item_t); +      const char *path = item->key; +      const char *name = item->value; + +      SVN_ERR(svn_cmdline_printf(scratch_pool, "%-20.20s %s\n", +                                 name, +                                 svn_dirent_local_style(path, scratch_pool))); +    } +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_list_by_paths(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 *wc_root_abspath; +  apr_array_header_t *targets; + +  /* Parse the remaining arguments as paths. */ +  SVN_ERR(targets_relative_to_a_wc(&wc_root_abspath, &targets, +                                   os, opt_state->targets, +                                   ctx, pool, pool)); + +  SVN_ERR(shelf_list_by_paths(targets, wc_root_abspath, ctx, pool)); +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_diff(apr_getopt_t *os, +                   void *baton, +                   apr_pool_t *pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  const char *local_abspath; +  const char *name; +  const char *arg = NULL; + +  SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool)); + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  /* Which checkpoint number? */ +  if (os->ind < os->argc) +    SVN_ERR(get_next_argument(&arg, os, pool, pool)); + +  if (os->ind < os->argc) +    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +                            _("Too many arguments")); + +  SVN_ERR(shelf_diff(name, arg, local_abspath, +                     opt_state->diff.summarize, +                     opt_state->depth, opt_state->ignore_ancestry, +                     ctx, pool)); + +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_drop(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 *name; +  apr_array_header_t *targets = NULL; +  apr_pool_t *iterpool = svn_pool_create(pool); +  int i; + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  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); + +  for (i = 0; i < targets->nelts; ++i) +    { +      const char *local_abspath; +      const char *target = APR_ARRAY_IDX(targets, i, const char *); + +      svn_pool_clear(iterpool); + +      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); +      SVN_ERR(shelf_drop(name, local_abspath, +                         opt_state->dry_run, opt_state->quiet, +                         ctx, iterpool)); +    } + +  svn_pool_destroy(iterpool); + +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_log(apr_getopt_t *os, +                  void *baton, +                  apr_pool_t *pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  const char *name; +  apr_array_header_t *targets = NULL; +  apr_pool_t *iterpool = svn_pool_create(pool); +  int i; + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  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); + +  for (i = 0; i < targets->nelts; ++i) +    { +      const char *local_abspath; +      const char *target = APR_ARRAY_IDX(targets, i, const char *); + +      svn_pool_clear(iterpool); + +      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); +      SVN_ERR(shelf_log(name, local_abspath, ctx, iterpool)); +    } + +  svn_pool_destroy(iterpool); + +  return SVN_NO_ERROR; +} + +/**************************************************************************/ + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__wc_copy_mods(apr_getopt_t *os, +                     void *baton, +                     apr_pool_t *pool) +{ +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  const char *src_wc_abspath, *dst_wc_abspath; + +  SVN_ERR(get_next_argument(&src_wc_abspath, os, pool, pool)); +  SVN_ERR(svn_dirent_get_absolute(&src_wc_abspath, src_wc_abspath, pool)); + +  SVN_ERR(get_next_argument(&dst_wc_abspath, os, pool, pool)); +  SVN_ERR(svn_dirent_get_absolute(&dst_wc_abspath, dst_wc_abspath, pool)); + +  SVN_ERR(svn_client__wc_copy_mods(src_wc_abspath, dst_wc_abspath, +                                   ctx->notify_func2, ctx->notify_baton2, +                                   ctx, pool)); + +  return SVN_NO_ERROR; +} + +const svn_opt_subcommand_desc3_t +svn_cl__cmd_table_shelf3[] = +{ +  { "x-shelf-diff", svn_cl__shelf_diff, {0}, {N_( +     "Show shelved changes as a diff.\n" +     "usage: x-shelf-diff SHELF [VERSION]\n" +     "\n"), N_( +     "  Show the changes in SHELF:VERSION (default: latest) as a diff.\n" +     "\n"), N_( +     "  See also: 'svn diff --cl=svn:shelf:SHELF' which supports most options of\n" +     "  'svn diff'.\n" +     "\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_summarize}, +  }, + +  { "x-shelf-drop", svn_cl__shelf_drop, {0}, {N_( +     "Delete a shelf.\n" +     "usage: x-shelf-drop SHELF [PATH ...]\n" +     "\n"), N_( +     "  Delete the shelves named SHELF from the working copies containing PATH\n" +     "  (default PATH is '.')\n" +     "\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" +    )}, +  }, + +  { "x-shelf-list", svn_cl__shelf_list, {"x-shelves"}, {N_( +     "List shelves.\n" +     "usage: x-shelf-list [PATH ...]\n" +     "\n"), N_( +     "  List shelves for each working copy containing PATH (default is '.')\n" +     "  Include the first line of any log message and some details about the\n" +     "  contents of the shelf, unless '-q' is given.\n" +     "\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', 'v'} +  }, + +  { "x-shelf-list-by-paths", svn_cl__shelf_list_by_paths, {0}, {N_( +     "List which shelf affects each path.\n" +     "usage: x-shelf-list-by-paths [PATH...]\n" +     "\n"), N_( +     "  List which shelf most recently affects each path below the given PATHs.\n" +     "\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" +    )}, +  }, + +  { "x-shelf-log", svn_cl__shelf_log, {0}, {N_( +     "Show the versions of a shelf.\n" +     "usage: x-shelf-log SHELF [PATH...]\n" +     "\n"), N_( +     "  Show all versions of SHELF for each working copy containing PATH (the\n" +     "  default PATH is '.').\n" +     "\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', 'v'} +  }, + +  { "x-shelf-save", svn_cl__shelf_save, {0}, {N_( +     "Copy local changes onto a new version of a shelf.\n" +     "usage: x-shelf-save SHELF [PATH...]\n" +     "\n"), N_( +     "  Save local changes in the given PATHs as a new version of SHELF.\n" +     "  The shelf's log message can be set with -m, -F, etc.\n" +     "\n"), N_( +     "  The same as 'svn shelve --keep-local'.\n" +     "\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', opt_dry_run, +     opt_depth, opt_targets, opt_changelist, +     SVN_CL__LOG_MSG_OPTIONS, +    } +  }, + +  { "x-shelve", svn_cl__shelf_shelve, {0}, {N_( +     "Move local changes onto a shelf.\n" +     "usage: x-shelve [--keep-local] SHELF [PATH...]\n" +     "\n"), N_( +     "  Save the local changes in the given PATHs to a new or existing SHELF.\n" +     "  Revert those changes from the WC unless '--keep-local' is given.\n" +     "  The shelf's log message can be set with -m, -F, etc.\n" +     "\n"), N_( +     "  'svn shelve --keep-local' is the same as 'svn shelf-save'.\n" +     "\n"), N_( +     "  The kinds of change you can shelve are committable changes to files and\n" +     "  properties, except the following kinds which are not yet supported:\n" +     "     * copies and moves\n" +     "     * mkdir and rmdir\n" +     "  Uncommittable states such as conflicts, unversioned and missing cannot\n" +     "  be shelved.\n" +     "\n"), N_( +     "  To bring back shelved changes, use 'svn unshelve SHELF'.\n" +     "\n"), N_( +     "  Shelves are currently stored under <WC>/.svn/experimental/shelves/ .\n" +     "  (In Subversion 1.10, shelves were stored under <WC>/.svn/shelves/ as\n" +     "  patch files. To recover a shelf created by 1.10, either use a 1.10\n" +     "  client to find and unshelve it, or find the patch file and use any\n" +     "  1.10 or later 'svn patch' to apply it.)\n" +     "\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', opt_dry_run, opt_keep_local, +     opt_depth, opt_targets, opt_changelist, +     SVN_CL__LOG_MSG_OPTIONS, +    } }, + +  { "x-unshelve", svn_cl__shelf_unshelve, {0}, {N_( +     "Copy shelved changes back into the WC.\n" +     "usage: x-unshelve [--drop] [SHELF [VERSION]]\n" +     "\n"), N_( +     "  Apply the changes stored in SHELF to the working copy.\n" +     "  SHELF defaults to the newest shelf.\n" +     "\n"), N_( +     "  Apply the newest version of the shelf, by default. If VERSION is\n" +     "  specified, apply that version and discard all versions newer than that.\n" +     "  In any case, retain the unshelved version and versions older than that\n" +     "  (unless --drop is specified).\n" +     "\n"), N_( +     "  With --drop, delete the entire shelf (like 'svn shelf-drop') after\n" +     "  successfully unshelving with no conflicts.\n" +     "\n"), N_( +     "  The working files involved should be in a clean, unmodified state\n" +     "  before using this command. To roll back to an older version of the\n" +     "  shelf, first ensure any current working changes are removed, such as\n" +     "  by shelving or reverting them, and then unshelve the desired version.\n" +     "\n"), N_( +     "  Unshelve normally refuses to apply any changes if any path involved is\n" +     "  already modified (or has any other abnormal status) in the WC. With\n" +     "  --force, it does not check and may error out and/or produce partial or\n" +     "  unexpected results.\n" +     "\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_drop, 'q', opt_dry_run, opt_force} }, + +  { "x-wc-copy-mods", svn_cl__wc_copy_mods, {0}, {N_( +     "Copy local modifications from one WC to another.\n" +     "usage: x-wc-copy-mods SRC_WC_PATH DST_WC_PATH\n" +     "\n"), N_( +     "  The source and destination WC paths may be in the same WC or in different" +     "  WCs.\n" +     "\n"), N_( +     "  This feature is EXPERIMENTAL. This command is likely to change\n" +     "  in the next release, and there is no promise of backward compatibility.\n" +    )}, +  }, + +  { NULL, NULL, {0}, {NULL}, {0} } +}; + diff --git a/subversion/svn/shelf-cmd.h b/subversion/svn/shelf-cmd.h new file mode 100644 index 0000000000000..c5f3cabe60de4 --- /dev/null +++ b/subversion/svn/shelf-cmd.h @@ -0,0 +1,49 @@ +/* + * shelf-cmd.h:  experimental shelving v3 + * + * ==================================================================== + *    Licensed to the Apache Software Foundation (ASF) under one + *    or more contributor license agreements.  See the NOTICE file + *    distributed with this work for additional information + *    regarding copyright ownership.  The ASF licenses this file + *    to you under the Apache License, Version 2.0 (the + *    "License"); you may not use this file except in compliance + *    with the License.  You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + *    Unless required by applicable law or agreed to in writing, + *    software distributed under the License is distributed on an + *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + *    KIND, either express or implied.  See the License for the + *    specific language governing permissions and limitations + *    under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +#ifndef SVN_SHELF_CMD_H +#define SVN_SHELF_CMD_H + +/*** Includes. ***/ +#include <apr_getopt.h> + +#include "svn_opt.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +extern const svn_opt_subcommand_desc3_t svn_cl__cmd_table_shelf3[]; +/*extern const apr_getopt_option_t svn_cl__options_shelving3[];*/ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_SHELF_CMD_H */ diff --git a/subversion/svn/shelf2-cmd.c b/subversion/svn/shelf2-cmd.c new file mode 100644 index 0000000000000..8305ce30938eb --- /dev/null +++ b/subversion/svn/shelf2-cmd.c @@ -0,0 +1,1369 @@ +/* + * shelf2-cmd.c -- Shelving 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_hash.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_pools.h" +#include "svn_utf.h" + +#include "shelf2-cmd.h" +#include "cl.h" + +#include "svn_private_config.h" +#include "private/svn_sorts_private.h" +#include "private/svn_client_private.h" +#include "private/svn_client_shelf2.h" + + +/* Open the newest version of SHELF; error if no versions found. */ +static svn_error_t * +get_newest_version_existing(svn_client__shelf2_version_t **shelf_version_p, +                            svn_client__shelf2_t *shelf, +                            apr_pool_t *result_pool, +                            apr_pool_t *scratch_pool) +{ +  SVN_ERR(svn_client__shelf2_get_newest_version(shelf_version_p, shelf, +                                              result_pool, scratch_pool)); +  if (!*shelf_version_p) +    { +      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                               _("Shelf '%s': no versions found"), +                               shelf->name); +    } + +  return SVN_NO_ERROR; +} + +/* Fetch the next argument. */ +static svn_error_t * +get_next_argument(const char **arg, +                  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(arg, +                                  APR_ARRAY_IDX(args, 0, const char *), +                                  result_pool)); +  return SVN_NO_ERROR; +} + +/* Parse the remaining arguments as paths relative to a WC. + * + * TARGETS are relative to current working directory. + * + * Set *targets_by_wcroot to a hash mapping (char *)wcroot_abspath to + * (apr_array_header_t *)array of relpaths relative to that WC root. + */ +static svn_error_t * +targets_relative_to_wcs(apr_hash_t **targets_by_wcroot_p, +                        apr_array_header_t *targets, +                        svn_client_ctx_t *ctx, +                        apr_pool_t *result_pool, +                        apr_pool_t *scratch_pool) +{ +  apr_hash_t *targets_by_wcroot = apr_hash_make(result_pool); +  int i; + +  /* Make each target relative to the WC root. */ +  for (i = 0; i < targets->nelts; i++) +    { +      const char *target = APR_ARRAY_IDX(targets, i, const char *); +      const char *wcroot_abspath; +      apr_array_header_t *paths; + +      SVN_ERR(svn_dirent_get_absolute(&target, target, result_pool)); +      SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, target, +                                     ctx, result_pool, scratch_pool)); +      paths = svn_hash_gets(targets_by_wcroot, wcroot_abspath); +      if (! paths) +        { +          paths = apr_array_make(result_pool, 0, sizeof(char *)); +          svn_hash_sets(targets_by_wcroot, wcroot_abspath, paths); +        } +      target = svn_dirent_skip_ancestor(wcroot_abspath, target); + +      if (target) +        APR_ARRAY_PUSH(paths, const char *) = target; +    } +  *targets_by_wcroot_p = targets_by_wcroot; +  return SVN_NO_ERROR; +} + +/* Return targets relative to a WC. Error if they refer to more than one WC. */ +static svn_error_t * +targets_relative_to_a_wc(const char **wc_root_abspath_p, +                         apr_array_header_t **paths_p, +                         apr_getopt_t *os, +                         const apr_array_header_t *known_targets, +                         svn_client_ctx_t *ctx, +                         apr_pool_t *result_pool, +                         apr_pool_t *scratch_pool) +{ +  apr_array_header_t *targets; +  apr_hash_t *targets_by_wcroot; +  apr_hash_index_t *hi; + +  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, +                                                      known_targets, +                                                      ctx, FALSE, result_pool)); +  svn_opt_push_implicit_dot_target(targets, result_pool); + +  SVN_ERR(targets_relative_to_wcs(&targets_by_wcroot, targets, +                                  ctx, result_pool, scratch_pool)); +  if (apr_hash_count(targets_by_wcroot) != 1) +    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, +                            _("All targets must be in the same WC")); + +  hi = apr_hash_first(scratch_pool, targets_by_wcroot); +  *wc_root_abspath_p = apr_hash_this_key(hi); +  *paths_p = apr_hash_this_val(hi); +  return SVN_NO_ERROR; +} + +/* Return a human-friendly description of DURATION. + */ +static char * +friendly_age_str(apr_time_t mtime, +                 apr_time_t time_now, +                 apr_pool_t *result_pool) +{ +  int minutes = (int)((time_now - mtime) / 1000000 / 60); +  char *s; + +  if (minutes >= 60 * 24) +    s = apr_psprintf(result_pool, +                     Q_("%d day ago", "%d days ago", +                        minutes / 60 / 24), +                     minutes / 60 / 24); +  else if (minutes >= 60) +    s = apr_psprintf(result_pool, +                     Q_("%d hour ago", "%d hours ago", +                        minutes / 60), +                     minutes / 60); +  else +    s = apr_psprintf(result_pool, +                     Q_("%d minute ago", "%d minutes ago", +                        minutes), +                     minutes); +  return s; +} + +/* A comparison function for svn_sort__hash(), comparing the mtime of two +   svn_client_shelf_info_t's. */ +static int +compare_shelf_infos_by_mtime(const svn_sort__item_t *a, +                             const svn_sort__item_t *b) +{ +  svn_client__shelf2_info_t *a_val = a->value; +  svn_client__shelf2_info_t *b_val = b->value; + +  return (a_val->mtime < b_val->mtime) +    ? -1 : (a_val->mtime > b_val->mtime) ? 1 : 0; +} + +/* Return a list of shelves sorted by their 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 *shelf_infos; + +  SVN_ERR(svn_client__shelf2_list(&shelf_infos, local_abspath, +                                ctx, scratch_pool, scratch_pool)); +  *list = svn_sort__hash(shelf_infos, +                         compare_shelf_infos_by_mtime, +                         scratch_pool); +  return SVN_NO_ERROR; +} + +/*  */ +static svn_error_t * +stats(svn_client__shelf2_t *shelf, +      int version, +      svn_client__shelf2_version_t *shelf_version, +      apr_time_t time_now, +      svn_boolean_t with_logmsg, +      apr_pool_t *scratch_pool) +{ +  char *age_str; +  char *version_str; +  apr_hash_t *paths; +  const char *paths_str = ""; + +  if (! shelf_version) +    { +      return SVN_NO_ERROR; +    } + +  age_str = friendly_age_str(shelf_version->mtime, time_now, scratch_pool); +  if (version == shelf->max_version) +    version_str = apr_psprintf(scratch_pool, +                               _("version %d"), version); +  else +    version_str = apr_psprintf(scratch_pool, +                               Q_("version %d of %d", "version %d of %d", +                                  shelf->max_version), +                               version, shelf->max_version); +  SVN_ERR(svn_client__shelf2_paths_changed(&paths, shelf_version, +                                         scratch_pool, scratch_pool)); +  paths_str = apr_psprintf(scratch_pool, +                           Q_("%d path changed", "%d paths changed", +                              apr_hash_count(paths)), +                           apr_hash_count(paths)); +  SVN_ERR(svn_cmdline_printf(scratch_pool, +                             "%-30s %s, %s, %s\n", +                             shelf->name, version_str, age_str, paths_str)); + +  if (with_logmsg) +    { +      char *log_message; + +      SVN_ERR(svn_client__shelf2_get_log_message(&log_message, shelf, +                                               scratch_pool)); +      if (log_message) +        { +          SVN_ERR(svn_cmdline_printf(scratch_pool, +                                     _(" %.50s\n"), +                                     log_message)); +        } +    } + +  return SVN_NO_ERROR; +} + +/* Display a list of shelves */ +static svn_error_t * +shelves_list(const char *local_abspath, +             svn_boolean_t quiet, +             svn_client_ctx_t *ctx, +             apr_pool_t *scratch_pool) +{ +  apr_time_t time_now = apr_time_now(); +  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__shelf2_t *shelf; +      svn_client__shelf2_version_t *shelf_version; + +      SVN_ERR(svn_client__shelf2_open_existing(&shelf, name, local_abspath, +                                             ctx, scratch_pool)); +      SVN_ERR(svn_client__shelf2_get_newest_version(&shelf_version, shelf, +                                                  scratch_pool, scratch_pool)); +      if (quiet) +        SVN_ERR(svn_cmdline_printf(scratch_pool, "%s\n", shelf->name)); +      else if (!shelf_version) +        SVN_ERR(svn_cmdline_printf(scratch_pool, "%-30s no versions\n", +                                   shelf->name)); +      else +        SVN_ERR(stats(shelf, shelf->max_version, shelf_version, time_now, +                      TRUE /*with_logmsg*/, scratch_pool)); +      SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); +    } + +  return SVN_NO_ERROR; +} + +/* Print info about each checkpoint of the shelf named NAME. + */ +static svn_error_t * +shelf_log(const char *name, +          const char *local_abspath, +          svn_client_ctx_t *ctx, +          apr_pool_t *scratch_pool) +{ +  apr_time_t time_now = apr_time_now(); +  svn_client__shelf2_t *shelf; +  apr_array_header_t *versions; +  int i; + +  SVN_ERR(svn_client__shelf2_open_existing(&shelf, name, local_abspath, +                                         ctx, scratch_pool)); +  SVN_ERR(svn_client__shelf2_get_all_versions(&versions, shelf, +                                            scratch_pool, scratch_pool)); +  for (i = 0; i < versions->nelts; i++) +    { +      svn_client__shelf2_version_t *shelf_version +        = APR_ARRAY_IDX(versions, i, void *); + +      SVN_ERR(stats(shelf, i + 1, shelf_version, time_now, +                    FALSE /*with_logmsg*/, scratch_pool)); +    } + +  SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +/* Find the name of the youngest shelf. + */ +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 shelves found")); + +  youngest_item = &APR_ARRAY_IDX(list, list->nelts - 1, svn_sort__item_t); +  *name_p = apr_pstrdup(result_pool, youngest_item->key); +  return SVN_NO_ERROR; +} + +struct status_baton +{ +  /* These fields correspond to the ones in the +     svn_cl__print_status() interface. */ +  const char *target_abspath; +  const char *target_path; + +  svn_boolean_t quiet;  /* don't display statuses while shelving them */ +  int num_paths_shelved; +  int num_paths_not_shelved; +  svn_client_ctx_t *ctx; +}; + +/* A status callback function for printing STATUS for PATH. */ +static svn_error_t * +print_status(void *baton, +             const char *path, +             const svn_client_status_t *status, +             apr_pool_t *scratch_pool) +{ +  struct status_baton *sb = baton; +  unsigned int conflicts; + +  return svn_cl__print_status(sb->target_abspath, sb->target_path, +                              path, status, +                              TRUE /*suppress_externals_placeholders*/, +                              FALSE /*detailed*/, +                              FALSE /*show_last_committed*/, +                              TRUE /*skip_unrecognized*/, +                              FALSE /*repos_locks*/, +                              &conflicts, &conflicts, &conflicts, +                              sb->ctx, +                              scratch_pool); +} + +/* A callback function for shelved paths. */ +static svn_error_t * +was_shelved(void *baton, +            const char *path, +            const svn_client_status_t *status, +            apr_pool_t *scratch_pool) +{ +  struct status_baton *sb = baton; + +  if (!sb->quiet) +    { +      SVN_ERR(print_status(baton, path, status, scratch_pool)); +    } + +  ++sb->num_paths_shelved; +  return SVN_NO_ERROR; +} + +/* A callback function for not-shelved paths. */ +static svn_error_t * +was_not_shelved(void *baton, +                const char *path, +                const svn_client_status_t *status, +                apr_pool_t *scratch_pool) +{ +  struct status_baton *sb = baton; + +  SVN_ERR(print_status(baton, path, status, scratch_pool)); +  SVN_ERR(svn_cmdline_printf(scratch_pool, "      >   not shelved\n")); +  ++sb->num_paths_not_shelved; +  return SVN_NO_ERROR; +} + +/** Shelve/save a new version of changes. + * + * Shelve in shelf @a name the local modifications found by @a paths, + * @a depth, @a changelists. Revert the shelved changes from the WC + * unless @a keep_local is true. + * + * If no local modifications are found, throw an error. + * + * If @a dry_run is true, don't actually do it. + * + * Report in @a *new_version_p the new version number (or, with dry run, + * what it would be). + */ +static svn_error_t * +shelve(int *new_version_p, +       const char *name, +       const apr_array_header_t *paths, +       svn_depth_t depth, +       const apr_array_header_t *changelists, +       apr_hash_t *revprop_table, +       svn_boolean_t keep_local, +       svn_boolean_t dry_run, +       svn_boolean_t quiet, +       const char *local_abspath, +       svn_client_ctx_t *ctx, +       apr_pool_t *scratch_pool) +{ +  svn_client__shelf2_t *shelf; +  svn_client__shelf2_version_t *previous_version; +  svn_client__shelf2_version_t *new_version; +  struct status_baton sb; + +  SVN_ERR(svn_client__shelf2_open_or_create(&shelf, +                                          name, local_abspath, +                                          ctx, scratch_pool)); +  SVN_ERR(svn_client__shelf2_get_newest_version(&previous_version, shelf, +                                              scratch_pool, scratch_pool)); + +  if (! quiet) +    { +      SVN_ERR(svn_cmdline_printf(scratch_pool, keep_local +                                 ? _("--- Save a new version of '%s' in WC root '%s'\n") +                                 : _("--- Shelve '%s' in WC root '%s'\n"), +                                 shelf->name, shelf->wc_root_abspath)); +      SVN_ERR(stats(shelf, shelf->max_version, previous_version, apr_time_now(), +                    TRUE /*with_logmsg*/, scratch_pool)); +    } + +  sb.target_abspath = shelf->wc_root_abspath; +  sb.target_path = ""; +  sb.quiet = quiet; +  sb.num_paths_shelved = 0; +  sb.num_paths_not_shelved = 0; +  sb.ctx = ctx; + +  if (! quiet) +    SVN_ERR(svn_cmdline_printf(scratch_pool, +                               keep_local ? _("--- Saving...\n") +                               : _("--- Shelving...\n"))); +  SVN_ERR(svn_client__shelf2_save_new_version3(&new_version, shelf, +                                             paths, depth, changelists, +                                             was_shelved, &sb, +                                             was_not_shelved, &sb, +                                             scratch_pool)); +  if (sb.num_paths_not_shelved > 0) +    { +      SVN_ERR(svn_client__shelf2_delete_newer_versions(shelf, previous_version, +                                                     scratch_pool)); +      SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); +      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                               Q_("%d path could not be shelved", +                                  "%d paths could not be shelved", +                                  sb.num_paths_not_shelved), +                               sb.num_paths_not_shelved); +    } +  if (sb.num_paths_shelved == 0 +      || ! new_version) +    { +      SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); +      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                               keep_local ? _("No local modifications could be saved") +                               : _("No local modifications could be shelved")); +    } + +  /* Un-apply the changes, if required. */ +  if (!keep_local) +    { +      SVN_ERR(svn_client__shelf2_unapply(new_version, +                                       dry_run, scratch_pool)); +    } + +  /* Fetch the log message and any other revprops */ +  if (ctx->log_msg_func3) +    { +      const char *tmp_file; +      apr_array_header_t *commit_items +        = apr_array_make(scratch_pool, 1, sizeof(void *)); +      const char *message = ""; + +      SVN_ERR(ctx->log_msg_func3(&message, &tmp_file, commit_items, +                                 ctx->log_msg_baton3, scratch_pool)); +      /* Abort the shelving if the log message callback requested so. */ +      if (! message) +        return SVN_NO_ERROR; + +      if (message && !dry_run) +        { +          svn_string_t *propval = svn_string_create(message, scratch_pool); + +          if (! revprop_table) +            revprop_table = apr_hash_make(scratch_pool); +          svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, propval); +        } +    } + +  SVN_ERR(svn_client__shelf2_revprop_set_all(shelf, revprop_table, scratch_pool)); + +  if (new_version_p) +    *new_version_p = shelf->max_version; + +  if (dry_run) +    { +      SVN_ERR(svn_client__shelf2_delete_newer_versions(shelf, previous_version, +                                                     scratch_pool)); +    } + +  SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +/* Return the single character representation of STATUS. + * (Similar to subversion/svn/status.c:generate_status_code() + * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */ +static char +status_to_char(enum svn_wc_status_kind status) +{ +  switch (status) +    { +    case svn_wc_status_none:        return '.'; +    case svn_wc_status_unversioned: return '?'; +    case svn_wc_status_normal:      return ' '; +    case svn_wc_status_added:       return 'A'; +    case svn_wc_status_missing:     return '!'; +    case svn_wc_status_deleted:     return 'D'; +    case svn_wc_status_replaced:    return 'R'; +    case svn_wc_status_modified:    return 'M'; +    case svn_wc_status_merged:      return 'G'; +    case svn_wc_status_conflicted:  return 'C'; +    case svn_wc_status_ignored:     return 'I'; +    case svn_wc_status_obstructed:  return '~'; +    case svn_wc_status_external:    return 'X'; +    case svn_wc_status_incomplete:  return ':'; +    default:                        return '*'; +    } +} + +/* Throw an error if any path affected by SHELF_VERSION gives a conflict + * when applied (as a dry-run) to the WC. */ +static svn_error_t * +test_apply(svn_client__shelf2_version_t *shelf_version, +           svn_client_ctx_t *ctx, +           apr_pool_t *scratch_pool) +{ +  apr_hash_t *paths; +  apr_hash_index_t *hi; + +  SVN_ERR(svn_client__shelf2_paths_changed(&paths, shelf_version, +                                         scratch_pool, scratch_pool)); +  for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi)) +    { +      const char *path = apr_hash_this_key(hi); +      svn_boolean_t conflict; + +      SVN_ERR(svn_client__shelf2_test_apply_file(&conflict, shelf_version, path, +                                               scratch_pool)); +      if (conflict) +        { +          char *to_wc_abspath +            = svn_dirent_join(shelf_version->shelf->wc_root_abspath, path, +                              scratch_pool); +          svn_wc_status3_t *status; + +          SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, to_wc_abspath, +                                 scratch_pool, scratch_pool)); +          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                                   _("Shelved path '%s' already has " +                                     "status '%c' in the working copy"), +                                   path, status_to_char(status->node_status)); +        } +    } +  return SVN_NO_ERROR; +} + +/** Restore/unshelve a given or newest version of changes. + * + * Restore local modifications from shelf @a name version @a arg, + * or the newest version is @a arg is null. + * + * If @a dry_run is true, don't actually do it. + * + * Error if any path would have a conflict, unless @a force_if_conflict. + */ +static svn_error_t * +shelf_restore(const char *name, +              const char *arg, +              svn_boolean_t dry_run, +              svn_boolean_t quiet, +              svn_boolean_t force_if_conflict, +              const char *local_abspath, +              svn_client_ctx_t *ctx, +              apr_pool_t *scratch_pool) +{ +  int version, old_version; +  apr_time_t time_now = apr_time_now(); +  svn_client__shelf2_t *shelf; +  svn_client__shelf2_version_t *shelf_version; + +  SVN_ERR(svn_client__shelf2_open_existing(&shelf, name, local_abspath, +                                         ctx, scratch_pool)); + +  old_version = shelf->max_version; +  if (arg) +    { +      SVN_ERR(svn_cstring_atoi(&version, arg)); +      SVN_ERR(svn_client__shelf2_version_open(&shelf_version, +                                            shelf, version, +                                            scratch_pool, scratch_pool)); +    } +  else +    { +      version = shelf->max_version; +      SVN_ERR(get_newest_version_existing(&shelf_version, shelf, +                                          scratch_pool, scratch_pool)); +    } + +  if (! quiet) +    { +      SVN_ERR(svn_cmdline_printf(scratch_pool, +                                 _("--- Unshelve '%s' in WC root '%s'\n"), +                                 shelf->name, shelf->wc_root_abspath)); +      SVN_ERR(stats(shelf, version, shelf_version, time_now, +                    TRUE /*with_logmsg*/, scratch_pool)); +    } +  if (! force_if_conflict) +    { +      SVN_ERR_W(test_apply(shelf_version, ctx, scratch_pool), +                _("Cannot unshelve/restore, as at least one shelved " +                  "path would conflict with a local modification " +                  "or other status in the working copy")); +    } + +  SVN_ERR(svn_client__shelf2_apply(shelf_version, +                                 dry_run, scratch_pool)); + +  if (! dry_run) +    { +      SVN_ERR(svn_client__shelf2_delete_newer_versions(shelf, shelf_version, +                                                     scratch_pool)); +    } + +  if (!quiet) +    { +      if (version < old_version) +        SVN_ERR(svn_cmdline_printf(scratch_pool, +                                   Q_("restored '%s' version %d and deleted %d newer version\n", +                                      "restored '%s' version %d and deleted %d newer versions\n", +                                      old_version - version), +                                   name, version, old_version - version)); +      else +        SVN_ERR(svn_cmdline_printf(scratch_pool, +                                   _("restored '%s' version %d (the newest version)\n"), +                                   name, version)); +    } + +  SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +static svn_error_t * +shelf_diff(const char *name, +           const char *arg, +           const char *local_abspath, +           svn_boolean_t summarize, +           svn_depth_t depth, +           svn_boolean_t ignore_ancestry, +           svn_client_ctx_t *ctx, +           apr_pool_t *scratch_pool) +{ +  svn_client__shelf2_t *shelf; +  svn_client__shelf2_version_t *shelf_version; +  svn_stream_t *stream, *errstream; +  svn_diff_tree_processor_t *diff_processor; + +  SVN_ERR(svn_client__shelf2_open_existing(&shelf, name, local_abspath, +                                         ctx, scratch_pool)); + +  if (arg) +    { +      int version; + +      SVN_ERR(svn_cstring_atoi(&version, arg)); +      SVN_ERR(svn_client__shelf2_version_open(&shelf_version, +                                            shelf, version, +                                            scratch_pool, scratch_pool)); +    } +  else +    { +      SVN_ERR(get_newest_version_existing(&shelf_version, shelf, +                                          scratch_pool, scratch_pool)); +    } + +  SVN_ERR(svn_stream_for_stdout(&stream, scratch_pool)); +  errstream = svn_stream_empty(scratch_pool); + +  if (summarize) +    { +      svn_client_diff_summarize_func_t func; +      void *baton; + +      SVN_ERR(svn_cl__get_diff_summary_writer(&func, &baton, +                                              FALSE /*xml*/, +                                              FALSE /*ignore_properties*/, +                                              "" /*anchor/prefix*/, +                                              scratch_pool, scratch_pool)); +      SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, +                                                       func, baton, +                                                       scratch_pool, +                                                       scratch_pool)); +    } +  else +    { +      SVN_ERR(svn_client__get_diff_writer_svn( +                &diff_processor, +                NULL /*anchor*/, +                "", "", /*orig_path_1, orig_path_2,*/ +                NULL /*options*/, +                "" /*relative_to_dir*/, +                FALSE /*no_diff_added*/, +                FALSE /*no_diff_deleted*/, +                FALSE /*show_copies_as_adds*/, +                FALSE /*ignore_content_type*/, +                FALSE /*ignore_properties*/, +                FALSE /*properties_only*/, +                TRUE /*pretty_print_mergeinfo*/, +                svn_cmdline_output_encoding(scratch_pool), +                stream, errstream, +                ctx, scratch_pool)); +    } + +  SVN_ERR(svn_client__shelf2_diff(shelf_version, "", +                                 depth, ignore_ancestry, +                                 diff_processor, scratch_pool)); +  SVN_ERR(svn_stream_close(stream)); + +  SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +shelf_drop(const char *name, +           const char *local_abspath, +           svn_boolean_t dry_run, +           svn_boolean_t quiet, +           svn_client_ctx_t *ctx, +           apr_pool_t *scratch_pool) +{ +  SVN_ERR(svn_client__shelf2_delete(name, local_abspath, dry_run, +                                  ctx, scratch_pool)); +  if (! quiet) +    SVN_ERR(svn_cmdline_printf(scratch_pool, +                               _("deleted '%s'\n"), +                               name)); +  return SVN_NO_ERROR; +} + +/*  */ +static svn_error_t * +shelf_shelve(int *new_version, +             const char *name, +             apr_array_header_t *targets, +             svn_depth_t depth, +             apr_array_header_t *changelists, +             apr_hash_t *revprop_table, +             svn_boolean_t keep_local, +             svn_boolean_t dry_run, +             svn_boolean_t quiet, +             svn_client_ctx_t *ctx, +             apr_pool_t *scratch_pool) +{ +  const char *local_abspath; + +  if (depth == svn_depth_unknown) +    depth = svn_depth_infinity; + +  SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); + +  SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); + +  svn_opt_push_implicit_dot_target(targets, scratch_pool); + +  /* ### TODO: check all paths are in same WC; for now use first path */ +  SVN_ERR(svn_dirent_get_absolute(&local_abspath, +                                  APR_ARRAY_IDX(targets, 0, char *), +                                  scratch_pool)); + +  SVN_ERR(shelve(new_version, name, +                 targets, depth, changelists, +                 revprop_table, +                 keep_local, dry_run, quiet, +                 local_abspath, ctx, scratch_pool)); + +  return SVN_NO_ERROR; +} + +static svn_error_t * +svn_cl__shelf_shelve(apr_getopt_t *os, +                     void *baton, +                     apr_pool_t *pool); + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_save(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; + +  opt_state->keep_local = TRUE; +  SVN_ERR(svn_cl__shelf_shelve(os, baton, pool)); +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_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 *name; +  apr_array_header_t *targets; + +  if (opt_state->quiet) +    ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */ + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  /* Parse the remaining arguments as paths. */ +  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, +                                                      opt_state->targets, +                                                      ctx, FALSE, pool)); +  { +    int new_version; +    svn_error_t *err; + +    if (ctx->log_msg_func3) +      SVN_ERR(svn_cl__make_log_msg_baton(&ctx->log_msg_baton3, +                                         opt_state, NULL, ctx->config, +                                         pool)); +    err = shelf_shelve(&new_version, name, +                       targets, opt_state->depth, opt_state->changelists, +                       opt_state->revprop_table, +                       opt_state->keep_local, opt_state->dry_run, +                       opt_state->quiet, 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 (! opt_state->quiet) +      { +        if (opt_state->keep_local) +          SVN_ERR(svn_cmdline_printf(pool, +                                     _("saved '%s' version %d\n"), +                                     name, new_version)); +        else +          SVN_ERR(svn_cmdline_printf(pool, +                                     _("shelved '%s' version %d\n"), +                                     name, new_version)); +      } +  } + +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_unshelve(apr_getopt_t *os, +                       void *baton, +                       apr_pool_t *scratch_pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  const char *local_abspath; +  const char *name; +  const char *arg = NULL; + +  SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", scratch_pool)); + +  if (os->ind < os->argc) +    { +      SVN_ERR(get_next_argument(&name, os, scratch_pool, scratch_pool)); +    } +  else +    { +      SVN_ERR(name_of_youngest(&name, +                               local_abspath, ctx, scratch_pool, scratch_pool)); +      SVN_ERR(svn_cmdline_printf(scratch_pool, +                                 _("unshelving the youngest shelf, '%s'\n"), +                                 name)); +    } + +  /* Which checkpoint number? */ +  if (os->ind < os->argc) +    SVN_ERR(get_next_argument(&arg, os, scratch_pool, scratch_pool)); + +  if (os->ind < os->argc) +    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +                            _("Too many arguments")); + +  if (opt_state->quiet) +    ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */ + +  SVN_ERR(shelf_restore(name, arg, +                        opt_state->dry_run, opt_state->quiet, +                        opt_state->force /*force_already_modified*/, +                        local_abspath, ctx, scratch_pool)); + +  if (opt_state->drop) +    { +      SVN_ERR(shelf_drop(name, local_abspath, +                         opt_state->dry_run, opt_state->quiet, +                         ctx, scratch_pool)); +    } +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_list(apr_getopt_t *os, +                   void *baton, +                   apr_pool_t *pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  apr_array_header_t *targets = NULL; +  apr_pool_t *iterpool = svn_pool_create(pool); +  int i; + +  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, +                                                      opt_state->targets, +                                                      ctx, FALSE, pool)); +  /* Add "." if user passed 0 arguments */ +  svn_opt_push_implicit_dot_target(targets, pool); + +  for (i = 0; i < targets->nelts; ++i) +    { +      const char *local_abspath; +      const char *target = APR_ARRAY_IDX(targets, i, const char *); + +      svn_pool_clear(iterpool); + +      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); + +      SVN_ERR(shelves_list(local_abspath, +                           opt_state->quiet, +                           ctx, iterpool)); +    } + +  svn_pool_destroy(iterpool); + +  return SVN_NO_ERROR; +} + +/* "svn shelf-list-by-paths [PATH...]" + * + * TARGET_RELPATHS are all within the same WC, relative to WC_ROOT_ABSPATH. + */ +static svn_error_t * +shelf_list_by_paths(apr_array_header_t *target_relpaths, +                    const char *wc_root_abspath, +                    svn_client_ctx_t *ctx, +                    apr_pool_t *scratch_pool) +{ +  apr_array_header_t *shelves; +  apr_hash_t *paths_to_shelf_name = apr_hash_make(scratch_pool); +  apr_array_header_t *array; +  int i; + +  SVN_ERR(list_sorted_by_date(&shelves, +                              wc_root_abspath, ctx, scratch_pool)); + +  /* Check paths are valid */ +  for (i = 0; i < target_relpaths->nelts; i++) +    { +      char *target_relpath = APR_ARRAY_IDX(target_relpaths, i, char *); + +      if (svn_path_is_url(target_relpath)) +        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, +                                 _("'%s' is not a local path"), target_relpath); +      SVN_ERR_ASSERT(svn_relpath_is_canonical(target_relpath)); +    } + +  /* Find the most recent shelf for each affected path */ +  for (i = 0; i < shelves->nelts; i++) +    { +      svn_sort__item_t *item = &APR_ARRAY_IDX(shelves, i, svn_sort__item_t); +      const char *name = item->key; +      svn_client__shelf2_t *shelf; +      svn_client__shelf2_version_t *shelf_version; +      apr_hash_t *shelf_paths; +      int j; + +      SVN_ERR(svn_client__shelf2_open_existing(&shelf, +                                              name, wc_root_abspath, +                                              ctx, scratch_pool)); +      SVN_ERR(svn_client__shelf2_get_newest_version(&shelf_version, shelf, +                                                   scratch_pool, scratch_pool)); +      if (!shelf_version) +        continue; +      SVN_ERR(svn_client__shelf2_paths_changed(&shelf_paths, +                                              shelf_version, +                                              scratch_pool, scratch_pool)); +      for (j = 0; j < target_relpaths->nelts; j++) +        { +          char *target_relpath = APR_ARRAY_IDX(target_relpaths, j, char *); +          apr_hash_index_t *hi; + +          for (hi = apr_hash_first(scratch_pool, shelf_paths); +               hi; hi = apr_hash_next(hi)) +            { +              const char *shelf_path = apr_hash_this_key(hi); + +              if (svn_relpath_skip_ancestor(target_relpath, shelf_path)) +                { +                  if (! svn_hash_gets(paths_to_shelf_name, shelf_path)) +                    { +                      svn_hash_sets(paths_to_shelf_name, shelf_path, shelf->name); +                    } +                } +            } +        } +    } + +  /* Print the results. */ +  array = svn_sort__hash(paths_to_shelf_name, +                         svn_sort_compare_items_as_paths, +                         scratch_pool); +  for (i = 0; i < array->nelts; i++) +    { +      svn_sort__item_t *item = &APR_ARRAY_IDX(array, i, svn_sort__item_t); +      const char *path = item->key; +      const char *name = item->value; + +      SVN_ERR(svn_cmdline_printf(scratch_pool, "%-20.20s %s\n", +                                 name, +                                 svn_dirent_local_style(path, scratch_pool))); +    } +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_list_by_paths(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 *wc_root_abspath; +  apr_array_header_t *targets; + +  /* Parse the remaining arguments as paths. */ +  SVN_ERR(targets_relative_to_a_wc(&wc_root_abspath, &targets, +                                   os, opt_state->targets, +                                   ctx, pool, pool)); + +  SVN_ERR(shelf_list_by_paths(targets, wc_root_abspath, ctx, pool)); +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_diff(apr_getopt_t *os, +                   void *baton, +                   apr_pool_t *pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  const char *local_abspath; +  const char *name; +  const char *arg = NULL; + +  SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool)); + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  /* Which checkpoint number? */ +  if (os->ind < os->argc) +    SVN_ERR(get_next_argument(&arg, os, pool, pool)); + +  if (os->ind < os->argc) +    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +                            _("Too many arguments")); + +  SVN_ERR(shelf_diff(name, arg, local_abspath, +                     opt_state->diff.summarize, +                     opt_state->depth, opt_state->ignore_ancestry, +                     ctx, pool)); + +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_drop(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 *name; +  apr_array_header_t *targets = NULL; +  apr_pool_t *iterpool = svn_pool_create(pool); +  int i; + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  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); + +  for (i = 0; i < targets->nelts; ++i) +    { +      const char *local_abspath; +      const char *target = APR_ARRAY_IDX(targets, i, const char *); + +      svn_pool_clear(iterpool); + +      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); +      SVN_ERR(shelf_drop(name, local_abspath, +                         opt_state->dry_run, opt_state->quiet, +                         ctx, iterpool)); +    } + +  svn_pool_destroy(iterpool); + +  return SVN_NO_ERROR; +} + +/* This implements the `svn_opt_subcommand_t' interface. */ +static svn_error_t * +svn_cl__shelf_log(apr_getopt_t *os, +                  void *baton, +                  apr_pool_t *pool) +{ +  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; +  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; +  const char *name; +  apr_array_header_t *targets = NULL; +  apr_pool_t *iterpool = svn_pool_create(pool); +  int i; + +  SVN_ERR(get_next_argument(&name, os, pool, pool)); + +  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); + +  for (i = 0; i < targets->nelts; ++i) +    { +      const char *local_abspath; +      const char *target = APR_ARRAY_IDX(targets, i, const char *); + +      svn_pool_clear(iterpool); + +      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool)); +      SVN_ERR(shelf_log(name, local_abspath, ctx, iterpool)); +    } + +  svn_pool_destroy(iterpool); + +  return SVN_NO_ERROR; +} + +const svn_opt_subcommand_desc3_t +svn_cl__cmd_table_shelf2[] = +{ +  { "x-shelf-diff", svn_cl__shelf_diff, {0}, {N_( +     "Show shelved changes as a diff.\n" +     "usage: x-shelf-diff SHELF [VERSION]\n" +     "\n"), N_( +     "  Show the changes in SHELF:VERSION (default: latest) as a diff.\n" +     "\n"), N_( +     "  See also: 'svn diff --cl=svn:shelf:SHELF' which supports most options of\n" +     "  'svn diff'.\n" +     "\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_summarize}, +  }, + +  { "x-shelf-drop", svn_cl__shelf_drop, {0}, {N_( +     "Delete a shelf.\n" +     "usage: x-shelf-drop SHELF [PATH ...]\n" +     "\n"), N_( +     "  Delete the shelves named SHELF from the working copies containing PATH\n" +     "  (default PATH is '.')\n" +     "\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" +    )}, +  }, + +  { "x-shelf-list", svn_cl__shelf_list, {"x-shelves"}, {N_( +     "List shelves.\n" +     "usage: x-shelf-list [PATH ...]\n" +     "\n"), N_( +     "  List shelves for each working copy containing PATH (default is '.')\n" +     "  Include the first line of any log message and some details about the\n" +     "  contents of the shelf, unless '-q' is given.\n" +     "\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', 'v'} +  }, + +  { "x-shelf-list-by-paths", svn_cl__shelf_list_by_paths, {0}, {N_( +     "List which shelf affects each path.\n" +     "usage: x-shelf-list-by-paths [PATH...]\n" +     "\n"), N_( +     "  List which shelf most recently affects each path below the given PATHs.\n" +     "\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" +    )}, +  }, + +  { "x-shelf-log", svn_cl__shelf_log, {0}, {N_( +     "Show the versions of a shelf.\n" +     "usage: x-shelf-log SHELF [PATH...]\n" +     "\n"), N_( +     "  Show all versions of SHELF for each working copy containing PATH (the\n" +     "  default PATH is '.').\n" +     "\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', 'v'} +  }, + +  { "x-shelf-save", svn_cl__shelf_save, {0}, {N_( +     "Copy local changes onto a new version of a shelf.\n" +     "usage: x-shelf-save SHELF [PATH...]\n" +     "\n"), N_( +     "  Save local changes in the given PATHs as a new version of SHELF.\n" +     "  The shelf's log message can be set with -m, -F, etc.\n" +     "\n"), N_( +     "  The same as 'svn shelve --keep-local'.\n" +     "\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', opt_dry_run, +     opt_depth, opt_targets, opt_changelist, +     SVN_CL__LOG_MSG_OPTIONS, +    } +  }, + +  { "x-shelve", svn_cl__shelf_shelve, {0}, {N_( +     "Move local changes onto a shelf.\n" +     "usage: x-shelve [--keep-local] SHELF [PATH...]\n" +     "\n"), N_( +     "  Save the local changes in the given PATHs to a new or existing SHELF.\n" +     "  Revert those changes from the WC unless '--keep-local' is given.\n" +     "  The shelf's log message can be set with -m, -F, etc.\n" +     "\n"), N_( +     "  'svn shelve --keep-local' is the same as 'svn shelf-save'.\n" +     "\n"), N_( +     "  The kinds of change you can shelve are committable changes to files and\n" +     "  properties, except the following kinds which are not yet supported:\n" +     "     * copies and moves\n" +     "     * mkdir and rmdir\n" +     "  Uncommittable states such as conflicts, unversioned and missing cannot\n" +     "  be shelved.\n" +     "\n"), N_( +     "  To bring back shelved changes, use 'svn unshelve SHELF'.\n" +     "\n"), N_( +     "  Shelves are currently stored under <WC>/.svn/experimental/shelves/ .\n" +     "  (In Subversion 1.10, shelves were stored under <WC>/.svn/shelves/ as\n" +     "  patch files. To recover a shelf created by 1.10, either use a 1.10\n" +     "  client to find and unshelve it, or find the patch file and use any\n" +     "  1.10 or later 'svn patch' to apply it.)\n" +     "\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', opt_dry_run, opt_keep_local, +     opt_depth, opt_targets, opt_changelist, +     SVN_CL__LOG_MSG_OPTIONS, +    } }, + +  { "x-unshelve", svn_cl__shelf_unshelve, {0}, {N_( +     "Copy shelved changes back into the WC.\n" +     "usage: x-unshelve [--drop] [SHELF [VERSION]]\n" +     "\n"), N_( +     "  Apply the changes stored in SHELF to the working copy.\n" +     "  SHELF defaults to the newest shelf.\n" +     "\n"), N_( +     "  Apply the newest version of the shelf, by default. If VERSION is\n" +     "  specified, apply that version and discard all versions newer than that.\n" +     "  In any case, retain the unshelved version and versions older than that\n" +     "  (unless --drop is specified).\n" +     "\n"), N_( +     "  With --drop, delete the entire shelf (like 'svn shelf-drop') after\n" +     "  successfully unshelving with no conflicts.\n" +     "\n"), N_( +     "  The working files involved should be in a clean, unmodified state\n" +     "  before using this command. To roll back to an older version of the\n" +     "  shelf, first ensure any current working changes are removed, such as\n" +     "  by shelving or reverting them, and then unshelve the desired version.\n" +     "\n"), N_( +     "  Unshelve normally refuses to apply any changes if any path involved is\n" +     "  already modified (or has any other abnormal status) in the WC. With\n" +     "  --force, it does not check and may error out and/or produce partial or\n" +     "  unexpected results.\n" +     "\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_drop, 'q', opt_dry_run, opt_force} }, + +  { NULL, NULL, {0}, {NULL}, {0} } +}; + diff --git a/subversion/svn/shelf2-cmd.h b/subversion/svn/shelf2-cmd.h new file mode 100644 index 0000000000000..b058073cce7b1 --- /dev/null +++ b/subversion/svn/shelf2-cmd.h @@ -0,0 +1,49 @@ +/* + * shelf2-cmd.h:  experimental shelving v2 + * + * ==================================================================== + *    Licensed to the Apache Software Foundation (ASF) under one + *    or more contributor license agreements.  See the NOTICE file + *    distributed with this work for additional information + *    regarding copyright ownership.  The ASF licenses this file + *    to you under the Apache License, Version 2.0 (the + *    "License"); you may not use this file except in compliance + *    with the License.  You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + *    Unless required by applicable law or agreed to in writing, + *    software distributed under the License is distributed on an + *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + *    KIND, either express or implied.  See the License for the + *    specific language governing permissions and limitations + *    under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +#ifndef SVN_SHELF2_CMD_H +#define SVN_SHELF2_CMD_H + +/*** Includes. ***/ +#include <apr_getopt.h> + +#include "svn_opt.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +extern const svn_opt_subcommand_desc3_t svn_cl__cmd_table_shelf2[]; +/*extern const apr_getopt_option_t svn_cl__options_shelving2[];*/ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_SHELF2_CMD_H */ diff --git a/subversion/svn/shelve-cmd.c b/subversion/svn/shelve-cmd.c deleted file mode 100644 index df88a651d9c25..0000000000000 --- a/subversion/svn/shelve-cmd.c +++ /dev/null @@ -1,369 +0,0 @@ -/* - * 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/svn.c b/subversion/svn/svn.c index 48b894f499831..3c3ac6d5068e1 100644 --- a/subversion/svn/svn.c +++ b/subversion/svn/svn.c @@ -52,6 +52,8 @@  #include "svn_hash.h"  #include "svn_version.h"  #include "cl.h" +#include "shelf2-cmd.h" +#include "shelf-cmd.h"  #include "private/svn_opt_private.h"  #include "private/svn_cmdline_private.h" @@ -63,95 +65,6 @@  /*** Option Processing ***/ -/* Add an identifier here for long options that don't have a short -   option. Options that have both long and short options should just -   use the short option letter as identifier.  */ -typedef enum svn_cl__longopt_t { -  opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID, -  opt_auth_password_from_stdin, -  opt_auth_username, -  opt_autoprops, -  opt_changelist, -  opt_config_dir, -  opt_config_options, -  /* diff options */ -  opt_diff_cmd, -  opt_internal_diff, -  opt_no_diff_added, -  opt_no_diff_deleted, -  opt_show_copies_as_adds, -  opt_notice_ancestry, -  opt_summarize, -  opt_use_git_diff_format, -  opt_ignore_properties, -  opt_properties_only, -  opt_patch_compatible, -  /* end of diff options */ -  opt_dry_run, -  opt_editor_cmd, -  opt_encoding, -  opt_force_log, -  opt_force, -  opt_keep_changelists, -  opt_ignore_ancestry, -  opt_ignore_externals, -  opt_incremental, -  opt_merge_cmd, -  opt_native_eol, -  opt_new_cmd, -  opt_no_auth_cache, -  opt_no_autoprops, -  opt_no_ignore, -  opt_no_unlock, -  opt_non_interactive, -  opt_force_interactive, -  opt_old_cmd, -  opt_record_only, -  opt_relocate, -  opt_remove, -  opt_revprop, -  opt_stop_on_copy, -  opt_strict,                   /* ### DEPRECATED */ -  opt_targets, -  opt_depth, -  opt_set_depth, -  opt_version, -  opt_xml, -  opt_keep_local, -  opt_with_revprop, -  opt_with_all_revprops, -  opt_with_no_revprops, -  opt_parents, -  opt_accept, -  opt_show_revs, -  opt_reintegrate, -  opt_trust_server_cert, -  opt_trust_server_cert_failures, -  opt_strip, -  opt_ignore_keywords, -  opt_reverse_diff, -  opt_ignore_whitespace, -  opt_diff, -  opt_allow_mixed_revisions, -  opt_include_externals, -  opt_show_inherited_props, -  opt_search, -  opt_search_and, -  opt_mergeinfo_log, -  opt_remove_unversioned, -  opt_remove_ignored, -  opt_no_newline, -  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; - -  /* Option codes and descriptions for the command line client.   *   * The entire list must be terminated with an entry of nulls. @@ -166,7 +79,8 @@ const apr_getopt_option_t svn_cl__options[] =    {"message",       'm', 1, N_("specify log message ARG")},    {"quiet",         'q', 0, N_("print nothing, or only summary information")},    {"recursive",     'R', 0, N_("descend recursively, same as --depth=infinity")}, -  {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")}, +  {"non-recursive", 'N', 0, N_("obsolete")}, +  {"human-readable",'H', 0, N_("show human-readable output")},    {"change",        'c', 1,                      N_("the change made by revision ARG (like -r ARG-1:ARG)\n"                         "                             " @@ -421,6 +335,8 @@ const apr_getopt_option_t svn_cl__options[] =    {"remove-unversioned", opt_remove_unversioned, 0,                         N_("remove unversioned items")},    {"remove-ignored", opt_remove_ignored, 0, N_("remove ignored items")}, +  {"remove-added", opt_remove_added, 0, +                       N_("reverting an added item will remove it from disk")},    {"no-newline", opt_no_newline, 0, N_("do not output the trailing newline")},    {"show-passwords", opt_show_passwords, 0, N_("show cached passwords")},    {"pin-externals", opt_pin_externals, 0, @@ -444,6 +360,10 @@ const apr_getopt_option_t svn_cl__options[] =                            "                             "                            "   'repos-uuid' UUID of repository\n"                            "                             " +                          "   'repos-size' for files, the size of TARGET\n" +                          "                             " +                          "                in the repository\n" +                          "                             "                            "   'revision'   specified or implied revision\n"                            "                             "                            "   'last-changed-revision'\n" @@ -460,7 +380,13 @@ const apr_getopt_option_t svn_cl__options[] =                            "                             "                            "                author of 'last-changed-revision'\n"                            "                             " -                          "   'wc-root'    root of TARGET's working copy")}, +                          "   'wc-root'    root of TARGET's working copy\n" +                          "                             " +                          "   'schedule'   'normal','add','delete','replace'\n" +                          "                             " +                          "   'depth'      checkout depth of TARGET in WC\n" +                          "                             " +                          "   'changelist' changelist of TARGET in WC")},    {"adds-as-modification", opt_adds_as_modification, 0,                         N_("Local additions are merged with incoming additions\n" @@ -474,9 +400,13 @@ const apr_getopt_option_t svn_cl__options[] =    {"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")}, +  {"drop", opt_drop, 0, +                       N_("drop shelf after successful unshelve")}, + +  {"x-viewspec", opt_viewspec, 1, +                       N_("print the working copy layout, formatted according\n" +                          "                             " +                          "to ARG: 'classic' or 'svn11'")},    /* Long-opt Aliases     * @@ -515,100 +445,120 @@ const int svn_cl__global_options[] =    opt_config_dir, opt_config_options, 0  }; -/* Options for giving a log message.  (Some of these also have other uses.) - */ -#define SVN_CL__LOG_MSG_OPTIONS 'm', 'F', \ -                                opt_force_log, \ -                                opt_editor_cmd, \ -                                opt_encoding, \ -                                opt_with_revprop - -const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] = +static const svn_opt_subcommand_desc3_t +svn_cl__cmd_table_main[] =  { -  { "add", svn_cl__add, {0}, N_ -    ("Put files and directories under version control, scheduling\n" -     "them for addition to repository.  They will be added in next commit.\n" -     "usage: add PATH...\n"), +  { "add", svn_cl__add, {0}, {N_( +     "Put new files and directories under version control.\n" +     "usage: add PATH...\n" +     "\n"), N_( +     "  Schedule unversioned PATHs for addition, so they will become versioned and\n" +     "  be added to the repository in the next commit. Recurse into directories by\n" +     "  default (see the --depth option).\n" +     "\n"), N_( +     "  The 'svn add' command is only necessary for files and directories that are\n" +     "  not yet under version control. Unversioned files and directories can be\n" +     "  identified with 'svn status' (see 'svn help status').\n" +     "\n"), N_( +     "  The effects of 'svn add' can be undone with 'svn revert' before the addition\n" +     "  has been committed. Once committed, a path can be removed from version\n" +     "  control with 'svn delete', and in some circumstances by running a reverse-\n" +     "  merge (see 'svn help merge' for details).\n" +     "\n"), N_( +     "  With --force, add all the unversioned paths found in PATHs and ignore the\n" +     "  rest; otherwise, error out if any specified paths are already versioned.\n" +     "\n"), N_( +     "  The selection of items to add may be influenced by the 'ignores' feature.\n" +     "  Properties may be attached to the items as configured by the 'auto-props'\n" +     "  feature.\n" +    )},      {opt_targets, 'N', opt_depth, 'q', opt_force, opt_no_ignore, opt_autoprops,       opt_no_autoprops, opt_parents }, -     {{opt_parents, N_("add intermediate parents")}} }, - -  { "auth", svn_cl__auth, {0}, N_ -   ("Manage cached authentication credentials.\n" -    "usage: 1. svn auth [PATTERN ...]\n" -    "usage: 2. svn auth --remove PATTERN [PATTERN ...]\n" -    "\n" -    "  With no arguments, list all cached authentication credentials.\n" -    "  Authentication credentials include usernames, passwords,\n" -    "  SSL certificates, and SSL client-certificate passphrases.\n" -    "  If PATTERN is specified, only list credentials with attributes matching one\n" -    "  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 if they\n" -    "  match all specified patterns. Patterns are matched case-sensitively and may\n" -    "  contain glob wildcards:\n" -    "    ?      matches any single character\n" -    "    *      matches a sequence of arbitrary characters\n" -    "    [abc]  matches any of the characters listed inside the brackets\n" -    "  Note that wildcards will usually need to be quoted or escaped on the\n" -    "  command line because many command shells will interfere by trying to\n" -    "  expand them.\n"), +    {{opt_parents, N_("add intermediate parents")}, +     {'N', N_("obsolete; same as --depth=empty")}, +     {opt_force, N_("ignore already versioned paths")}} }, + +  { "auth", svn_cl__auth, {0}, {N_( +     "Manage cached authentication credentials.\n" +     "usage: 1. svn auth [PATTERN ...]\n" +     "       2. svn auth --remove PATTERN [PATTERN ...]\n" +     "\n"), N_( +     "  With no arguments, list all cached authentication credentials.\n" +     "  Authentication credentials include usernames, passwords,\n" +     "  SSL certificates, and SSL client-certificate passphrases.\n" +     "  If PATTERN is specified, only list credentials with attributes matching one\n" +     "  or more patterns. With the --remove option, remove cached authentication\n" +     "  credentials matching one or more patterns.\n" +     "\n"), 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" +     "    *      matches a sequence of arbitrary characters\n" +     "    [abc]  matches any of the characters listed inside the brackets\n" +     "  Note that wildcards will usually need to be quoted or escaped on the\n" +     "  command line because many command shells will interfere by trying to\n" +     "  expand them.\n" +    )},      { opt_remove, opt_show_passwords },      { {opt_remove, N_("remove matching authentication credentials")} }      }, -  { "blame", svn_cl__blame, {"praise", "annotate", "ann"}, N_ -    ("Show when each line of a file was last (or\n" +  { "blame", svn_cl__blame, {"praise", "annotate", "ann"}, {N_( +     "Show when each line of a file was last (or\n"       "next) changed.\n"       "usage: blame [-rM:N] TARGET[@REV]...\n" -     "\n" +     "\n"), N_(       "  Annotate each line of a file with the revision number and author of the\n"       "  last change (or optionally the next change) to that line.\n" -     "\n" +     "\n"), N_(       "  With no revision range (same as -r0:REV), or with '-r M:N' where M < N,\n"       "  annotate each line that is present in revision N of the file, with\n"       "  the last revision at or before rN that changed or added the line,\n"       "  looking back no further than rM.\n" -     "\n" +     "\n"), N_(       "  With a reverse revision range '-r M:N' where M > N,\n"       "  annotate each line that is present in revision N of the file, with\n"       "  the next revision after rN that changed or deleted the line,\n"       "  looking forward no further than rM.\n" -     "\n" +     "\n"), N_(       "  If specified, REV determines in which revision the target is first\n"       "  looked up.\n" -     "\n" -     "  Write the annotated result to standard output.\n"), +     "\n"), N_( +     "  Write the annotated result to standard output.\n" +    )},      {'r', 'v', 'g', opt_incremental, opt_xml, 'x', opt_force} }, -  { "cat", svn_cl__cat, {0}, N_ -    ("Output the content of specified files or URLs.\n" +  { "cat", svn_cl__cat, {0}, {N_( +     "Output the content of specified files or URLs.\n"       "usage: cat TARGET[@REV]...\n" -     "\n" +     "\n"), N_(       "  If specified, REV determines in which revision the target is first\n" -     "  looked up.\n"), +     "  looked up.\n" +    )},      {'r', opt_ignore_keywords} }, -  { "changelist", svn_cl__changelist, {"cl"}, N_ -    ("Associate (or dissociate) changelist CLNAME with the named files.\n" +  { "changelist", svn_cl__changelist, {"cl"}, {N_( +     "Associate (or dissociate) changelist CLNAME with the named\n" +     "files.\n"       "usage: 1. changelist CLNAME PATH...\n" -     "       2. changelist --remove PATH...\n"), +     "       2. changelist --remove PATH...\n" +    )},      { 'q', 'R', opt_depth, opt_remove, opt_targets, opt_changelist} }, -  { "checkout", svn_cl__checkout, {"co"}, N_ -    ("Check out a working copy from a repository.\n" +  { "checkout", svn_cl__checkout, {"co"}, {N_( +     "Check out a working copy from a repository.\n"       "usage: checkout URL[@REV]... [PATH]\n" -     "\n" +     "\n"), N_(       "  If specified, REV determines in which revision the URL is first\n"       "  looked up.\n" -     "\n" +     "\n"), N_(       "  If PATH is omitted, the basename of the URL will be used as\n"       "  the destination. If multiple URLs are given each will be checked\n"       "  out into a sub-directory of PATH, with the name of the sub-directory\n"       "  being the basename of the URL.\n" -     "\n" +     "\n"), N_(       "  If --force is used, unversioned obstructing paths in the working\n"       "  copy destination do not automatically cause the check out to fail.\n"       "  If the obstructing path is the same type (file or directory) as the\n" @@ -619,62 +569,67 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  obstruction and the repository are treated like a local modification\n"       "  to the working copy.  All properties from the repository are applied\n"       "  to the obstructing path.\n" -     "\n" +     "\n"), N_(       "  See also 'svn help update' for a list of possible characters\n" -     "  reporting the action taken.\n"), -    {'r', 'q', 'N', opt_depth, opt_force, opt_ignore_externals} }, +     "  reporting the action taken.\n" +    )}, +    {'r', 'q', 'N', opt_depth, opt_force, opt_ignore_externals}, +    {{'N', N_("obsolete; same as --depth=files")}} }, -  { "cleanup", svn_cl__cleanup, {0}, N_ -    ("Either recover from an interrupted operation that left the working copy locked,\n" -     "or remove unwanted files.\n" +  { "cleanup", svn_cl__cleanup, {0}, {N_( +     "Either recover from an interrupted operation that left the working\n" +     "copy locked, 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" +     "\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" +     "\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" +     "\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" +     "\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"), +     "    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_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, 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" -       "  fixed revision.\n"), + +  { "commit", svn_cl__commit, {"ci"}, {N_( +     "Send changes from your working copy to the repository.\n" +     "usage: commit [PATH...]\n" +     "\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"), N_( +     "  If any targets are (or contain) locked items, those will be\n" +     "  unlocked after a successful commit, unless --no-unlock is given.\n" +     "\n"), N_( +     "  If --include-externals is given, also commit file and directory\n" +     "  externals reached by recursion. Do not commit externals with a\n" +     "  fixed revision.\n" +    )},      {'q', 'N', opt_depth, opt_targets, opt_no_unlock, SVN_CL__LOG_MSG_OPTIONS, -     opt_changelist, opt_keep_changelists, opt_include_externals} }, +     opt_changelist, opt_keep_changelists, opt_include_externals}, +    {{'N', N_("obsolete; same as --depth=empty")}} }, -  { "copy", svn_cl__copy, {"cp"}, N_ -    ("Copy files and directories in a working copy or repository.\n" +  { "copy", svn_cl__copy, {"cp"}, {N_( +     "Copy files and directories in a working copy or repository.\n"       "usage: copy SRC[@REV]... DST\n" -     "\n" +     "\n"), N_(       "  SRC and DST can each be either a working copy (WC) path or URL:\n"       "    WC  -> WC:   copy and schedule for addition (with history)\n"       "    WC  -> URL:  immediately commit a copy of WC to URL\n" @@ -683,33 +638,35 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  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" +     "\n"), N_(       "  WARNING: For compatibility with previous versions of Subversion,\n"       "  copies performed using two working copy paths (WC -> WC) will not\n"       "  contact the repository.  As such, they may not, by default, be able\n"       "  to propagate merge tracking information from the source of the copy\n" -     "  to the destination.\n"), +     "  to the destination.\n" +    )},      {'r', 'q', opt_ignore_externals, opt_parents, SVN_CL__LOG_MSG_OPTIONS,       opt_pin_externals} }, -  { "delete", svn_cl__delete, {"del", "remove", "rm"}, N_ -    ("Remove files and directories from version control.\n" +  { "delete", svn_cl__delete, {"del", "remove", "rm"}, {N_( +     "Remove files and directories from version control.\n"       "usage: 1. delete PATH...\n"       "       2. delete URL...\n" -     "\n" +     "\n"), N_(       "  1. Each item specified by a PATH is scheduled for deletion upon\n"       "    the next commit.  Files, and directories that have not been\n"       "    committed, are immediately removed from the working copy\n"       "    unless the --keep-local option is given.\n"       "    PATHs that are, or contain, unversioned or modified items will\n"       "    not be removed unless the --force or --keep-local option is given.\n" -     "\n" +     "\n"), N_(       "  2. Each item specified by a URL is deleted from the repository\n" -     "    via an immediate commit.\n"), +     "    via an immediate commit.\n" +    )},      {opt_force, 'q', opt_targets, SVN_CL__LOG_MSG_OPTIONS, opt_keep_local} }, -  { "diff", svn_cl__diff, {"di"}, N_ -    ("Display local changes or differences between two revisions or paths.\n" +  { "diff", svn_cl__diff, {"di"}, {N_( +     "Display local changes or differences between two revisions or paths.\n"       "usage: 1. diff\n"       "       2. diff [-c M | -r N[:M]] [TARGET[@REV]...]\n"       "       3. diff [-r N[:M]] --old=OLD-TGT[@OLDREV] [--new=NEW-TGT[@NEWREV]] \\\n" @@ -717,16 +674,16 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "       4. diff OLD-URL[@OLDREV] NEW-URL[@NEWREV]\n"       "       5. diff OLD-URL[@OLDREV] NEW-PATH[@NEWREV]\n"       "       6. diff OLD-PATH[@OLDREV] NEW-URL[@NEWREV]\n" -     "\n" +     "\n"), N_(       "  1. Use just 'svn diff' to display local modifications in a working copy.\n" -     "\n" +     "\n"), N_(       "  2. Display the changes made to TARGETs as they are seen in REV between\n"       "     two revisions.  TARGETs may be all working copy paths or all URLs.\n"       "     If TARGETs are working copy paths, N defaults to BASE and M to the\n"       "     working copy; if URLs, N must be specified and M defaults to HEAD.\n"       "     The '-c M' option is equivalent to '-r N:M' where N = M-1.\n"       "     Using '-c -M' does the reverse: '-r M:N' where N = M-1.\n" -     "\n" +     "\n"), N_(       "  3. Display the differences between OLD-TGT as it was seen in OLDREV and\n"       "     NEW-TGT as it was seen in NEWREV.  PATHs, if given, are relative to\n"       "     OLD-TGT and NEW-TGT and restrict the output to differences for those\n" @@ -735,164 +692,178 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "     to N, -r N:M makes OLDREV default to N and NEWREV default to M.\n"       "     If OLDREV or NEWREV are not specified, they default to WORKING for\n"       "     working copy targets and to HEAD for URL targets.\n" -     "\n" +     "\n"), N_(       "     Either or both OLD-TGT and NEW-TGT may also be paths to unversioned\n"       "     targets. Revisions cannot be specified for unversioned targets.\n"       "     Both targets must be of the same node kind (file or directory).\n"       "     Diffing unversioned targets against URL targets is not supported.\n" -     "\n" +     "\n"), N_(       "  4. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-URL[@NEWREV]'\n"       "  5. Shorthand for 'svn diff --old=OLD-URL[@OLDREV] --new=NEW-PATH[@NEWREV]'\n" -     "  6. Shorthand for 'svn diff --old=OLD-PATH[@OLDREV] --new=NEW-URL[@NEWREV]'\n"), +     "  6. Shorthand for 'svn diff --old=OLD-PATH[@OLDREV] --new=NEW-URL[@NEWREV]'\n" +    )},      {'r', 'c', opt_old_cmd, opt_new_cmd, 'N', opt_depth, opt_diff_cmd,       opt_internal_diff, 'x', opt_no_diff_added, opt_no_diff_deleted,       opt_ignore_properties, opt_properties_only,       opt_show_copies_as_adds, opt_notice_ancestry, opt_summarize, opt_changelist, -     opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible} }, -  { "export", svn_cl__export, {0}, N_ -    ("Create an unversioned copy of a tree.\n" +     opt_force, opt_xml, opt_use_git_diff_format, opt_patch_compatible}, +    {{'N', N_("obsolete; same as --depth=files")}} }, + +  { "export", svn_cl__export, {0}, {N_( +     "Create an unversioned copy of a tree.\n"       "usage: 1. export [-r REV] URL[@PEGREV] [PATH]\n"       "       2. export [-r REV] PATH1[@PEGREV] [PATH2]\n" -     "\n" +     "\n"), N_(       "  1. Exports a clean directory tree from the repository specified by\n"       "     URL, at revision REV if it is given, otherwise at HEAD, into\n"       "     PATH. If PATH is omitted, the last component of the URL is used\n"       "     for the local directory name.\n" -     "\n" +     "\n"), N_(       "  2. Exports a clean directory tree from the working copy specified by\n"       "     PATH1, at revision REV if it is given, otherwise at WORKING, into\n"       "     PATH2.  If PATH2 is omitted, the last component of the PATH1 is used\n"       "     for the local directory name. If REV is not specified, all local\n"       "     changes will be preserved.  Files not under version control will\n"       "     not be copied.\n" -     "\n" +     "\n"), N_(       "  If specified, PEGREV determines in which revision the target is first\n" -     "  looked up.\n"), +     "  looked up.\n" +    )},      {'r', 'q', 'N', opt_depth, opt_force, opt_native_eol, opt_ignore_externals, -     opt_ignore_keywords} }, - -  { "help", svn_cl__help, {"?", "h"}, N_ -    ("Describe the usage of this program or its subcommands.\n" -     "usage: help [SUBCOMMAND...]\n"), -    {0} }, +     opt_ignore_keywords}, +    {{'N', N_("obsolete; same as --depth=files")}} }, + +  { "help", svn_cl__help, {"?", "h"}, {N_( +     "Describe the usage of this program or its subcommands.\n" +     "usage: help [SUBCOMMAND...]\n" +    )}, +    {'v'}, +    {{'v', N_("also show experimental subcommands and options")}} },    /* This command is also invoked if we see option "--help", "-h" or "-?". */ -  { "import", svn_cl__import, {0}, N_ -    ("Commit an unversioned file or tree into the repository.\n" +  { "import", svn_cl__import, {0}, {N_( +     "Commit an unversioned file or tree into the repository.\n"       "usage: import [PATH] URL\n" -     "\n" +     "\n"), N_(       "  Recursively commit a copy of PATH to URL.\n"       "  If PATH is omitted '.' is assumed.\n"       "  Parent directories are created as necessary in the repository.\n"       "  If PATH is a directory, the contents of the directory are added\n"       "  directly under URL.\n"       "  Unversionable items such as device files and pipes are ignored\n" -     "  if --force is specified.\n"), +     "  if --force is specified.\n" +    )},      {'q', 'N', opt_depth, opt_autoprops, opt_force, opt_no_autoprops, -     SVN_CL__LOG_MSG_OPTIONS, opt_no_ignore} }, +     SVN_CL__LOG_MSG_OPTIONS, opt_no_ignore}, +    {{'N', N_("obsolete; same as --depth=files")}} }, -  { "info", svn_cl__info, {0}, N_ -    ("Display information about a local or remote item.\n" +  { "info", svn_cl__info, {0}, {N_( +     "Display information about a local or remote item.\n"       "usage: info [TARGET[@REV]...]\n" -     "\n" +     "\n"), N_(       "  Print information about each TARGET (default: '.').\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" +     "\n"), N_(       "  With --show-item, print only the value of one item of information\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} +     "  about TARGET.\n" +     "\n"), N_( +     "  EXPERIMENTAL:\n" +     "  With --x-viewspec, print the working copy layout.\n" +    )}, +    {'r', 'R', 'H', opt_depth, opt_targets, opt_incremental, opt_xml, +     opt_changelist, opt_include_externals, opt_show_item, opt_no_newline, +     opt_viewspec}, +    {{'H', N_("show file sizes with base-2 unit suffixes\n" +              "                             " +              "(Byte, Kilobyte, Megabyte, Gigabyte, Terabyte\n" +              "                             " +              "and Petabyte), limiting the number of digits\n" +              "                             " +              "to three or less")}}    },    { "list", svn_cl__list, {"ls"}, -#if defined(WIN32) -    N_ -    ("List directory entries in the repository.\n" +    {N_( +     "List directory entries in the repository.\n"       "usage: list [TARGET[@REV]...]\n" -     "\n" +     "\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" +     "\n"), N_(       "  The default TARGET is '.', meaning the repository URL of the current\n"       "  working directory.\n" -     "\n" +     "\n"), +#if defined(WIN32) +     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"), +     "\n"),  #else -    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" +     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" +     "\n"), +#endif +     N_(       "  With --verbose, the following fields will be shown for each item:\n" -     "\n" +     "\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"), -#endif -    {'r', 'v', 'R', opt_depth, opt_incremental, opt_xml, -     opt_include_externals, opt_search}, }, - -  { "lock", svn_cl__lock, {0}, N_ -    ("Lock working copy paths or URLs in the repository, so that\n" +     "    Date and time of the last commit\n" +    )}, +    {'r', 'v', 'R', 'H', opt_depth, opt_incremental, opt_xml, +     opt_include_externals, opt_search}, +    {{'H', N_("with --verbose, show file sizes with base-2\n" +              "                             " +              "unit suffixes (Byte, Kilobyte, Megabyte,\n" +              "                             " +              "Gigabyte, Terabyte and Petabyte), limiting\n" +              "                             " +              "the number of digits to three or less")}} }, + +  { "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 a lock from another user or working copy.\n"), +     "\n"), N_( +     "  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, N_("steal locks")}} }, -  { "log", svn_cl__log, {0}, N_ -    ("Show the log messages for a set of revision(s) and/or path(s).\n" +  { "log", svn_cl__log, {0}, {N_( +     "Show the log messages for a set of revision(s) and/or path(s).\n"       "usage: 1. log [PATH][@REV]\n"       "       2. log URL[@REV] [PATH...]\n" -     "\n" +     "\n"), N_(       "  1. Print the log messages for the URL corresponding to PATH\n"       "     (default: '.'). If specified, REV is the revision in which the\n"       "     URL is first looked up, and the default revision range is REV:1.\n"       "     If REV is not specified, the default revision range is BASE:1,\n"       "     since the URL might not exist in the HEAD revision.\n" -     "\n" +     "\n"), N_(       "  2. Print the log messages for the PATHs (default: '.') under URL.\n"       "     If specified, REV is the revision in which the URL is first\n"       "     looked up, and the default revision range is REV:1; otherwise,\n"       "     the URL is looked up in HEAD, and the default revision range is\n"       "     HEAD:1.\n" -     "\n" +     "\n"), N_(       "  Multiple '-c' or '-r' options may be specified (but not a\n"       "  combination of '-c' and '-r' options), and mixing of forward and\n"       "  reverse ranges is allowed.\n" -     "\n" +     "\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" @@ -904,18 +875,18 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  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" +     "\n"), N_(       "  With -q, don't print the log message body itself (note that this is\n"       "  compatible with -v).\n" -     "\n" +     "\n"), N_(       "  Each log message is printed just once, even if more than one of the\n"       "  affected paths for that revision were explicitly requested.  Logs\n"       "  follow copy history by default.  Use --stop-on-copy to disable this\n"       "  behavior, which can be useful for determining branchpoints.\n" -     "\n" +     "\n"), N_(       "  The --depth option is only valid in combination with the --diff option\n"       "  and limits the scope of the displayed diff to the specified depth.\n" -     "\n" +     "\n"), N_(       "  If the --search option is used, log messages are displayed only if the\n"       "  provided search pattern matches any of the author, date, log message\n"       "  text (unless --quiet is used), or, if the --verbose option is also\n" @@ -932,37 +903,42 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  If --limit is used in combination with --search, --limit restricts the\n"       "  number of log messages searched, rather than restricting the output\n"       "  to a particular number of matching log messages.\n" -     "\n" +     "\n"), N_(       "  Examples:\n" -     "\n" +     "\n"), N_(       "    Show the latest 5 log messages for the current working copy\n"       "    directory and display paths changed in each commit:\n"       "      svn log -l 5 -v\n" -     "\n" +     "\n"), N_(       "    Show the log for bar.c as of revision 42:\n"       "      svn log bar.c@42\n" -     "\n" +     "\n"), N_(       "    Show log messages and diffs for each commit to foo.c:\n"       "      svn log --diff http://www.example.com/repo/project/foo.c\n"       "    (Because the above command uses a full URL it does not require\n"       "     a working copy.)\n" -     "\n" +     "\n"), N_(       "    Show log messages for the children foo.c and bar.c of the directory\n"       "    '/trunk' as it appeared in revision 50, using the ^/ URL shortcut:\n"       "      svn log ^/trunk@50 foo.c bar.c\n" -     "\n" +     "\n"), N_(       "    Show the log messages for any incoming changes to foo.c during the\n"       "    next 'svn update':\n"       "      svn log -r BASE:HEAD foo.c\n" -     "\n" +     "\n"), N_(       "    Show the log message for the revision in which /branches/foo\n"       "    was created:\n"       "      svn log --stop-on-copy --limit 1 -r0:HEAD ^/branches/foo\n" -     "\n" +     "\n"), N_( +     "    Show all log messages for commits between the tags ^/tags/2.0 and\n" +     "    ^/tags/3.0; assuming that tag 2.0 was created in revision 100:\n" +     "      svn log -rHEAD:100 ^/tags/3.0\n" +     "\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"), +     "       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, @@ -972,8 +948,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       {'v', N_("also print all affected paths")},       {'q', N_("do not print the log message")}} }, -  { "merge", svn_cl__merge, {0}, N_ -    ( /* For this large section, let's keep it unindented for easier +  { "merge", svn_cl__merge, {0}, {N_( +      /* For this large section, let's keep it unindented for easier         * viewing/editing. It has been vim-treated with a textwidth=75 and 'gw'         * (with quotes and newlines removed). */  "Merge changes into a working copy.\n" @@ -983,54 +959,54 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =  "          (the 'cherry-pick' merge)\n"  "       3. merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n"  "          (the '2-URL' merge)\n" -"\n" +"\n"), N_(  "  1. This form, with one source path and no revision range, is called\n"  "     a 'complete' merge:\n" -"\n" +"\n"), N_(  "       svn merge SOURCE[@REV] [TARGET_WCPATH]\n" -"\n" +"\n"), N_(  "     The complete merge is used for the 'sync' and 'reintegrate' merges\n"  "     in the 'feature branch' pattern described below. It finds all the\n"  "     changes on the source branch that have not already been merged to the\n"  "     target branch, and merges them into the working copy. Merge tracking\n"  "     is used to know which changes have already been merged.\n" -"\n" +"\n"), N_(  "     SOURCE specifies the branch from where the changes will be pulled, and\n"  "     TARGET_WCPATH specifies a working copy of the target branch to which\n"  "     the changes will be applied. Normally SOURCE and TARGET_WCPATH should\n"  "     each correspond to the root of a branch. (If you want to merge only a\n"  "     subtree, then the subtree path must be included in both SOURCE and\n"  "     TARGET_WCPATH; this is discouraged, to avoid subtree mergeinfo.)\n" -"\n" +"\n"), N_(  "     SOURCE is usually a URL. The optional '@REV' specifies both the peg\n"  "     revision of the URL and the latest revision that will be considered\n"  "     for merging; if REV is not specified, the HEAD revision is assumed. If\n"  "     SOURCE is a working copy path, the corresponding URL of the path is\n"  "     used, and the default value of 'REV' is the base revision (usually the\n"  "     revision last updated to).\n" -"\n" +"\n"), N_(  "     TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"  "     assumed. There are some special cases:\n" -"\n" +"\n"), N_(  "       - If SOURCE is a URL:\n" -"\n" +"\n"), N_(  "           - If the basename of the URL and the basename of '.' are the\n"  "             same, then the differences are applied to '.'. Otherwise,\n"  "             if a file with the same basename as that of the URL is found\n"  "             within '.', then the differences are applied to that file.\n"  "             In all other cases, the target defaults to '.'.\n" -"\n" +"\n"), N_(  "       - If SOURCE is a working copy path:\n" -"\n" +"\n"), N_(  "           - If the source is a file, then differences are applied to that\n"  "             file (useful for reverse-merging earlier changes). Otherwise,\n"  "             if the source is a directory, then the target defaults to '.'.\n" -"\n" +"\n"), N_(  "     In normal usage the working copy should be up to date, at a single\n"  "     revision, with no local modifications and no switched subtrees.\n" -"\n" +"\n"), N_(  "       - The 'Feature Branch' Merging Pattern -\n" -"\n" +"\n"), N_(  "     In this commonly used work flow, known also as the 'development\n"  "     branch' pattern, a developer creates a branch and commits a series of\n"  "     changes that implement a new feature. The developer periodically\n" @@ -1038,17 +1014,17 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =  "     development branch up to date with those changes. When the feature is\n"  "     complete, the developer performs a merge from the feature branch to\n"  "     the parent branch to re-integrate the changes.\n" -"\n" +"\n"), N_(  "         parent --+----------o------o-o-------------o--\n"  "                   \\            \\           \\      /\n"  "                    \\          merge      merge  merge\n"  "                     \\            \\           \\  /\n"  "         feature      +--o-o-------o----o-o----o-------\n" -"\n" +"\n"), N_(  "     A merge from the parent branch to the feature branch is called a\n"  "     'sync' or 'catch-up' merge, and a merge from the feature branch to the\n"  "     parent branch is called a 'reintegrate' merge.\n" -"\n" +"\n"), N_(  "       - Sync Merge Example -\n"  "                                 ............\n"  "                                .            .\n" @@ -1058,7 +1034,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =  "                     \\                         v\n"  "         feature      +------------------------o-----\n"  "                             r100            r200\n" -"\n" +"\n"), N_(  "     Subversion will locate all the changes on 'trunk' that have not yet\n"  "     been merged into the 'feature' branch. In this case that is a single\n"  "     range, r100:200. In the diagram above, L marks the left side (trunk@100)\n" @@ -1066,162 +1042,162 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =  "     difference between L and R will be applied to the target working copy\n"  "     path. In this case, the working copy is a clean checkout of the entire\n"  "     'feature' branch.\n" -"\n" +"\n"), N_(  "     To perform this sync merge, have a clean working copy of the feature\n"  "     branch and run the following command in its top-level directory:\n" -"\n" +"\n"), N_(  "         svn merge ^/trunk\n" -"\n" +"\n"), N_(  "     Note that the merge is now only in your local working copy and still\n"  "     needs to be committed to the repository so that it can be seen by\n"  "     others. You can review the changes and you may have to resolve\n"  "     conflicts before you commit the merge.\n" -"\n" +"\n"), N_(  "       - Reintegrate Merge Example -\n" -"\n" +"\n"), N_(  "     The feature branch was last synced with trunk up to revision X. So the\n"  "     difference between trunk@X and feature@HEAD contains the complete set\n"  "     of changes that implement the feature, and no other changes. These\n"  "     changes are applied to trunk.\n" -"\n" +"\n"), N_(  "                    rW                   rX\n"  "         trunk ------+--------------------L------------------o\n"  "                      \\                    .                 ^\n"  "                       \\                    .............   /\n"  "                        \\                                . /\n"  "         feature         +--------------------------------R\n" -"\n" +"\n"), N_(  "     In the diagram above, L marks the left side (trunk@X) and R marks the\n"  "     right side (feature@HEAD) of the merge. The difference between the\n"  "     left and right side is merged into trunk, the target.\n" -"\n" +"\n"), N_(  "     To perform the merge, have a clean working copy of trunk and run the\n"  "     following command in its top-level directory:\n" -"\n" +"\n"), N_(  "         svn merge ^/feature\n" -"\n" +"\n"), N_(  "     To prevent unnecessary merge conflicts, a reintegrate merge requires\n"  "     that TARGET_WCPATH is not a mixed-revision working copy, has no local\n"  "     modifications, and has no switched subtrees.\n" -"\n" +"\n"), N_(  "     A reintegrate merge also requires that the source branch is coherently\n"  "     synced with the target -- in the above example, this means that all\n"  "     revisions between the branch point W and the last merged revision X\n"  "     are merged to the feature branch, so that there are no unmerged\n"  "     revisions in-between.\n" -"\n" -"\n" +"\n"), N_( +"\n"), N_(  "  2. This form is called a 'cherry-pick' merge:\n" -"\n" +"\n"), N_(  "       svn merge [-c M[,N...] | -r N:M ...] SOURCE[@REV] [TARGET_WCPATH]\n" -"\n" +"\n"), N_(  "     A cherry-pick merge is used to merge specific revisions (or revision\n"  "     ranges) from one branch to another. By default, this uses merge\n"  "     tracking to automatically skip any revisions that have already been\n"  "     merged to the target; you can use the --ignore-ancestry option to\n"  "     disable such skipping.\n" -"\n" +"\n"), N_(  "     SOURCE is usually a URL. The optional '@REV' specifies only the peg\n"  "     revision of the URL and does not affect the merge range; if REV is not\n"  "     specified, the HEAD revision is assumed. If SOURCE is a working copy\n"  "     path, the corresponding URL of the path is used, and the default value\n"  "     of 'REV' is the base revision (usually the revision last updated to).\n" -"\n" +"\n"), N_(  "     TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"  "     assumed. The special cases noted above in the 'complete' merge form\n"  "     also apply here.\n" -"\n" +"\n"), N_(  "     The revision ranges to be merged are specified by the '-r' and/or '-c'\n"  "     options. '-r N:M' refers to the difference in the history of the\n"  "     source branch between revisions N and M. You can use '-c M' to merge\n"  "     single revisions: '-c M' is equivalent to '-r <M-1>:M'. Each such\n"  "     difference is applied to TARGET_WCPATH.\n" -"\n" +"\n"), N_(  "     If the mergeinfo in TARGET_WCPATH indicates that revisions within the\n"  "     range were already merged, changes made in those revisions are not\n"  "     merged again. If needed, the range is broken into multiple sub-ranges,\n"  "     and each sub-range is merged separately.\n" -"\n" +"\n"), N_(  "     A 'reverse range' can be used to undo changes. For example, when\n"  "     source and target refer to the same branch, a previously committed\n"  "     revision can be 'undone'. In a reverse range, N is greater than M in\n"  "     '-r N:M', or the '-c' option is used with a negative number: '-c -M'\n"  "     is equivalent to '-r M:<M-1>'. Undoing changes like this is also known\n"  "     as performing a 'reverse merge'.\n" -"\n" +"\n"), N_(  "     Multiple '-c' and/or '-r' options may be specified and mixing of\n"  "     forward and reverse ranges is allowed.\n" -"\n" +"\n"), N_(  "       - Cherry-pick Merge Example -\n" -"\n" +"\n"), N_(  "     A bug has been fixed on trunk in revision 50. This fix needs to\n"  "     be merged from trunk onto the release branch.\n" -"\n" +"\n"), N_(  "            1.x-release  +-----------------------o-----\n"  "                        /                        ^\n"  "                       /                         |\n"  "                      /                          |\n"  "         trunk ------+--------------------------LR-----\n"  "                                                r50\n" -"\n" +"\n"), N_(  "     In the above diagram, L marks the left side (trunk@49) and R marks the\n"  "     right side (trunk@50) of the merge. The difference between the left\n"  "     and right side is applied to the target working copy path.\n" -"\n" +"\n"), N_(  "     Note that the difference between revision 49 and 50 is exactly those\n"  "     changes that were committed in revision 50, not including changes\n"  "     committed in revision 49.\n" -"\n" +"\n"), N_(  "     To perform the merge, have a clean working copy of the release branch\n"  "     and run the following command in its top-level directory; remember\n"  "     that the default target is '.':\n" -"\n" +"\n"), N_(  "         svn merge -c50 ^/trunk\n" -"\n" +"\n"), N_(  "     You can also cherry-pick several revisions and/or revision ranges:\n" -"\n" +"\n"), N_(  "         svn merge -c50,54,60 -r65:68 ^/trunk\n" -"\n" -"\n" +"\n"), N_( +"\n"), N_(  "  3. This form is called a '2-URL merge':\n" -"\n" +"\n"), N_(  "       svn merge SOURCE1[@REV1] SOURCE2[@REV2] [TARGET_WCPATH]\n" -"\n" +"\n"), N_(  "     You should use this merge variant only if the other variants do not\n"  "     apply to your situation, as this variant can be quite complex to\n"  "     master.\n" -"\n" +"\n"), N_(  "     Two source URLs are specified, identifying two trees on the same\n"  "     branch or on different branches. The trees are compared and the\n"  "     difference from SOURCE1@REV1 to SOURCE2@REV2 is applied to the\n"  "     working copy of the target branch at TARGET_WCPATH. The target\n"  "     branch may be the same as one or both sources, or different again.\n"  "     The three branches involved can be completely unrelated.\n" -"\n" +"\n"), N_(  "     TARGET_WCPATH is a working copy path; if omitted, '.' is generally\n"  "     assumed. The special cases noted above in the 'complete' merge form\n"  "     also apply here.\n" -"\n" +"\n"), N_(  "     SOURCE1 and/or SOURCE2 can also be specified as a working copy path,\n"  "     in which case the merge source URL is derived from the working copy.\n" -"\n" +"\n"), N_(  "       - 2-URL Merge Example -\n" -"\n" +"\n"), N_(  "     Two features have been developed on separate branches called 'foo' and\n"  "     'bar'. It has since become clear that 'bar' should be combined with\n"  "     the 'foo' branch for further development before reintegration.\n" -"\n" +"\n"), N_(  "     Although both feature branches originate from trunk, they are not\n"  "     directly related -- one is not a direct copy of the other. A 2-URL\n"  "     merge is necessary.\n" -"\n" +"\n"), N_(  "     The 'bar' branch has been synced with trunk up to revision 500.\n"  "     (If this revision number is not known, it can be located using the\n"  "     'svn log' and/or 'svn mergeinfo' commands.)\n"  "     The difference between trunk@500 and bar@HEAD contains the complete\n"  "     set of changes related to feature 'bar', and no other changes. These\n"  "     changes are applied to the 'foo' branch.\n" -"\n" +"\n"), N_(  "                           foo  +-----------------------------------o\n"  "                               /                                    ^\n"  "                              /                                    /\n" @@ -1231,41 +1207,41 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =  "                       \\                        ............   /\n"  "                        \\                                   . /\n"  "                    bar  +-----------------------------------R\n" -"\n" +"\n"), N_(  "     In the diagram above, L marks the left side (trunk@500) and R marks\n"  "     the right side (bar@HEAD) of the merge. The difference between the\n"  "     left and right side is applied to the target working copy path, in\n"  "     this case a working copy of the 'foo' branch.\n" -"\n" +"\n"), N_(  "     To perform the merge, have a clean working copy of the 'foo' branch\n"  "     and run the following command in its top-level directory:\n" -"\n" +"\n"), N_(  "         svn merge ^/trunk@500 ^/bar\n" -"\n" +"\n"), N_(  "     The exact changes applied by a 2-URL merge can be previewed with svn's\n"  "     diff command, which is a good idea to verify if you do not have the\n"  "     luxury of a clean working copy to merge to. In this case:\n" -"\n" +"\n"), N_(  "         svn diff ^/trunk@500 ^/bar@HEAD\n" -"\n" -"\n" +"\n"), N_( +"\n"), N_(  "  The following applies to all types of merges:\n" -"\n" +"\n"), N_(  "  To prevent unnecessary merge conflicts, svn merge requires that\n"  "  TARGET_WCPATH is not a mixed-revision working copy. Running 'svn update'\n"  "  before starting a merge ensures that all items in the working copy are\n"  "  based on the same revision.\n" -"\n" +"\n"), N_(  "  If possible, you should have no local modifications in the merge's target\n"  "  working copy prior to the merge, to keep things simpler. It will be\n"  "  easier to revert the merge and to understand the branch's history.\n" -"\n" +"\n"), N_(  "  Switched sub-paths should also be avoided during merging, as they may\n"  "  cause incomplete merges and create subtree mergeinfo.\n" -"\n" +"\n"), N_(  "  For each merged item a line will be printed with characters reporting the\n"  "  action taken. These characters have the following meaning:\n" -"\n" +"\n"), N_(  "    A  Added\n"  "    D  Deleted\n"  "    U  Updated\n" @@ -1273,15 +1249,15 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =  "    G  Merged\n"  "    E  Existed\n"  "    R  Replaced\n" -"\n" +"\n"), N_(  "  Characters in the first column report about the item itself.\n"  "  Characters in the second column report about properties of the item.\n"  "  A 'C' in the third column indicates a tree conflict, while a 'C' in\n"  "  the first and second columns indicate textual conflicts in files\n"  "  and in property values, respectively.\n" -"\n" +"\n"), N_(  "    - Merge Tracking -\n" -"\n" +"\n"), N_(  "  Subversion uses the svn:mergeinfo property to track merge history. This\n"  "  property is considered at the start of a merge to determine what to merge\n"  "  and it is updated at the conclusion of the merge to describe the merge\n" @@ -1290,78 +1266,82 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =  "  or vice-versa (i.e. if one has originally been created by copying the\n"  "  other). This is verified and enforced when using sync merges and\n"  "  reintegrate merges.\n" -"\n" +"\n"), N_(  "  The --ignore-ancestry option prevents merge tracking and thus ignores\n"  "  mergeinfo, neither considering it nor recording it.\n" -"\n" +"\n"), N_(  "    - Merging from foreign repositories -\n" -"\n" +"\n"), N_(  "  Subversion does support merging from foreign repositories.\n"  "  While all merge source URLs must point to the same repository, the merge\n"  "  target working copy may come from a different repository than the source.\n"  "  However, there are some caveats. Most notably, copies made in the\n"  "  merge source will be transformed into plain additions in the merge\n"  "  target. Also, merge-tracking is not supported for merges from foreign\n" -"  repositories.\n"), +"  repositories.\n" +    )},      {'r', 'c', 'N', opt_depth, 'q', opt_force, opt_dry_run, opt_merge_cmd,       opt_record_only, 'x', opt_ignore_ancestry, opt_accept, opt_reintegrate,       opt_allow_mixed_revisions, 'v'}, -    { { opt_force, N_("force deletions even if deleted contents don't match") } } +    { { opt_force, N_("force deletions even if deleted contents don't match") }, +      {'N', N_("obsolete; same as --depth=files")} }    }, -  { "mergeinfo", svn_cl__mergeinfo, {0}, N_ -    ("Display merge-related information.\n" +  { "mergeinfo", svn_cl__mergeinfo, {0}, {N_( +     "Display merge-related information.\n"       "usage: 1. mergeinfo SOURCE[@REV] [TARGET[@REV]]\n"       "       2. mergeinfo --show-revs=WHICH SOURCE[@REV] [TARGET[@REV]]\n" -     "\n" +     "\n"), N_(       "  1. Summarize the history of merging between SOURCE and TARGET. The graph\n"       "     shows, from left to right:\n"       "       the youngest common ancestor of the branches;\n"       "       the latest full merge in either direction, and thus the common base\n"       "         that will be used for the next complete merge;\n"       "       the repository path and revision number of the tip of each branch.\n" -     "\n" +     "\n"), N_(       "  2. Print the revision numbers on SOURCE that have been merged to TARGET\n"       "     (with --show-revs=merged), or that have not been merged to TARGET\n"       "     (with --show-revs=eligible). Print only revisions in which there was\n"       "     at least one change in SOURCE.\n" -     "\n" +     "\n"), N_(       "     If --revision (-r) is provided, filter the displayed information to\n"       "     show only that which is associated with the revisions within the\n"       "     specified range.  Revision numbers, dates, and the 'HEAD' keyword are\n"       "     valid range values.\n" -     "\n" +     "\n"), N_(       "  SOURCE and TARGET are the source and target branch URLs, respectively.\n"       "  (If a WC path is given, the corresponding base URL is used.) The default\n"       "  TARGET is the current working directory ('.'). REV specifies the revision\n"       "  to be considered the tip of the branch; the default for SOURCE is HEAD,\n"       "  and the default for TARGET is HEAD for a URL or BASE for a WC path.\n" -     "\n" -     "  The depth can be 'empty' or 'infinity'; the default is 'empty'.\n"), +     "\n"), N_( +     "  The depth can be 'empty' or 'infinity'; the default is 'empty'.\n" +    )},      {'r', 'R', 'q', 'v', opt_depth, opt_show_revs, opt_mergeinfo_log,        opt_incremental } }, -  { "mkdir", svn_cl__mkdir, {0}, N_ -    ("Create a new directory under version control.\n" +  { "mkdir", svn_cl__mkdir, {0}, {N_( +     "Create a new directory under version control.\n"       "usage: 1. mkdir PATH...\n"       "       2. mkdir URL...\n" -     "\n" +     "\n"), N_(       "  Create version controlled directories.\n" -     "\n" +     "\n"), N_(       "  1. Each directory specified by a working copy PATH is created locally\n"       "    and scheduled for addition upon the next commit.\n" -     "\n" +     "\n"), N_(       "  2. Each directory specified by a URL is created in the repository via\n"       "    an immediate commit.\n" -     "\n" +     "\n"), N_(       "  In both cases, all the intermediate directories must already exist,\n" -     "  unless the --parents option is given.\n"), +     "  unless the --parents option is given.\n" +    )},      {'q', opt_parents, SVN_CL__LOG_MSG_OPTIONS} }, -  { "move", svn_cl__move, {"mv", "rename", "ren"}, N_ -    ("Move (rename) an item in a working copy or repository.\n" +  { "move", svn_cl__move, {"mv", "rename", "ren"}, {N_( +     "Move (rename) an item in a working copy or repository.\n"       "usage: move SRC... DST\n" -     "\n" +     "\n"), N_(       "  SRC and DST can both be working copy (WC) paths or URLs:\n"       "    WC  -> WC:  move an item in a working copy, as a local change to\n"       "                be committed later (with or without further changes)\n" @@ -1370,28 +1350,29 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  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" +     "\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"), +     "  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" +  { "patch", svn_cl__patch, {0}, {N_( +     "Apply a patch to a working copy.\n"       "usage: patch PATCHFILE [WCPATH]\n" -     "\n" +     "\n"), N_(       "  Apply a unidiff patch in PATCHFILE to the working copy WCPATH.\n"       "  If WCPATH is omitted, '.' is assumed.\n" -     "\n" +     "\n"), N_(       "  A unidiff patch suitable for application to a working copy can be\n"       "  produced with the 'svn diff' command or third-party diffing tools.\n"       "  Any non-unidiff content of PATCHFILE is ignored, except for Subversion\n"       "  property diffs as produced by 'svn diff'.\n" -     "\n" +     "\n"), N_(       "  Changes listed in the patch will either be applied or rejected.\n"       "  If a change does not match at its exact line offset, it may be applied\n"       "  earlier or later in the file if a match is found elsewhere for the\n" @@ -1400,114 +1381,118 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  or more lines of context are ignored when matching the change.\n"       "  If no matching context can be found for a change, the change conflicts\n"       "  and will be written to a reject file with the extension .svnpatch.rej.\n" -     "\n" +     "\n"), N_(       "  For each patched file a line will be printed with characters reporting\n"       "  the action taken. These characters have the following meaning:\n" -     "\n" +     "\n"), N_(       "    A  Added\n"       "    D  Deleted\n"       "    U  Updated\n"       "    C  Conflict\n"       "    G  Merged (with local uncommitted changes)\n" -     "\n" +     "\n"), N_(       "  Changes applied with an offset or fuzz are reported on lines starting\n"       "  with the '>' symbol. You should review such changes carefully.\n" -     "\n" +     "\n"), N_(       "  If the patch removes all content from a file, that file is scheduled\n"       "  for deletion. If the patch creates a new file, that file is scheduled\n"       "  for addition. Use 'svn revert' to undo deletions and additions you\n"       "  do not agree with.\n" -     "\n" +     "\n"), N_(       "  Hint: If the patch file was created with Subversion, it will contain\n"       "        the number of a revision N the patch will cleanly apply to\n"       "        (look for lines like '--- foo/bar.txt        (revision N)').\n"       "        To avoid rejects, first update to the revision N using\n"       "        'svn update -r N', apply the patch, and then update back to the\n"       "        HEAD revision. This way, conflicts can be resolved interactively.\n" -     ), +    )},      {'q', opt_dry_run, opt_strip, opt_reverse_diff,       opt_ignore_whitespace} }, -  { "propdel", svn_cl__propdel, {"pdel", "pd"}, N_ -    ("Remove a property from files, dirs, or revisions.\n" +  { "propdel", svn_cl__propdel, {"pdel", "pd"}, {N_( +     "Remove a property from files, dirs, or revisions.\n"       "usage: 1. propdel PROPNAME [PATH...]\n"       "       2. propdel PROPNAME --revprop -r REV [TARGET]\n" -     "\n" +     "\n"), N_(       "  1. Removes versioned props in working copy.\n"       "  2. Removes unversioned remote prop on repos revision.\n"       "     TARGET only determines which repository to access.\n" -     "\n" -     "  See 'svn help propset' for descriptions of the svn:* special properties.\n"), +     "\n"), N_( +     "  See 'svn help propset' for descriptions of the svn:* special properties.\n" +    )},      {'q', 'R', opt_depth, 'r', opt_revprop, opt_changelist} }, -  { "propedit", svn_cl__propedit, {"pedit", "pe"}, N_ -    ("Edit a property with an external editor.\n" +  { "propedit", svn_cl__propedit, {"pedit", "pe"}, {N_( +     "Edit a property with an external editor.\n"       "usage: 1. propedit PROPNAME TARGET...\n"       "       2. propedit PROPNAME --revprop -r REV [TARGET]\n" -     "\n" +     "\n"), N_(       "  1. Edits versioned prop in working copy or repository.\n"       "  2. Edits unversioned remote prop on repos revision.\n"       "     TARGET only determines which repository to access.\n" -     "\n" -     "  See 'svn help propset' for descriptions of the svn:* special properties.\n"), +     "\n"), N_( +     "  See 'svn help propset' for descriptions of the svn:* special properties.\n" +    )},      {'r', opt_revprop, SVN_CL__LOG_MSG_OPTIONS, opt_force} }, -  { "propget", svn_cl__propget, {"pget", "pg"}, N_ -    ("Print the value of a property on files, dirs, or revisions.\n" +  { "propget", svn_cl__propget, {"pget", "pg"}, {N_( +     "Print the value of a property on files, dirs, or revisions.\n"       "usage: 1. propget PROPNAME [TARGET[@REV]...]\n"       "       2. propget PROPNAME --revprop -r REV [TARGET]\n" -     "\n" +     "\n"), N_(       "  1. Prints versioned props. If specified, REV determines in which\n"       "     revision the target is first looked up.\n"       "  2. Prints unversioned remote prop on repos revision.\n"       "     TARGET only determines which repository to access.\n" -     "\n" +     "\n"), N_(       "  With --verbose, the target path and the property name are printed on\n"       "  separate lines before each value, like 'svn proplist --verbose'.\n"       "  Otherwise, if there is more than one TARGET or a depth other than\n"       "  'empty', the target path is printed on the same line before each value.\n" -     "\n" +     "\n"), N_(       "  By default, an extra newline is printed after the property value so that\n"       "  the output looks pretty.  With a single TARGET, depth 'empty' and without\n"       "  --show-inherited-props, you can use the --no-newline option to disable this\n"       "  (useful when redirecting a binary property value to a file, for example).\n" -     "\n" -     "  See 'svn help propset' for descriptions of the svn:* special properties.\n"), +     "\n"), N_( +     "  See 'svn help propset' for descriptions of the svn:* special properties.\n" +    )},      {'v', 'R', opt_depth, 'r', opt_revprop, opt_strict, opt_no_newline, opt_xml,       opt_changelist, opt_show_inherited_props },      {{'v', N_("print path, name and value on separate lines")},       {opt_strict, N_("(deprecated; use --no-newline)")}} }, -  { "proplist", svn_cl__proplist, {"plist", "pl"}, N_ -    ("List all properties on files, dirs, or revisions.\n" +  { "proplist", svn_cl__proplist, {"plist", "pl"}, {N_( +     "List all properties on files, dirs, or revisions.\n"       "usage: 1. proplist [TARGET[@REV]...]\n"       "       2. proplist --revprop -r REV [TARGET]\n" -     "\n" +     "\n"), N_(       "  1. Lists versioned props. If specified, REV determines in which\n"       "     revision the target is first looked up.\n"       "  2. Lists unversioned remote props on repos revision.\n"       "     TARGET only determines which repository to access.\n" -     "\n" +     "\n"), N_(       "  With --verbose, the property values are printed as well, like 'svn propget\n"       "  --verbose'.  With --quiet, the paths are not printed.\n" -     "\n" -     "  See 'svn help propset' for descriptions of the svn:* special properties.\n"), +     "\n"), N_( +     "  See 'svn help propset' for descriptions of the svn:* special properties.\n" +    )},      {'v', 'R', opt_depth, 'r', 'q', opt_revprop, opt_xml, opt_changelist,       opt_show_inherited_props },      {{'v', N_("print path, name and value on separate lines")},       {'q', N_("don't print the path")}} }, -  { "propset", svn_cl__propset, {"pset", "ps"}, N_ -    ("Set the value of a property on files, dirs, or revisions.\n" +  { "propset", svn_cl__propset, {"pset", "ps"}, {N_( +     "Set the value of a property on files, dirs, or revisions.\n"       "usage: 1. propset PROPNAME PROPVAL PATH...\n"       "       2. propset PROPNAME --revprop -r REV PROPVAL [TARGET]\n" -     "\n" +     "\n"), N_(       "  1. Changes a versioned file or directory property in a working copy.\n"       "  2. Changes an unversioned property on a repository revision.\n"       "     (TARGET only determines which repository to access.)\n" -     "\n" +     "\n"), N_(       "  The value may be provided with the --file option instead of PROPVAL.\n" -     "\n" +     "\n"), N_(       "  Property names starting with 'svn:' are reserved.  Subversion recognizes\n"       "  the following special versioned properties on a file:\n"       "    svn:keywords   - Keywords to be expanded.  Valid keywords are:\n" @@ -1518,7 +1503,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "        LastChangedRevision\n"       "      Id                       - A compressed summary of the previous four.\n"       "      Header                   - Similar to Id but includes the full URL.\n" -     "\n" +     "\n"), N_(       "      Custom keywords can be defined with a format string separated from\n"       "      the keyword name with '='. Valid format substitutions are:\n"       "        %a   - The author of the revision given by %r.\n" @@ -1536,7 +1521,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "      Example custom keyword definition: MyKeyword=%r%_%a%_%P\n"       "      Once a custom keyword has been defined for a file, it can be used\n"       "      within the file like any other keyword: $MyKeyword$\n" -     "\n" +     "\n"), N_(       "    svn:executable - If present, make the file executable.  Use\n"       "      'svn propdel svn:executable PATH...' to clear.\n"       "    svn:eol-style  - One of 'native', 'LF', 'CR', 'CRLF'.\n" @@ -1548,7 +1533,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "      before it is modified.  Makes the working copy file read-only\n"       "      when it is not locked.  Use 'svn propdel svn:needs-lock PATH...'\n"       "      to clear.\n" -     "\n" +     "\n"), N_(       "  Subversion recognizes the following special versioned properties on a\n"       "  directory:\n"       "    svn:ignore         - A list of file glob patterns to ignore, one per line.\n" @@ -1582,51 +1567,53 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "        LOCALPATH [-r PEG] URL\n"       "      The ambiguous format 'relative_path relative_path' is taken as\n"       "      'relative_url relative_path' with peg revision support.\n" -     "      Lines starting with a '#' character are ignored.\n"), +     "      Lines starting with a '#' character are ignored.\n" +    )},      {'F', opt_encoding, 'q', 'r', opt_targets, 'R', opt_depth, opt_revprop,       opt_force, opt_changelist },      {{'F', N_("read property value from file ARG")}} }, -  { "relocate", svn_cl__relocate, {0}, N_ -    ("Relocate the working copy to point to a different repository root URL.\n" +  { "relocate", svn_cl__relocate, {0}, {N_( +     "Relocate the working copy to point to a different repository root URL.\n"       "usage: 1. relocate FROM-PREFIX TO-PREFIX [PATH...]\n"       "       2. relocate TO-URL [PATH]\n" -     "\n" +     "\n"), N_(       "  Rewrite working copy URL metadata to reflect a syntactic change only.\n"       "  This is used when a repository's root URL changes (such as a scheme\n"       "  or hostname change) but your working copy still reflects the same\n"       "  directory within the same repository.\n" -     "\n" +     "\n"), N_(       "  1. FROM-PREFIX and TO-PREFIX are initial substrings of the working\n"       "     copy's current and new URLs, respectively.  (You may specify the\n"       "     complete old and new URLs if you wish.)  Use 'svn info' to determine\n"       "     the current working copy URL.\n" -     "\n" +     "\n"), N_(       "  2. TO-URL is the (complete) new repository URL to use for PATH.\n" -     "\n" +     "\n"), N_(       "  Examples:\n"       "    svn relocate http:// svn:// project1 project2\n"       "    svn relocate http://www.example.com/repo/project \\\n" -     "                 svn://svn.example.com/repo/project\n"), +     "                 svn://svn.example.com/repo/project\n" +    )},      {opt_ignore_externals} }, -  { "resolve", svn_cl__resolve, {0}, N_ -    ("Resolve conflicts on working copy files or directories.\n" +  { "resolve", svn_cl__resolve, {0}, {N_( +     "Resolve conflicts on working copy files or directories.\n"       "usage: resolve [PATH...]\n" -     "\n" +     "\n"), N_(       "  By default, perform interactive conflict resolution on PATH.\n"       "  In this mode, the command is recursive by default (depth 'infinity').\n" -     "\n" +     "\n"), N_(       "  The --accept=ARG option prevents interactive prompting and forces\n"       "  conflicts on PATH to be resolved in the manner specified by ARG.\n"       "  In this mode, the command is not recursive by default (depth 'empty').\n" -     "\n" +     "\n"), N_(       "  A conflicted path cannot be committed with 'svn commit' until it\n"       "  has been marked as resolved with 'svn resolve'.\n" -     "\n" +     "\n"), N_(       "  Subversion knows three types of conflicts:\n"       "  Text conflicts, Property conflicts, and Tree conflicts.\n" -     "\n" +     "\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" @@ -1634,12 +1621,12 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  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" +     "\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" +     "\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" @@ -1649,7 +1636,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  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" +     "\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" @@ -1660,7 +1647,8 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  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"), +     "  copied, deleted, or moved.\n" +    )},      {opt_targets, 'R', opt_depth, 'q', opt_accept},      {{opt_accept, N_("specify automatic conflict resolution source\n"                       "                             " @@ -1668,37 +1656,40 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =                       "                             "                       "'theirs-conflict', 'mine-full', 'theirs-full')")}} }, -  { "resolved", svn_cl__resolved, {0}, N_ -    ("Remove 'conflicted' state on working copy files or directories.\n" +  { "resolved", svn_cl__resolved, {0}, {N_( +     "Remove 'conflicted' state on working copy files or directories.\n"       "usage: resolved PATH...\n" -     "\n" +     "\n"), N_(       "  Note:  this subcommand does not semantically resolve conflicts or\n"       "  remove conflict markers; it merely removes the conflict-related\n"       "  artifact files and allows PATH to be committed again.  It has been\n" -     "  deprecated in favor of running 'svn resolve --accept working'.\n"), +     "  deprecated in favor of running 'svn resolve --accept working'.\n" +    )},      {opt_targets, 'R', opt_depth, 'q'} }, -  { "revert", svn_cl__revert, {0}, N_ -    ("Restore pristine working copy state (undo local changes).\n" +  { "revert", svn_cl__revert, {0}, {N_( +     "Restore pristine working copy state (undo local changes).\n"       "usage: revert PATH...\n" -     "\n" +     "\n"), N_(       "  Revert changes in the working copy at or within PATH, and remove\n"       "  conflict markers as well, if any.\n" -     "\n" +     "\n"), N_(       "  This subcommand does not revert already committed changes.\n"       "  For information about undoing already committed changes, search\n" -     "  the output of 'svn help merge' for 'undo'.\n"), -    {opt_targets, 'R', opt_depth, 'q', opt_changelist} }, +     "  the output of 'svn help merge' for 'undo'.\n" +    )}, +    {opt_targets, 'R', opt_depth, 'q', opt_changelist, +     opt_remove_added} }, -  { "status", svn_cl__status, {"stat", "st"}, N_ -    ("Print the status of working copy files and directories.\n" +  { "status", svn_cl__status, {"stat", "st"}, {N_( +     "Print the status of working copy files and directories.\n"       "usage: status [PATH...]\n" -     "\n" +     "\n"), N_(       "  With no args, print only locally modified items (no network access).\n"       "  With -q, print only summary information about locally modified items.\n"       "  With -u, add working revision and server out-of-date information.\n"       "  With -v, print full revision information on every item.\n" -     "\n" +     "\n"), N_(       "  The first seven columns in the output are each one character wide:\n"       "    First column: Says if item was added, deleted, or otherwise changed\n"       "      ' ' no modifications\n" @@ -1742,61 +1733,64 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "      'C' tree-Conflicted\n"       "    If the item is a tree conflict victim, an additional line is printed\n"       "    after the item's status line, explaining the nature of the conflict.\n" -     "\n" +     "\n"), N_(       "  The out-of-date information appears in the ninth column (with -u):\n"       "      '*' a newer revision exists on the server\n"       "      ' ' the working copy is up to date\n" -     "\n" +     "\n"), N_(       "  Remaining fields are variable width and delimited by spaces:\n"       "    The working revision (with -u or -v; '-' if the item is copied)\n"       "    The last committed revision and last committed author (with -v)\n"       "    The working copy path is always the final field, so it can\n"       "      include spaces.\n" -     "\n" +     "\n"), N_(       "  The presence of a question mark ('?') where a working revision, last\n"       "  committed revision, or last committed author was expected indicates\n"       "  that the information is unknown or irrelevant given the state of the\n"       "  item (for example, when the item is the result of a copy operation).\n"       "  The question mark serves as a visual placeholder to facilitate parsing.\n" -     "\n" +     "\n"), N_(       "  Example output:\n"       "    svn status wc\n"       "     M      wc/bar.c\n"       "    A  +    wc/qax.c\n" -     "\n" +     "\n"), N_(       "    svn status -u wc\n"       "     M             965   wc/bar.c\n"       "            *      965   wc/foo.c\n"       "    A  +             -   wc/qax.c\n"       "    Status against revision:   981\n" -     "\n" +     "\n"), N_(       "    svn status --show-updates --verbose wc\n"       "     M             965      938 kfogel       wc/bar.c\n"       "            *      965      922 sussman      wc/foo.c\n"       "    A  +             -      687 joe          wc/qax.c\n"       "                   965      687 joe          wc/zig.c\n"       "    Status against revision:   981\n" -     "\n" +     "\n"), N_(       "    svn status\n"       "     M      wc/bar.c\n"       "    !     C wc/qaz.c\n"       "          >   local missing, incoming edit upon update\n" -     "    D       wc/qax.c\n"), +     "    D       wc/qax.c\n" +    )},      { 'u', 'v', 'N', opt_depth, 'r', 'q', opt_no_ignore, opt_incremental,        opt_xml, opt_ignore_externals, opt_changelist}, -    {{'q', N_("don't print unversioned items")}} }, +    {{'q', N_("don't print unversioned items")}, +     {'N', N_("obsolete; same as --depth=immediates")}} }, -  { "switch", svn_cl__switch, {"sw"}, N_ -    ("Update the working copy to a different URL within the same repository.\n" +  { "switch", svn_cl__switch, {"sw"}, {N_( +     "Update the working copy to a different URL within the same\n" +     "repository.\n"       "usage: 1. switch URL[@PEGREV] [PATH]\n"       "       2. switch --relocate FROM-PREFIX TO-PREFIX [PATH...]\n" -     "\n" +     "\n"), N_(       "  1. Update the working copy to mirror a new URL within the repository.\n"       "     This behavior is similar to 'svn update', and is the way to\n"       "     move a working copy to a branch or tag within the same repository.\n"       "     If specified, PEGREV determines in which revision the target is first\n"       "     looked up.\n" -     "\n" +     "\n"), N_(       "     If --force is used, unversioned obstructing paths in the working\n"       "     copy do not automatically cause a failure if the switch attempts to\n"       "     add the same path.  If the obstructing path is the same type (file\n" @@ -1807,25 +1801,23 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "     between the obstruction and the repository are treated like a local\n"       "     modification to the working copy.  All properties from the repository\n"       "     are applied to the obstructing path.\n" -     "\n" +     "\n"), N_(       "     Use the --set-depth option to set a new working copy depth on the\n"       "     targets of this operation.\n" -     "\n" +     "\n"), N_(       "     By default, Subversion will refuse to switch a working copy path to\n"       "     a new URL with which it shares no common version control ancestry.\n"       "     Use the '--ignore-ancestry' option to override this sanity check.\n" -     "\n" +     "\n"), N_(       "  2. The '--relocate' option is deprecated. This syntax is equivalent to\n"       "     'svn relocate FROM-PREFIX TO-PREFIX [PATH]'.\n" -     "\n" +     "\n"), N_(       "  See also 'svn help update' for a list of possible characters\n"       "  reporting the action taken.\n" -     "\n" +     "\n"), N_(       "  Examples:\n"       "    svn switch ^/branches/1.x-release\n" -     "    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_ignore_externals, opt_ignore_ancestry, opt_force, opt_accept,        opt_relocate }, @@ -1833,27 +1825,29 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       N_("allow switching to a node with no common ancestor")},       {opt_force,        N_("handle unversioned obstructions as changes")}, -     {opt_relocate,N_("deprecated; use 'svn relocate'")}} +     {opt_relocate, N_("deprecated; use 'svn relocate'")}, +     {'N', N_("obsolete; same as --depth=files")}}    }, -  { "unlock", svn_cl__unlock, {0}, N_ -    ("Unlock working copy paths or URLs.\n" +  { "unlock", svn_cl__unlock, {0}, {N_( +     "Unlock working copy paths or URLs.\n"       "usage: unlock TARGET...\n" -     "\n" -     "  Use --force to break a lock held by another user or working copy.\n"), +     "\n"), N_( +     "  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" +  { "update", svn_cl__update, {"up"},  {N_( +     "Bring changes from the repository into the working copy.\n"       "usage: update [PATH...]\n" -     "\n" +     "\n"), N_(       "  If no revision is given, bring working copy up-to-date with HEAD rev.\n"       "  Else synchronize working copy to revision given by -r.\n" -     "\n" +     "\n"), N_(       "  For each updated item a line will be printed with characters reporting\n"       "  the action taken. These characters have the following meaning:\n" -     "\n" +     "\n"), N_(       "    A  Added\n"       "    D  Deleted\n"       "    U  Updated\n" @@ -1861,7 +1855,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "    G  Merged\n"       "    E  Existed\n"       "    R  Replaced\n" -     "\n" +     "\n"), N_(       "  Characters in the first column report about the item itself.\n"       "  Characters in the second column report about properties of the item.\n"       "  A 'B' in the third column signifies that the lock for the file has\n" @@ -1869,7 +1863,7 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  A 'C' in the fourth column indicates a tree conflict, while a 'C' in\n"       "  the first and second columns indicate textual conflicts in files\n"       "  and in property values, respectively.\n" -     "\n" +     "\n"), N_(       "  If --force is used, unversioned obstructing paths in the working\n"       "  copy do not automatically cause a failure if the update attempts to\n"       "  add the same path.  If the obstructing path is the same type (file\n" @@ -1881,99 +1875,36 @@ const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =       "  modification to the working copy.  All properties from the repository\n"       "  are applied to the obstructing path.  Obstructing paths are reported\n"       "  in the first column with code 'E'.\n" -     "\n" +     "\n"), N_(       "  If the specified update target is missing from the working copy but its\n"       "  immediate parent directory is present, checkout the target into its\n"       "  parent directory at the specified depth.  If --parents is specified,\n"       "  create any missing parent directories of the target by checking them\n"       "  out, too, at depth=empty.\n" -     "\n" +     "\n"), N_(       "  Use the --set-depth option to set a new working copy depth on the\n" -     "  targets of this operation.\n"), +     "  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_adds_as_modification},      { {opt_force, -       N_("handle unversioned obstructions as changes")} } }, +       N_("handle unversioned obstructions as changes")}, +      {'N', N_("obsolete; same as --depth=files")} } }, -  { "upgrade", svn_cl__upgrade, {0}, N_ -    ("Upgrade the metadata storage format for a working copy.\n" +  { "upgrade", svn_cl__upgrade, {0}, {N_( +     "Upgrade the metadata storage format for a working copy.\n"       "usage: upgrade [WCPATH...]\n" -     "\n" -     "  Local modifications are preserved.\n"), +     "\n"), N_( +     "  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} } +  { NULL, NULL, {0}, {NULL}, {0} }  }; +const svn_opt_subcommand_desc3_t *svn_cl__cmd_table = svn_cl__cmd_table_main; +  /* Version compatibility check */  static svn_error_t * @@ -2036,6 +1967,49 @@ add_search_pattern_to_latest_group(svn_cl__opt_state_t *opt_state,    APR_ARRAY_PUSH(group, const char *) = pattern;  } +/* Parse the argument to the --x-viewspec option. */ +static svn_error_t * +viewspec_from_word(enum svn_cl__viewspec_t *viewspec, +                   const char *utf8_opt_arg) +{ +  if (!strcmp(utf8_opt_arg, "classic")) +    *viewspec = svn_cl__viewspec_classic; +  else if (!strcmp(utf8_opt_arg, "svn11")) +    *viewspec = svn_cl__viewspec_svn11; +  else +    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, +                             _("'%s' is not a valid --x-viewspec value"), +                             utf8_opt_arg); +  return SVN_NO_ERROR; +} + +/* Re-initialize the command table SVN_CL__CMD_TABLE, + * adding additional commands from CMDS_ADD. + * (TODO: and the options table) */ +static void +add_commands(const svn_opt_subcommand_desc3_t *cmds_add, +             apr_pool_t *pool) +{ +  int elt_size = sizeof(svn_opt_subcommand_desc3_t); +  const svn_opt_subcommand_desc3_t *cmds_old = svn_cl__cmd_table; +  const svn_opt_subcommand_desc3_t *cmd; +  int n_cmds_old, n_cmds_add, n_cmds_new; +  svn_opt_subcommand_desc3_t *cmds_new; + +  for (cmd = cmds_old; cmd->name; cmd++) ; +  n_cmds_old = (int)(cmd - cmds_old); +  for (cmd = cmds_add; cmd->name; cmd++) ; +  n_cmds_add = (int)(cmd - cmds_add); +  n_cmds_new = n_cmds_old + n_cmds_add; + +  /* copy CMDS_OLD and CMDS_ADD, plus an all-zeros terminator entry */ +  cmds_new = apr_pcalloc(pool, (n_cmds_new + 1) * elt_size); +  memcpy(cmds_new, cmds_old, n_cmds_old * elt_size); +  memcpy(&cmds_new[n_cmds_old], cmds_add, n_cmds_add * elt_size); + +  svn_cl__cmd_table = cmds_new; +} +  /*** Main. ***/ @@ -2053,8 +2027,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)    svn_cl__opt_state_t opt_state = { 0, { 0 } };    svn_client_ctx_t *ctx;    apr_array_header_t *received_opts; +  const char *exp_cmds;    int i; -  const svn_opt_subcommand_desc2_t *subcommand = NULL; +  const svn_opt_subcommand_desc3_t *subcommand = NULL;    const char *dash_F_arg = NULL;    svn_cl__cmd_baton_t command_baton;    svn_auth_baton_t *ab; @@ -2093,6 +2068,18 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)    /* Init the temporary buffer. */    svn_membuf__create(&buf, 0, pool); +  /* Add experimental commands, if requested. Use the most recent version +   * that we know about and that is mentioned in the env. var. */ +  exp_cmds = getenv("SVN_EXPERIMENTAL_COMMANDS"); +  if (exp_cmds && strstr(exp_cmds, "shelf3")) +    { +      add_commands(svn_cl__cmd_table_shelf3, pool); +    } +  else if (exp_cmds && strstr(exp_cmds, "shelf2")) +    { +      add_commands(svn_cl__cmd_table_shelf2, pool); +    } +    /* Begin processing arguments. */    opt_state.start_revision.kind = svn_opt_revision_unspecified;    opt_state.end_revision.kind = svn_opt_revision_unspecified; @@ -2102,6 +2089,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)    opt_state.set_depth = svn_depth_unknown;    opt_state.accept_which = svn_cl__accept_unspecified;    opt_state.show_revs = svn_cl__show_revs_invalid; +  opt_state.file_size_unit = SVN_CL__SIZE_UNIT_NONE;    /* No args?  Show usage. */    if (argc <= 1) @@ -2308,9 +2296,6 @@ 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; @@ -2320,6 +2305,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)        case 'N':          descend = FALSE;          break; +      case 'H': +        opt_state.file_size_unit = SVN_CL__SIZE_UNIT_BASE_2; +        break;        case opt_depth:          err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);          if (err) @@ -2457,7 +2445,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)          break;        case opt_config_dir:          SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); -        opt_state.config_dir = svn_dirent_internal_style(utf8_opt_arg, pool); +        SVN_ERR(svn_dirent_internal_style_safe(&opt_state.config_dir, NULL, +                                               utf8_opt_arg, pool, pool));          break;        case opt_config_options:          if (!opt_state.config_options) @@ -2494,9 +2483,11 @@ 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_drop: +        opt_state.drop = TRUE; +        break;        case opt_changelist:          SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));          if (utf8_opt_arg[0] == '\0') @@ -2510,7 +2501,6 @@ 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: @@ -2625,6 +2615,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)        case opt_remove_ignored:          opt_state.remove_ignored = TRUE;          break; +      case opt_remove_added: +        opt_state.remove_added = TRUE; +        break;        case opt_no_newline:        case opt_strict:          /* ### DEPRECATED */          opt_state.no_newline = TRUE; @@ -2645,6 +2638,11 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)        case opt_vacuum_pristines:          opt_state.vacuum_pristines = TRUE;          break; +      case opt_viewspec: +        opt_state.viewspec = TRUE; +        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); +        SVN_ERR(viewspec_from_word(&opt_state.viewspec, utf8_opt_arg)); +        break;        default:          /* Hmmm. Perhaps this would be a good place to squirrel away             opts that commands like svn diff might need. Hmmm indeed. */ @@ -2685,7 +2683,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)       just typos/mistakes.  Whatever the case, the subcommand to       actually run is svn_cl__help(). */    if (opt_state.help) -    subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, "help"); +    subcommand = svn_opt_get_canonical_subcommand3(svn_cl__cmd_table, "help");    /* If we're not running the `help' subcommand, then look for a       subcommand in the first argument. */ @@ -2696,8 +2694,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)            if (opt_state.version)              {                /* Use the "help" subcommand to handle the "--version" option. */ -              static const svn_opt_subcommand_desc2_t pseudo_cmd = -                { "--version", svn_cl__help, {0}, "", +              static const svn_opt_subcommand_desc3_t pseudo_cmd = +              { "--version", svn_cl__help, {0}, {""},                    {opt_version,    /* must accept its own option */                     'q',            /* brief output */                     'v',            /* verbose output */ @@ -2722,7 +2720,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)            SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],                                            pool)); -          subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, +          subcommand = svn_opt_get_canonical_subcommand3(svn_cl__cmd_table,                                                           first_arg);            if (subcommand == NULL)              { @@ -2760,12 +2758,12 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)        if (opt_id == 'h' || opt_id == '?')          continue; -      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, +      if (! svn_opt_subcommand_takes_option4(subcommand, opt_id,                                               svn_cl__global_options))          {            const char *optstr;            const apr_getopt_option_t *badopt = -            svn_opt_get_option_from_code2(opt_id, svn_cl__options, +            svn_opt_get_option_from_code3(opt_id, svn_cl__options,                                            subcommand, pool);            svn_opt_format_option(&optstr, badopt, FALSE, pool);            if (subcommand->name[0] == '-') @@ -3016,15 +3014,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)       sense (unless we've also been instructed not to care).  This may       access the working copy so do it after setting the locking mode. */    if ((! opt_state.force_log) -      && (subcommand->cmd_func == svn_cl__commit -          || subcommand->cmd_func == svn_cl__copy -          || subcommand->cmd_func == svn_cl__delete -          || subcommand->cmd_func == svn_cl__import -          || 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__shelve)) +      && subcommand->cmd_func != svn_cl__propset)      {        /* If the -F argument is a file that's under revision control,           that's probably not what the user intended. */ @@ -3032,7 +3022,10 @@ 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 = svn_dirent_internal_style(dash_F_arg, pool); +          const char *fname; + +          SVN_ERR(svn_dirent_internal_style_safe(&fname, NULL, dash_F_arg, +                                                 pool, pool));            err = svn_dirent_get_absolute(&local_abspath, fname, pool); diff --git a/subversion/svn/util.c b/subversion/svn/util.c index a18e5f37c9f7a..757983a9963f1 100644 --- a/subversion/svn/util.c +++ b/subversion/svn/util.c @@ -163,6 +163,8 @@ svn_cl__merge_file_externally(const char *base_path,      arguments[5] = wc_path;      arguments[6] = NULL; +    /* Presumably apr_filepath_get() returns a valid path, so we don't have +       to use the safe version of svn_dirent_internal_style() here. */      SVN_ERR(svn_io_run_cmd(svn_dirent_internal_style(cwd, pool), merge_tool,                             arguments, &exitcode, NULL, TRUE, NULL, NULL, NULL,                             pool)); | 
