Modified: subversion/branches/shelve-checkpoint/subversion/svn/auth-cmd.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/svn/auth-cmd.c?rev=1829257&r1=1829256&r2=1829257&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/svn/auth-cmd.c (original) +++ subversion/branches/shelve-checkpoint/subversion/svn/auth-cmd.c Mon Apr 16 12:21:02 2018 @@ -455,12 +455,15 @@ svn_cl__auth(apr_getopt_t *os, void *bat { 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 *bat "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;
Modified: subversion/branches/shelve-checkpoint/subversion/svn/cl.h URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/svn/cl.h?rev=1829257&r1=1829256&r2=1829257&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/svn/cl.h (original) +++ subversion/branches/shelve-checkpoint/subversion/svn/cl.h Mon Apr 16 12:21:02 2018 @@ -256,7 +256,8 @@ typedef struct svn_cl__opt_state_t 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_boolean_t viewspec; } svn_cl__opt_state_t; /* Conflict stats for operations such as update and merge. */ @@ -307,13 +308,11 @@ svn_opt_subcommand_t svn_cl__shelf_diff, svn_cl__shelf_drop, svn_cl__shelf_list, + svn_cl__shelf_list_by_paths, svn_cl__shelf_log, svn_cl__shelf_save, svn_cl__shelf_shelve, svn_cl__shelf_unshelve, - svn_cl__shelve, - svn_cl__unshelve, - svn_cl__shelves, svn_cl__status, svn_cl__switch, svn_cl__unlock, @@ -322,7 +321,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[]; Modified: subversion/branches/shelve-checkpoint/subversion/svn/help-cmd.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/svn/help-cmd.c?rev=1829257&r1=1829256&r2=1829257&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/svn/help-cmd.c (original) +++ subversion/branches/shelve-checkpoint/subversion/svn/help-cmd.c Mon Apr 16 12:21:02 2018 @@ -166,7 +166,7 @@ svn_cl__help(apr_getopt_t *os, pool); #endif } -#ifdef SVN_HAVE_GNOME_KEYRING +#if (defined(SVN_HAVE_GNOME_KEYRING) || defined(SVN_HAVE_LIBSECRET)) svn_stringbuf_appendcstr(version_footer, "* Gnome Keyring\n"); #endif #ifdef SVN_HAVE_GPG_AGENT @@ -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, Modified: subversion/branches/shelve-checkpoint/subversion/svn/info-cmd.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/svn/info-cmd.c?rev=1829257&r1=1829256&r2=1829257&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/svn/info-cmd.c (original) +++ subversion/branches/shelve-checkpoint/subversion/svn/info-cmd.c Mon Apr 16 12:21:02 2018 @@ -45,6 +45,245 @@ /*** 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; +} + +static svn_error_t * +cl_layout_list(apr_array_header_t *targets, + 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; + + if (TRUE) + { + /* 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)); + } + else + { + /* svn command-line format */ + SVN_ERR(svn_client_layout_list(list_abspath, + output_svn_command_line, &llb, + ctx, scratch_pool)); + } + return SVN_NO_ERROR; +} + static svn_error_t * svn_cl__info_print_time(apr_time_t atime, const char *desc, @@ -110,7 +349,9 @@ 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_t; /* Mapping between option keywords and info_item_t. */ @@ -133,7 +374,9 @@ static const info_item_map_t info_item_m 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 }, }; #undef MAKE_STRING @@ -888,6 +1131,20 @@ 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; + default: SVN_ERR_MALFUNCTION(); } @@ -918,6 +1175,12 @@ svn_cl__info(apr_getopt_t *os, opt_state->targets, ctx, FALSE, pool)); + if (opt_state->viewspec) + { + SVN_ERR(cl_layout_list(targets, baton, ctx, pool)); + return SVN_NO_ERROR; + } + /* Add "." if user passed 0 arguments. */ svn_opt_push_implicit_dot_target(targets, pool); Modified: subversion/branches/shelve-checkpoint/subversion/svn/shelf-cmd.c URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint/subversion/svn/shelf-cmd.c?rev=1829257&r1=1829256&r2=1829257&view=diff ============================================================================== --- subversion/branches/shelve-checkpoint/subversion/svn/shelf-cmd.c (original) +++ subversion/branches/shelve-checkpoint/subversion/svn/shelf-cmd.c Mon Apr 16 12:21:02 2018 @@ -28,7 +28,9 @@ #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_utf.h" #include "cl.h" @@ -53,45 +55,107 @@ get_next_argument(const char **arg, 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_duration_str(apr_time_t duration, - apr_pool_t *result_pool) +friendly_age_str(apr_time_t mtime, + apr_time_t time_now, + apr_pool_t *result_pool) { - int minutes = (int)(duration / 1000000 / 60); + int minutes = (int)((time_now - mtime) / 1000000 / 60); char *s; if (minutes >= 60 * 24) - s = apr_psprintf(result_pool, _("%d days"), 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, _("%d hours"), minutes / 60); + s = apr_psprintf(result_pool, + Q_("%d hour ago", "%d hours ago", + minutes / 60), + minutes / 60); else - s = apr_psprintf(result_pool, _("%d minutes"), minutes); + s = apr_psprintf(result_pool, + Q_("%d minute ago", "%d minutes ago", + minutes), + minutes); return s; } -/* Print some details of the changes in the patch described by INFO. - */ -static svn_error_t * -show_diffstat(svn_client_shelf_version_t *shelf_version, - apr_pool_t *scratch_pool) -{ -#ifndef WIN32 - const char *patch_abspath; - int result; - - SVN_ERR(svn_client_shelf_get_patch_abspath(&patch_abspath, shelf_version, - scratch_pool)); - result = system(apr_psprintf(scratch_pool, - "diffstat -p0 '%s' 2> /dev/null", - patch_abspath)); - if (result == 0) - SVN_ERR(svn_cmdline_printf(scratch_pool, "\n")); -#endif - return SVN_NO_ERROR; -} - /* A comparison function for svn_sort__hash(), comparing the mtime of two svn_client_shelf_info_t's. */ static int @@ -127,45 +191,39 @@ list_sorted_by_date(apr_array_header_t * 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) { - svn_client_shelf_version_t *shelf_version; char *age_str; char *version_str; apr_hash_t *paths; const char *paths_str = ""; - char *info_str; - if (version == 0) + if (! shelf_version) { return SVN_NO_ERROR; } - SVN_ERR(svn_client_shelf_version_open(&shelf_version, - shelf, version, - scratch_pool, scratch_pool)); - - age_str = friendly_duration_str(time_now - shelf_version->mtime, scratch_pool); + 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, - _("version %d of %d"), + 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)); - if (paths) - paths_str = apr_psprintf(scratch_pool, - _(", %d paths changed"), apr_hash_count(paths)); - info_str = apr_psprintf(scratch_pool, - _("%s, %s ago%s\n"), - version_str, age_str, paths_str); + 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", - shelf->name, info_str)); + "%-30s %s, %s, %s\n", + shelf->name, version_str, age_str, paths_str)); if (with_logmsg) { @@ -188,7 +246,6 @@ stats(svn_client_shelf_t *shelf, static svn_error_t * shelves_list(const char *local_abspath, svn_boolean_t quiet, - svn_boolean_t with_diffstat, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { @@ -208,18 +265,13 @@ shelves_list(const char *local_abspath, SVN_ERR(svn_client_shelf_open_existing(&shelf, name, local_abspath, ctx, scratch_pool)); - SVN_ERR(svn_client_shelf_version_open(&shelf_version, - shelf, shelf->max_version, - scratch_pool, scratch_pool)); - if (quiet) + SVN_ERR(svn_client_shelf_get_newest_version(&shelf_version, shelf, + scratch_pool, scratch_pool)); + if (quiet || !shelf_version) SVN_ERR(svn_cmdline_printf(scratch_pool, "%s\n", shelf->name)); else - SVN_ERR(stats(shelf, shelf->max_version, time_now, + SVN_ERR(stats(shelf, shelf->max_version, shelf_version, time_now, TRUE /*with_logmsg*/, scratch_pool)); - if (with_diffstat) - { - SVN_ERR(show_diffstat(shelf_version, scratch_pool)); - } SVN_ERR(svn_client_shelf_close(shelf, scratch_pool)); } @@ -231,30 +283,25 @@ shelves_list(const char *local_abspath, static svn_error_t * shelf_log(const char *name, const char *local_abspath, - svn_boolean_t with_diffstat, 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)); - - for (i = 1; i <= shelf->max_version; i++) + 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; + svn_client_shelf_version_t *shelf_version + = APR_ARRAY_IDX(versions, i, void *); - SVN_ERR(svn_client_shelf_version_open(&shelf_version, - shelf, i, - scratch_pool, scratch_pool)); - SVN_ERR(stats(shelf, i, time_now, + SVN_ERR(stats(shelf, i + 1, shelf_version, time_now, FALSE /*with_logmsg*/, scratch_pool)); - if (with_diffstat) - { - SVN_ERR(show_diffstat(shelf_version, scratch_pool)); - } } SVN_ERR(svn_client_shelf_close(shelf, scratch_pool)); @@ -417,13 +464,16 @@ shelve(int *new_version_p, apr_pool_t *scratch_pool) { svn_client_shelf_t *shelf; - int previous_version; + svn_client_shelf_version_t *previous_version; + svn_client_shelf_version_t *new_version; const char *cwd_abspath; struct status_baton sb; - SVN_ERR(svn_client_shelf_open(&shelf, - name, local_abspath, ctx, scratch_pool)); - previous_version = shelf->max_version; + 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) { @@ -431,7 +481,7 @@ shelve(int *new_version_p, ? _("--- 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, previous_version, apr_time_now(), + SVN_ERR(stats(shelf, shelf->max_version, previous_version, apr_time_now(), TRUE /*with_logmsg*/, scratch_pool)); } @@ -457,10 +507,10 @@ shelve(int *new_version_p, SVN_ERR(svn_cmdline_printf(scratch_pool, keep_local ? _("--- Saving...\n") : _("--- Shelving...\n"))); - SVN_ERR(svn_client_shelf_save_new_version(shelf, - paths, depth, changelists, - scratch_pool)); - if (shelf->max_version == previous_version) + SVN_ERR(svn_client_shelf_save_new_version2(&new_version, shelf, + paths, depth, changelists, + scratch_pool)); + if (! new_version) { SVN_ERR(svn_client_shelf_close(shelf, scratch_pool)); return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, @@ -471,94 +521,112 @@ shelve(int *new_version_p, /* Un-apply the patch, if required. */ if (!keep_local) { - svn_client_shelf_version_t *shelf_version; - - SVN_ERR(svn_client_shelf_version_open(&shelf_version, - shelf, shelf->max_version, - scratch_pool, scratch_pool)); - SVN_ERR(svn_client_shelf_unapply(shelf_version, + SVN_ERR(svn_client_shelf_unapply(new_version, dry_run, scratch_pool)); } - SVN_ERR(svn_client_shelf_set_log_message(shelf, revprop_table, - 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_set_current_version(shelf, previous_version, - scratch_pool)); + 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; } -/* Throw an error if any paths affected by SHELF:VERSION are currently - * modified in the WC. */ +/* 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 * -check_no_modified_paths(const char *paths_base_abspath, - svn_client_shelf_version_t *shelf_version, - svn_boolean_t quiet, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) +test_apply(svn_client_shelf_version_t *shelf_version, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { apr_hash_t *paths; - struct status_baton sb; apr_hash_index_t *hi; - sb.target_abspath = shelf_version->shelf->wc_root_abspath; - sb.target_path = ""; - sb.header = _("--- Paths modified in shelf and in WC:\n"); - sb.quiet = quiet; - sb.modified = FALSE; - sb.ctx = ctx; - 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); - const char *abspath = svn_dirent_join(paths_base_abspath, path, - scratch_pool); + svn_boolean_t conflict; - SVN_ERR(svn_client_status6(NULL /*result_rev*/, - ctx, abspath, - NULL /*revision*/, - svn_depth_empty, - FALSE /*get_all*/, - FALSE /*check_out_of_date*/, - TRUE /*check_working_copy*/, - TRUE /*no_ignore*/, - TRUE /*ignore_externals*/, - FALSE /*depth_as_sticky*/, - NULL /*changelists*/, - modification_checker, &sb, - scratch_pool)); - } - if (sb.modified) - { - return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, - _("Cannot unshelve/restore, as at least one " - "path is modified in shelf and in WC")); + SVN_ERR(svn_client_shelf_test_apply_file(&conflict, shelf_version, path, + scratch_pool)); + if (conflict) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Conflict in applying shelf '%s' path '%s'"), + shelf_version->shelf->name, path); } return SVN_NO_ERROR; } +/* Intercept patch notifications to detect when there is a conflict */ +struct patch_notify_baton_t +{ + svn_wc_notify_func2_t notify_func; + void *notify_baton; + svn_boolean_t rejects; +}; + +/* Intercept patch notifications to detect when there is a conflict */ +static void +patch_notify(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + struct patch_notify_baton_t *b = baton; + + if (notify->action == svn_wc_notify_patch_rejected_hunk) + b->rejects = TRUE; + b->notify_func(b->notify_baton, notify, pool); +} + /** 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) @@ -567,6 +635,7 @@ shelf_restore(const char *name, apr_time_t time_now = apr_time_now(); svn_client_shelf_t *shelf; svn_client_shelf_version_t *shelf_version; + struct patch_notify_baton_t b; SVN_ERR(svn_client_shelf_open_existing(&shelf, name, local_abspath, ctx, scratch_pool)); @@ -575,10 +644,15 @@ shelf_restore(const char *name, 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(svn_client_shelf_get_newest_version(&shelf_version, shelf, + scratch_pool, scratch_pool)); } if (! quiet) @@ -586,30 +660,47 @@ shelf_restore(const char *name, 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, time_now, + SVN_ERR(stats(shelf, version, shelf_version, time_now, TRUE /*with_logmsg*/, scratch_pool)); } - SVN_ERR(svn_client_shelf_version_open(&shelf_version, - shelf, version, - scratch_pool, scratch_pool)); - SVN_ERR(check_no_modified_paths(shelf->wc_root_abspath, - shelf_version, quiet, ctx, scratch_pool)); + if (! force_if_conflict) + { + SVN_ERR_W(test_apply(shelf_version, ctx, scratch_pool), + _("Cannot unshelve/restore, as at least one " + "path would conflict")); + } + + b.rejects = FALSE; + b.notify_func = ctx->notify_func2; + b.notify_baton = ctx->notify_baton2; + ctx->notify_func2 = patch_notify; + ctx->notify_baton2 = &b; SVN_ERR(svn_client_shelf_apply(shelf_version, dry_run, scratch_pool)); + ctx->notify_func2 = b.notify_func; + ctx->notify_baton2 = b.notify_baton; + + if (b.rejects) + { + return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Unshelve/restore failed due to conflicts")); + } if (! dry_run) { - SVN_ERR(svn_client_shelf_set_current_version(shelf, version, - scratch_pool)); + 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, - _("restored '%s' version %d and deleted %d newer versions\n"), - name, version, old_version - version)); + 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"), @@ -627,7 +718,6 @@ shelf_diff(const char *name, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { - int version; svn_client_shelf_t *shelf; svn_client_shelf_version_t *shelf_version; svn_stream_t *stream; @@ -637,15 +727,18 @@ shelf_diff(const char *name, 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 { - version = shelf->max_version; + SVN_ERR(svn_client_shelf_get_newest_version(&shelf_version, shelf, + scratch_pool, scratch_pool)); } - SVN_ERR(svn_client_shelf_version_open(&shelf_version, - shelf, version, - scratch_pool, scratch_pool)); SVN_ERR(svn_stream_for_stdout(&stream, scratch_pool)); SVN_ERR(svn_client_shelf_export_patch(shelf_version, stream, @@ -821,8 +914,15 @@ svn_cl__shelf_unshelve(apr_getopt_t *os, 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; } @@ -843,12 +943,116 @@ svn_cl__shelf_list(apr_getopt_t *os, SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool)); SVN_ERR(shelves_list(local_abspath, opt_state->quiet, - opt_state->verbose /*with_diffstat*/, ctx, pool)); 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)); + 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. */ +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. */ svn_error_t * svn_cl__shelf_diff(apr_getopt_t *os, @@ -908,7 +1112,6 @@ 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; const char *local_abspath; @@ -922,7 +1125,6 @@ svn_cl__shelf_log(apr_getopt_t *os, SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool)); SVN_ERR(shelf_log(name, local_abspath, - opt_state->verbose /*with_diffstat*/, ctx, pool)); return SVN_NO_ERROR;