diff options
Diffstat (limited to 'subversion/svn/shelf2-cmd.c')
-rw-r--r-- | subversion/svn/shelf2-cmd.c | 1369 |
1 files changed, 1369 insertions, 0 deletions
diff --git a/subversion/svn/shelf2-cmd.c b/subversion/svn/shelf2-cmd.c new file mode 100644 index 000000000000..8305ce30938e --- /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} } +}; + |