summaryrefslogtreecommitdiff
path: root/subversion/svn/shelf-cmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svn/shelf-cmd.c')
-rw-r--r--subversion/svn/shelf-cmd.c1233
1 files changed, 1233 insertions, 0 deletions
diff --git a/subversion/svn/shelf-cmd.c b/subversion/svn/shelf-cmd.c
new file mode 100644
index 0000000..9066fb7
--- /dev/null
+++ b/subversion/svn/shelf-cmd.c
@@ -0,0 +1,1233 @@
+/*
+ * 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 "cl.h"
+
+#include "svn_private_config.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_client_private.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;
+}
+
+/* This implements the `svn_opt_subcommand_t' interface. */
+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. */
+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. */
+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. */
+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. */
+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,
+ 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. */
+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. */
+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. */
+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;
+}