blob: 8305ce30938eb9dea6d6784ad630c61d6073db34 [file] [log] [blame]
/*
* 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} }
};