shelf-cmd.c revision 362181
1/*
2 * shelf-cmd.c -- Shelving commands.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* We define this here to remove any further warnings about the usage of
25   experimental functions in this file. */
26#define SVN_EXPERIMENTAL
27
28#include "svn_client.h"
29#include "svn_error_codes.h"
30#include "svn_error.h"
31#include "svn_hash.h"
32#include "svn_path.h"
33#include "svn_props.h"
34#include "svn_pools.h"
35#include "svn_utf.h"
36
37#include "shelf-cmd.h"
38#include "cl.h"
39
40#include "svn_private_config.h"
41#include "private/svn_sorts_private.h"
42#include "private/svn_client_private.h"
43#include "private/svn_client_shelf.h"
44
45
46/* Open the newest version of SHELF; error if no versions found. */
47static svn_error_t *
48get_newest_version_existing(svn_client__shelf_version_t **shelf_version_p,
49                            svn_client__shelf_t *shelf,
50                            apr_pool_t *result_pool,
51                            apr_pool_t *scratch_pool)
52{
53  SVN_ERR(svn_client__shelf_get_newest_version(shelf_version_p, shelf,
54                                              result_pool, scratch_pool));
55  if (!*shelf_version_p)
56    {
57      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
58                               _("Shelf '%s': no versions found"),
59                               shelf->name);
60    }
61
62  return SVN_NO_ERROR;
63}
64
65/* Fetch the next argument. */
66static svn_error_t *
67get_next_argument(const char **arg,
68                  apr_getopt_t *os,
69                  apr_pool_t *result_pool,
70                  apr_pool_t *scratch_pool)
71{
72  apr_array_header_t *args;
73
74  SVN_ERR(svn_opt_parse_num_args(&args, os, 1, scratch_pool));
75  SVN_ERR(svn_utf_cstring_to_utf8(arg,
76                                  APR_ARRAY_IDX(args, 0, const char *),
77                                  result_pool));
78  return SVN_NO_ERROR;
79}
80
81/* Parse the remaining arguments as paths relative to a WC.
82 *
83 * TARGETS are relative to current working directory.
84 *
85 * Set *targets_by_wcroot to a hash mapping (char *)wcroot_abspath to
86 * (apr_array_header_t *)array of relpaths relative to that WC root.
87 */
88static svn_error_t *
89targets_relative_to_wcs(apr_hash_t **targets_by_wcroot_p,
90                        apr_array_header_t *targets,
91                        svn_client_ctx_t *ctx,
92                        apr_pool_t *result_pool,
93                        apr_pool_t *scratch_pool)
94{
95  apr_hash_t *targets_by_wcroot = apr_hash_make(result_pool);
96  int i;
97
98  /* Make each target relative to the WC root. */
99  for (i = 0; i < targets->nelts; i++)
100    {
101      const char *target = APR_ARRAY_IDX(targets, i, const char *);
102      const char *wcroot_abspath;
103      apr_array_header_t *paths;
104
105      SVN_ERR(svn_dirent_get_absolute(&target, target, result_pool));
106      SVN_ERR(svn_client_get_wc_root(&wcroot_abspath, target,
107                                     ctx, result_pool, scratch_pool));
108      paths = svn_hash_gets(targets_by_wcroot, wcroot_abspath);
109      if (! paths)
110        {
111          paths = apr_array_make(result_pool, 0, sizeof(char *));
112          svn_hash_sets(targets_by_wcroot, wcroot_abspath, paths);
113        }
114      target = svn_dirent_skip_ancestor(wcroot_abspath, target);
115
116      if (target)
117        APR_ARRAY_PUSH(paths, const char *) = target;
118    }
119  *targets_by_wcroot_p = targets_by_wcroot;
120  return SVN_NO_ERROR;
121}
122
123/* Return targets relative to a WC. Error if they refer to more than one WC. */
124static svn_error_t *
125targets_relative_to_a_wc(const char **wc_root_abspath_p,
126                         apr_array_header_t **paths_p,
127                         apr_getopt_t *os,
128                         const apr_array_header_t *known_targets,
129                         svn_client_ctx_t *ctx,
130                         apr_pool_t *result_pool,
131                         apr_pool_t *scratch_pool)
132{
133  apr_array_header_t *targets;
134  apr_hash_t *targets_by_wcroot;
135  apr_hash_index_t *hi;
136
137  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
138                                                      known_targets,
139                                                      ctx, FALSE, result_pool));
140  svn_opt_push_implicit_dot_target(targets, result_pool);
141
142  SVN_ERR(targets_relative_to_wcs(&targets_by_wcroot, targets,
143                                  ctx, result_pool, scratch_pool));
144  if (apr_hash_count(targets_by_wcroot) != 1)
145    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
146                            _("All targets must be in the same WC"));
147
148  hi = apr_hash_first(scratch_pool, targets_by_wcroot);
149  *wc_root_abspath_p = apr_hash_this_key(hi);
150  *paths_p = apr_hash_this_val(hi);
151  return SVN_NO_ERROR;
152}
153
154/* Return a human-friendly description of DURATION.
155 */
156static char *
157friendly_age_str(apr_time_t mtime,
158                 apr_time_t time_now,
159                 apr_pool_t *result_pool)
160{
161  int minutes = (int)((time_now - mtime) / 1000000 / 60);
162  char *s;
163
164  if (minutes >= 60 * 24)
165    s = apr_psprintf(result_pool,
166                     Q_("%d day ago", "%d days ago",
167                        minutes / 60 / 24),
168                     minutes / 60 / 24);
169  else if (minutes >= 60)
170    s = apr_psprintf(result_pool,
171                     Q_("%d hour ago", "%d hours ago",
172                        minutes / 60),
173                     minutes / 60);
174  else
175    s = apr_psprintf(result_pool,
176                     Q_("%d minute ago", "%d minutes ago",
177                        minutes),
178                     minutes);
179  return s;
180}
181
182/* A comparison function for svn_sort__hash(), comparing the mtime of two
183   svn_client_shelf_info_t's. */
184static int
185compare_shelf_infos_by_mtime(const svn_sort__item_t *a,
186                             const svn_sort__item_t *b)
187{
188  svn_client__shelf_info_t *a_val = a->value;
189  svn_client__shelf_info_t *b_val = b->value;
190
191  return (a_val->mtime < b_val->mtime)
192    ? -1 : (a_val->mtime > b_val->mtime) ? 1 : 0;
193}
194
195/* Return a list of shelves sorted by their mtime, oldest first.
196 */
197static svn_error_t *
198list_sorted_by_date(apr_array_header_t **list,
199                    const char *local_abspath,
200                    svn_client_ctx_t *ctx,
201                    apr_pool_t *scratch_pool)
202{
203  apr_hash_t *shelf_infos;
204
205  SVN_ERR(svn_client__shelf_list(&shelf_infos, local_abspath,
206                                ctx, scratch_pool, scratch_pool));
207  *list = svn_sort__hash(shelf_infos,
208                         compare_shelf_infos_by_mtime,
209                         scratch_pool);
210  return SVN_NO_ERROR;
211}
212
213/*  */
214static svn_error_t *
215stats(svn_client__shelf_t *shelf,
216      int version,
217      svn_client__shelf_version_t *shelf_version,
218      apr_time_t time_now,
219      svn_boolean_t with_logmsg,
220      apr_pool_t *scratch_pool)
221{
222  char *age_str;
223  char *version_str;
224  apr_hash_t *paths;
225  const char *paths_str = "";
226
227  if (! shelf_version)
228    {
229      return SVN_NO_ERROR;
230    }
231
232  age_str = friendly_age_str(shelf_version->mtime, time_now, scratch_pool);
233  if (version == shelf->max_version)
234    version_str = apr_psprintf(scratch_pool,
235                               _("version %d"), version);
236  else
237    version_str = apr_psprintf(scratch_pool,
238                               Q_("version %d of %d", "version %d of %d",
239                                  shelf->max_version),
240                               version, shelf->max_version);
241  SVN_ERR(svn_client__shelf_paths_changed(&paths, shelf_version,
242                                         scratch_pool, scratch_pool));
243  paths_str = apr_psprintf(scratch_pool,
244                           Q_("%d path changed", "%d paths changed",
245                              apr_hash_count(paths)),
246                           apr_hash_count(paths));
247  SVN_ERR(svn_cmdline_printf(scratch_pool,
248                             "%-30s %s, %s, %s\n",
249                             shelf->name, version_str, age_str, paths_str));
250
251  if (with_logmsg)
252    {
253      char *log_message;
254
255      SVN_ERR(svn_client__shelf_get_log_message(&log_message, shelf,
256                                               scratch_pool));
257      if (log_message)
258        {
259          SVN_ERR(svn_cmdline_printf(scratch_pool,
260                                     _(" %.50s\n"),
261                                     log_message));
262        }
263    }
264
265  return SVN_NO_ERROR;
266}
267
268/* Display a list of shelves */
269static svn_error_t *
270shelves_list(const char *local_abspath,
271             svn_boolean_t quiet,
272             svn_client_ctx_t *ctx,
273             apr_pool_t *scratch_pool)
274{
275  apr_time_t time_now = apr_time_now();
276  apr_array_header_t *list;
277  int i;
278
279  SVN_ERR(list_sorted_by_date(&list,
280                              local_abspath, ctx, scratch_pool));
281
282  for (i = 0; i < list->nelts; i++)
283    {
284      const svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t);
285      const char *name = item->key;
286      svn_client__shelf_t *shelf;
287      svn_client__shelf_version_t *shelf_version;
288
289      SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
290                                             ctx, scratch_pool));
291      SVN_ERR(svn_client__shelf_get_newest_version(&shelf_version, shelf,
292                                                  scratch_pool, scratch_pool));
293      if (quiet)
294        SVN_ERR(svn_cmdline_printf(scratch_pool, "%s\n", shelf->name));
295      else if (!shelf_version)
296        SVN_ERR(svn_cmdline_printf(scratch_pool, "%-30s no versions\n",
297                                   shelf->name));
298      else
299        SVN_ERR(stats(shelf, shelf->max_version, shelf_version, time_now,
300                      TRUE /*with_logmsg*/, scratch_pool));
301      SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
302    }
303
304  return SVN_NO_ERROR;
305}
306
307/* Print info about each checkpoint of the shelf named NAME.
308 */
309static svn_error_t *
310shelf_log(const char *name,
311          const char *local_abspath,
312          svn_client_ctx_t *ctx,
313          apr_pool_t *scratch_pool)
314{
315  apr_time_t time_now = apr_time_now();
316  svn_client__shelf_t *shelf;
317  apr_array_header_t *versions;
318  int i;
319
320  SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
321                                         ctx, scratch_pool));
322  SVN_ERR(svn_client__shelf_get_all_versions(&versions, shelf,
323                                            scratch_pool, scratch_pool));
324  for (i = 0; i < versions->nelts; i++)
325    {
326      svn_client__shelf_version_t *shelf_version
327        = APR_ARRAY_IDX(versions, i, void *);
328
329      SVN_ERR(stats(shelf, i + 1, shelf_version, time_now,
330                    FALSE /*with_logmsg*/, scratch_pool));
331    }
332
333  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
334  return SVN_NO_ERROR;
335}
336
337/* Find the name of the youngest shelf.
338 */
339static svn_error_t *
340name_of_youngest(const char **name_p,
341                 const char *local_abspath,
342                 svn_client_ctx_t *ctx,
343                 apr_pool_t *result_pool,
344                 apr_pool_t *scratch_pool)
345{
346  apr_array_header_t *list;
347  const svn_sort__item_t *youngest_item;
348
349  SVN_ERR(list_sorted_by_date(&list,
350                              local_abspath, ctx, scratch_pool));
351  if (list->nelts == 0)
352    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
353                            _("No shelves found"));
354
355  youngest_item = &APR_ARRAY_IDX(list, list->nelts - 1, svn_sort__item_t);
356  *name_p = apr_pstrdup(result_pool, youngest_item->key);
357  return SVN_NO_ERROR;
358}
359
360struct status_baton
361{
362  /* These fields correspond to the ones in the
363     svn_cl__print_status() interface. */
364  const char *target_abspath;
365  const char *target_path;
366
367  svn_boolean_t quiet;  /* don't display statuses while shelving them */
368  int num_paths_shelved;
369  int num_paths_not_shelved;
370  svn_client_ctx_t *ctx;
371};
372
373/* A status callback function for printing STATUS for PATH. */
374static svn_error_t *
375print_status(void *baton,
376             const char *path,
377             const svn_client_status_t *status,
378             apr_pool_t *scratch_pool)
379{
380  struct status_baton *sb = baton;
381  unsigned int conflicts;
382
383  return svn_cl__print_status(sb->target_abspath, sb->target_path,
384                              path, status,
385                              TRUE /*suppress_externals_placeholders*/,
386                              FALSE /*detailed*/,
387                              FALSE /*show_last_committed*/,
388                              TRUE /*skip_unrecognized*/,
389                              FALSE /*repos_locks*/,
390                              &conflicts, &conflicts, &conflicts,
391                              sb->ctx,
392                              scratch_pool);
393}
394
395/* A callback function for shelved paths. */
396static svn_error_t *
397was_shelved(void *baton,
398            const char *path,
399            const svn_client_status_t *status,
400            apr_pool_t *scratch_pool)
401{
402  struct status_baton *sb = baton;
403
404  if (!sb->quiet)
405    {
406      SVN_ERR(print_status(baton, path, status, scratch_pool));
407    }
408
409  ++sb->num_paths_shelved;
410  return SVN_NO_ERROR;
411}
412
413/* A callback function for not-shelved paths. */
414static svn_error_t *
415was_not_shelved(void *baton,
416                const char *path,
417                const svn_client_status_t *status,
418                apr_pool_t *scratch_pool)
419{
420  struct status_baton *sb = baton;
421
422  SVN_ERR(print_status(baton, path, status, scratch_pool));
423  SVN_ERR(svn_cmdline_printf(scratch_pool, "      >   not shelved\n"));
424  ++sb->num_paths_not_shelved;
425  return SVN_NO_ERROR;
426}
427
428/** Shelve/save a new version of changes.
429 *
430 * Shelve in shelf @a name the local modifications found by @a paths,
431 * @a depth, @a changelists. Revert the shelved changes from the WC
432 * unless @a keep_local is true.
433 *
434 * If no local modifications are found, throw an error.
435 *
436 * If @a dry_run is true, don't actually do it.
437 *
438 * Report in @a *new_version_p the new version number (or, with dry run,
439 * what it would be).
440 */
441static svn_error_t *
442shelve(int *new_version_p,
443       const char *name,
444       const apr_array_header_t *paths,
445       svn_depth_t depth,
446       const apr_array_header_t *changelists,
447       apr_hash_t *revprop_table,
448       svn_boolean_t keep_local,
449       svn_boolean_t dry_run,
450       svn_boolean_t quiet,
451       const char *local_abspath,
452       svn_client_ctx_t *ctx,
453       apr_pool_t *scratch_pool)
454{
455  svn_client__shelf_t *shelf;
456  svn_client__shelf_version_t *previous_version;
457  svn_client__shelf_version_t *new_version;
458  struct status_baton sb;
459
460  SVN_ERR(svn_client__shelf_open_or_create(&shelf,
461                                          name, local_abspath,
462                                          ctx, scratch_pool));
463  SVN_ERR(svn_client__shelf_get_newest_version(&previous_version, shelf,
464                                              scratch_pool, scratch_pool));
465
466  if (! quiet)
467    {
468      SVN_ERR(svn_cmdline_printf(scratch_pool, keep_local
469                                 ? _("--- Save a new version of '%s' in WC root '%s'\n")
470                                 : _("--- Shelve '%s' in WC root '%s'\n"),
471                                 shelf->name, shelf->wc_root_abspath));
472      SVN_ERR(stats(shelf, shelf->max_version, previous_version, apr_time_now(),
473                    TRUE /*with_logmsg*/, scratch_pool));
474    }
475
476  sb.target_abspath = shelf->wc_root_abspath;
477  sb.target_path = "";
478  sb.quiet = quiet;
479  sb.num_paths_shelved = 0;
480  sb.num_paths_not_shelved = 0;
481  sb.ctx = ctx;
482
483  if (! quiet)
484    SVN_ERR(svn_cmdline_printf(scratch_pool,
485                               keep_local ? _("--- Saving...\n")
486                               : _("--- Shelving...\n")));
487  SVN_ERR(svn_client__shelf_save_new_version3(&new_version, shelf,
488                                             paths, depth, changelists,
489                                             was_shelved, &sb,
490                                             was_not_shelved, &sb,
491                                             scratch_pool));
492  if (sb.num_paths_not_shelved > 0)
493    {
494      SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, previous_version,
495                                                     scratch_pool));
496      SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
497      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
498                               Q_("%d path could not be shelved",
499                                  "%d paths could not be shelved",
500                                  sb.num_paths_not_shelved),
501                               sb.num_paths_not_shelved);
502    }
503  if (sb.num_paths_shelved == 0
504      || ! new_version)
505    {
506      SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
507      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
508                               keep_local ? _("No local modifications could be saved")
509                               : _("No local modifications could be shelved"));
510    }
511
512  /* Un-apply the changes, if required. */
513  if (!keep_local)
514    {
515      SVN_ERR(svn_client__shelf_unapply(new_version,
516                                       dry_run, scratch_pool));
517    }
518
519  /* Fetch the log message and any other revprops */
520  if (ctx->log_msg_func3)
521    {
522      const char *tmp_file;
523      apr_array_header_t *commit_items
524        = apr_array_make(scratch_pool, 1, sizeof(void *));
525      const char *message = "";
526
527      SVN_ERR(ctx->log_msg_func3(&message, &tmp_file, commit_items,
528                                 ctx->log_msg_baton3, scratch_pool));
529      /* Abort the shelving if the log message callback requested so. */
530      if (! message)
531        return SVN_NO_ERROR;
532
533      if (message && !dry_run)
534        {
535          svn_string_t *propval = svn_string_create(message, scratch_pool);
536
537          if (! revprop_table)
538            revprop_table = apr_hash_make(scratch_pool);
539          svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, propval);
540        }
541    }
542
543  SVN_ERR(svn_client__shelf_revprop_set_all(shelf, revprop_table, scratch_pool));
544
545  if (new_version_p)
546    *new_version_p = shelf->max_version;
547
548  if (dry_run)
549    {
550      SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, previous_version,
551                                                     scratch_pool));
552    }
553
554  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
555  return SVN_NO_ERROR;
556}
557
558/* Return the single character representation of STATUS.
559 * (Similar to subversion/svn/status.c:generate_status_code()
560 * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */
561static char
562status_to_char(enum svn_wc_status_kind status)
563{
564  switch (status)
565    {
566    case svn_wc_status_none:        return '.';
567    case svn_wc_status_unversioned: return '?';
568    case svn_wc_status_normal:      return ' ';
569    case svn_wc_status_added:       return 'A';
570    case svn_wc_status_missing:     return '!';
571    case svn_wc_status_deleted:     return 'D';
572    case svn_wc_status_replaced:    return 'R';
573    case svn_wc_status_modified:    return 'M';
574    case svn_wc_status_merged:      return 'G';
575    case svn_wc_status_conflicted:  return 'C';
576    case svn_wc_status_ignored:     return 'I';
577    case svn_wc_status_obstructed:  return '~';
578    case svn_wc_status_external:    return 'X';
579    case svn_wc_status_incomplete:  return ':';
580    default:                        return '*';
581    }
582}
583
584/* Throw an error if any path affected by SHELF_VERSION gives a conflict
585 * when applied (as a dry-run) to the WC. */
586static svn_error_t *
587test_apply(svn_client__shelf_version_t *shelf_version,
588           svn_client_ctx_t *ctx,
589           apr_pool_t *scratch_pool)
590{
591  apr_hash_t *paths;
592  apr_hash_index_t *hi;
593
594  SVN_ERR(svn_client__shelf_paths_changed(&paths, shelf_version,
595                                         scratch_pool, scratch_pool));
596  for (hi = apr_hash_first(scratch_pool, paths); hi; hi = apr_hash_next(hi))
597    {
598      const char *path = apr_hash_this_key(hi);
599      svn_boolean_t conflict;
600
601      SVN_ERR(svn_client__shelf_test_apply_file(&conflict, shelf_version, path,
602                                               scratch_pool));
603      if (conflict)
604        {
605          char *to_wc_abspath
606            = svn_dirent_join(shelf_version->shelf->wc_root_abspath, path,
607                              scratch_pool);
608          svn_wc_status3_t *status;
609
610          SVN_ERR(svn_wc_status3(&status, ctx->wc_ctx, to_wc_abspath,
611                                 scratch_pool, scratch_pool));
612          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
613                                   _("Shelved path '%s' already has "
614                                     "status '%c' in the working copy"),
615                                   path, status_to_char(status->node_status));
616        }
617    }
618  return SVN_NO_ERROR;
619}
620
621/** Restore/unshelve a given or newest version of changes.
622 *
623 * Restore local modifications from shelf @a name version @a arg,
624 * or the newest version is @a arg is null.
625 *
626 * If @a dry_run is true, don't actually do it.
627 *
628 * Error if any path would have a conflict, unless @a force_if_conflict.
629 */
630static svn_error_t *
631shelf_restore(const char *name,
632              const char *arg,
633              svn_boolean_t dry_run,
634              svn_boolean_t quiet,
635              svn_boolean_t force_if_conflict,
636              const char *local_abspath,
637              svn_client_ctx_t *ctx,
638              apr_pool_t *scratch_pool)
639{
640  int version, old_version;
641  apr_time_t time_now = apr_time_now();
642  svn_client__shelf_t *shelf;
643  svn_client__shelf_version_t *shelf_version;
644
645  SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
646                                         ctx, scratch_pool));
647
648  old_version = shelf->max_version;
649  if (arg)
650    {
651      SVN_ERR(svn_cstring_atoi(&version, arg));
652      SVN_ERR(svn_client__shelf_version_open(&shelf_version,
653                                            shelf, version,
654                                            scratch_pool, scratch_pool));
655    }
656  else
657    {
658      version = shelf->max_version;
659      SVN_ERR(get_newest_version_existing(&shelf_version, shelf,
660                                          scratch_pool, scratch_pool));
661    }
662
663  if (! quiet)
664    {
665      SVN_ERR(svn_cmdline_printf(scratch_pool,
666                                 _("--- Unshelve '%s' in WC root '%s'\n"),
667                                 shelf->name, shelf->wc_root_abspath));
668      SVN_ERR(stats(shelf, version, shelf_version, time_now,
669                    TRUE /*with_logmsg*/, scratch_pool));
670    }
671  if (! force_if_conflict)
672    {
673      SVN_ERR_W(test_apply(shelf_version, ctx, scratch_pool),
674                _("Cannot unshelve/restore, as at least one shelved "
675                  "path would conflict with a local modification "
676                  "or other status in the working copy"));
677    }
678
679  SVN_ERR(svn_client__shelf_apply(shelf_version,
680                                 dry_run, scratch_pool));
681
682  if (! dry_run)
683    {
684      SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, shelf_version,
685                                                     scratch_pool));
686    }
687
688  if (!quiet)
689    {
690      if (version < old_version)
691        SVN_ERR(svn_cmdline_printf(scratch_pool,
692                                   Q_("restored '%s' version %d and deleted %d newer version\n",
693                                      "restored '%s' version %d and deleted %d newer versions\n",
694                                      old_version - version),
695                                   name, version, old_version - version));
696      else
697        SVN_ERR(svn_cmdline_printf(scratch_pool,
698                                   _("restored '%s' version %d (the newest version)\n"),
699                                   name, version));
700    }
701
702  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
703  return SVN_NO_ERROR;
704}
705
706static svn_error_t *
707shelf_diff(const char *name,
708           const char *arg,
709           const char *local_abspath,
710           svn_boolean_t summarize,
711           svn_depth_t depth,
712           svn_boolean_t ignore_ancestry,
713           svn_client_ctx_t *ctx,
714           apr_pool_t *scratch_pool)
715{
716  svn_client__shelf_t *shelf;
717  svn_client__shelf_version_t *shelf_version;
718  svn_stream_t *stream, *errstream;
719  svn_diff_tree_processor_t *diff_processor;
720
721  SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
722                                         ctx, scratch_pool));
723
724  if (arg)
725    {
726      int version;
727
728      SVN_ERR(svn_cstring_atoi(&version, arg));
729      SVN_ERR(svn_client__shelf_version_open(&shelf_version,
730                                            shelf, version,
731                                            scratch_pool, scratch_pool));
732    }
733  else
734    {
735      SVN_ERR(get_newest_version_existing(&shelf_version, shelf,
736                                          scratch_pool, scratch_pool));
737    }
738
739  SVN_ERR(svn_stream_for_stdout(&stream, scratch_pool));
740  errstream = svn_stream_empty(scratch_pool);
741
742  if (summarize)
743    {
744      svn_client_diff_summarize_func_t func;
745      void *baton;
746
747      SVN_ERR(svn_cl__get_diff_summary_writer(&func, &baton,
748                                              FALSE /*xml*/,
749                                              FALSE /*ignore_properties*/,
750                                              "" /*anchor/prefix*/,
751                                              scratch_pool, scratch_pool));
752      SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor,
753                                                       func, baton,
754                                                       scratch_pool,
755                                                       scratch_pool));
756    }
757  else
758    {
759      SVN_ERR(svn_client__get_diff_writer_svn(
760                &diff_processor,
761                NULL /*anchor*/,
762                "", "", /*orig_path_1, orig_path_2,*/
763                NULL /*options*/,
764                "" /*relative_to_dir*/,
765                FALSE /*no_diff_added*/,
766                FALSE /*no_diff_deleted*/,
767                FALSE /*show_copies_as_adds*/,
768                FALSE /*ignore_content_type*/,
769                FALSE /*ignore_properties*/,
770                FALSE /*properties_only*/,
771                TRUE /*pretty_print_mergeinfo*/,
772                svn_cmdline_output_encoding(scratch_pool),
773                stream, errstream,
774                ctx, scratch_pool));
775    }
776
777  SVN_ERR(svn_client__shelf_diff(shelf_version, "",
778                                 depth, ignore_ancestry,
779                                 diff_processor, scratch_pool));
780  SVN_ERR(svn_stream_close(stream));
781
782  SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
783  return SVN_NO_ERROR;
784}
785
786/* This implements the `svn_opt_subcommand_t' interface. */
787static svn_error_t *
788shelf_drop(const char *name,
789           const char *local_abspath,
790           svn_boolean_t dry_run,
791           svn_boolean_t quiet,
792           svn_client_ctx_t *ctx,
793           apr_pool_t *scratch_pool)
794{
795  SVN_ERR(svn_client__shelf_delete(name, local_abspath, dry_run,
796                                  ctx, scratch_pool));
797  if (! quiet)
798    SVN_ERR(svn_cmdline_printf(scratch_pool,
799                               _("deleted '%s'\n"),
800                               name));
801  return SVN_NO_ERROR;
802}
803
804/*  */
805static svn_error_t *
806shelf_shelve(int *new_version,
807             const char *name,
808             apr_array_header_t *targets,
809             svn_depth_t depth,
810             apr_array_header_t *changelists,
811             apr_hash_t *revprop_table,
812             svn_boolean_t keep_local,
813             svn_boolean_t dry_run,
814             svn_boolean_t quiet,
815             svn_client_ctx_t *ctx,
816             apr_pool_t *scratch_pool)
817{
818  const char *local_abspath;
819
820  if (depth == svn_depth_unknown)
821    depth = svn_depth_infinity;
822
823  SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
824
825  SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
826
827  svn_opt_push_implicit_dot_target(targets, scratch_pool);
828
829  /* ### TODO: check all paths are in same WC; for now use first path */
830  SVN_ERR(svn_dirent_get_absolute(&local_abspath,
831                                  APR_ARRAY_IDX(targets, 0, char *),
832                                  scratch_pool));
833
834  SVN_ERR(shelve(new_version, name,
835                 targets, depth, changelists,
836                 revprop_table,
837                 keep_local, dry_run, quiet,
838                 local_abspath, ctx, scratch_pool));
839
840  return SVN_NO_ERROR;
841}
842
843static svn_error_t *
844svn_cl__shelf_shelve(apr_getopt_t *os,
845                     void *baton,
846                     apr_pool_t *pool);
847
848/* This implements the `svn_opt_subcommand_t' interface. */
849static svn_error_t *
850svn_cl__shelf_save(apr_getopt_t *os,
851                   void *baton,
852                   apr_pool_t *pool)
853{
854  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
855
856  opt_state->keep_local = TRUE;
857  SVN_ERR(svn_cl__shelf_shelve(os, baton, pool));
858  return SVN_NO_ERROR;
859}
860
861/* This implements the `svn_opt_subcommand_t' interface. */
862static svn_error_t *
863svn_cl__shelf_shelve(apr_getopt_t *os,
864                     void *baton,
865                     apr_pool_t *pool)
866{
867  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
868  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
869  const char *name;
870  apr_array_header_t *targets;
871
872  if (opt_state->quiet)
873    ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */
874
875  SVN_ERR(get_next_argument(&name, os, pool, pool));
876
877  /* Parse the remaining arguments as paths. */
878  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
879                                                      opt_state->targets,
880                                                      ctx, FALSE, pool));
881  {
882    int new_version;
883    svn_error_t *err;
884
885    if (ctx->log_msg_func3)
886      SVN_ERR(svn_cl__make_log_msg_baton(&ctx->log_msg_baton3,
887                                         opt_state, NULL, ctx->config,
888                                         pool));
889    err = shelf_shelve(&new_version, name,
890                       targets, opt_state->depth, opt_state->changelists,
891                       opt_state->revprop_table,
892                       opt_state->keep_local, opt_state->dry_run,
893                       opt_state->quiet, ctx, pool);
894    if (ctx->log_msg_func3)
895      SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3,
896                                      err, pool));
897    else
898      SVN_ERR(err);
899
900      if (! opt_state->quiet)
901      {
902        if (opt_state->keep_local)
903          SVN_ERR(svn_cmdline_printf(pool,
904                                     _("saved '%s' version %d\n"),
905                                     name, new_version));
906        else
907          SVN_ERR(svn_cmdline_printf(pool,
908                                     _("shelved '%s' version %d\n"),
909                                     name, new_version));
910      }
911  }
912
913  return SVN_NO_ERROR;
914}
915
916/* This implements the `svn_opt_subcommand_t' interface. */
917static svn_error_t *
918svn_cl__shelf_unshelve(apr_getopt_t *os,
919                       void *baton,
920                       apr_pool_t *scratch_pool)
921{
922  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
923  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
924  const char *local_abspath;
925  const char *name;
926  const char *arg = NULL;
927
928  SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", scratch_pool));
929
930  if (os->ind < os->argc)
931    {
932      SVN_ERR(get_next_argument(&name, os, scratch_pool, scratch_pool));
933    }
934  else
935    {
936      SVN_ERR(name_of_youngest(&name,
937                               local_abspath, ctx, scratch_pool, scratch_pool));
938      SVN_ERR(svn_cmdline_printf(scratch_pool,
939                                 _("unshelving the youngest shelf, '%s'\n"),
940                                 name));
941    }
942
943  /* Which checkpoint number? */
944  if (os->ind < os->argc)
945    SVN_ERR(get_next_argument(&arg, os, scratch_pool, scratch_pool));
946
947  if (os->ind < os->argc)
948    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
949                            _("Too many arguments"));
950
951  if (opt_state->quiet)
952    ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */
953
954  SVN_ERR(shelf_restore(name, arg,
955                        opt_state->dry_run, opt_state->quiet,
956                        opt_state->force /*force_already_modified*/,
957                        local_abspath, ctx, scratch_pool));
958
959  if (opt_state->drop)
960    {
961      SVN_ERR(shelf_drop(name, local_abspath,
962                         opt_state->dry_run, opt_state->quiet,
963                         ctx, scratch_pool));
964    }
965  return SVN_NO_ERROR;
966}
967
968/* This implements the `svn_opt_subcommand_t' interface. */
969static svn_error_t *
970svn_cl__shelf_list(apr_getopt_t *os,
971                   void *baton,
972                   apr_pool_t *pool)
973{
974  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
975  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
976  apr_array_header_t *targets = NULL;
977  apr_pool_t *iterpool = svn_pool_create(pool);
978  int i;
979
980  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
981                                                      opt_state->targets,
982                                                      ctx, FALSE, pool));
983  /* Add "." if user passed 0 arguments */
984  svn_opt_push_implicit_dot_target(targets, pool);
985
986  for (i = 0; i < targets->nelts; ++i)
987    {
988      const char *local_abspath;
989      const char *target = APR_ARRAY_IDX(targets, i, const char *);
990
991      svn_pool_clear(iterpool);
992
993      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
994
995      SVN_ERR(shelves_list(local_abspath,
996                           opt_state->quiet,
997                           ctx, iterpool));
998    }
999
1000  svn_pool_destroy(iterpool);
1001
1002  return SVN_NO_ERROR;
1003}
1004
1005/* "svn shelf-list-by-paths [PATH...]"
1006 *
1007 * TARGET_RELPATHS are all within the same WC, relative to WC_ROOT_ABSPATH.
1008 */
1009static svn_error_t *
1010shelf_list_by_paths(apr_array_header_t *target_relpaths,
1011                    const char *wc_root_abspath,
1012                    svn_client_ctx_t *ctx,
1013                    apr_pool_t *scratch_pool)
1014{
1015  apr_array_header_t *shelves;
1016  apr_hash_t *paths_to_shelf_name = apr_hash_make(scratch_pool);
1017  apr_array_header_t *array;
1018  int i;
1019
1020  SVN_ERR(list_sorted_by_date(&shelves,
1021                              wc_root_abspath, ctx, scratch_pool));
1022
1023  /* Check paths are valid */
1024  for (i = 0; i < target_relpaths->nelts; i++)
1025    {
1026      char *target_relpath = APR_ARRAY_IDX(target_relpaths, i, char *);
1027
1028      if (svn_path_is_url(target_relpath))
1029        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1030                                 _("'%s' is not a local path"), target_relpath);
1031      SVN_ERR_ASSERT(svn_relpath_is_canonical(target_relpath));
1032    }
1033
1034  /* Find the most recent shelf for each affected path */
1035  for (i = 0; i < shelves->nelts; i++)
1036    {
1037      svn_sort__item_t *item = &APR_ARRAY_IDX(shelves, i, svn_sort__item_t);
1038      const char *name = item->key;
1039      svn_client__shelf_t *shelf;
1040      svn_client__shelf_version_t *shelf_version;
1041      apr_hash_t *shelf_paths;
1042      int j;
1043
1044      SVN_ERR(svn_client__shelf_open_existing(&shelf,
1045                                              name, wc_root_abspath,
1046                                              ctx, scratch_pool));
1047      SVN_ERR(svn_client__shelf_get_newest_version(&shelf_version, shelf,
1048                                                   scratch_pool, scratch_pool));
1049      if (!shelf_version)
1050        continue;
1051      SVN_ERR(svn_client__shelf_paths_changed(&shelf_paths,
1052                                              shelf_version,
1053                                              scratch_pool, scratch_pool));
1054      for (j = 0; j < target_relpaths->nelts; j++)
1055        {
1056          char *target_relpath = APR_ARRAY_IDX(target_relpaths, j, char *);
1057          apr_hash_index_t *hi;
1058
1059          for (hi = apr_hash_first(scratch_pool, shelf_paths);
1060               hi; hi = apr_hash_next(hi))
1061            {
1062              const char *shelf_path = apr_hash_this_key(hi);
1063
1064              if (svn_relpath_skip_ancestor(target_relpath, shelf_path))
1065                {
1066                  if (! svn_hash_gets(paths_to_shelf_name, shelf_path))
1067                    {
1068                      svn_hash_sets(paths_to_shelf_name, shelf_path, shelf->name);
1069                    }
1070                }
1071            }
1072        }
1073    }
1074
1075  /* Print the results. */
1076  array = svn_sort__hash(paths_to_shelf_name,
1077                         svn_sort_compare_items_as_paths,
1078                         scratch_pool);
1079  for (i = 0; i < array->nelts; i++)
1080    {
1081      svn_sort__item_t *item = &APR_ARRAY_IDX(array, i, svn_sort__item_t);
1082      const char *path = item->key;
1083      const char *name = item->value;
1084
1085      SVN_ERR(svn_cmdline_printf(scratch_pool, "%-20.20s %s\n",
1086                                 name,
1087                                 svn_dirent_local_style(path, scratch_pool)));
1088    }
1089  return SVN_NO_ERROR;
1090}
1091
1092/* This implements the `svn_opt_subcommand_t' interface. */
1093static svn_error_t *
1094svn_cl__shelf_list_by_paths(apr_getopt_t *os,
1095                            void *baton,
1096                            apr_pool_t *pool)
1097{
1098  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
1099  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
1100  const char *wc_root_abspath;
1101  apr_array_header_t *targets;
1102
1103  /* Parse the remaining arguments as paths. */
1104  SVN_ERR(targets_relative_to_a_wc(&wc_root_abspath, &targets,
1105                                   os, opt_state->targets,
1106                                   ctx, pool, pool));
1107
1108  SVN_ERR(shelf_list_by_paths(targets, wc_root_abspath, ctx, pool));
1109  return SVN_NO_ERROR;
1110}
1111
1112/* This implements the `svn_opt_subcommand_t' interface. */
1113static svn_error_t *
1114svn_cl__shelf_diff(apr_getopt_t *os,
1115                   void *baton,
1116                   apr_pool_t *pool)
1117{
1118  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
1119  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
1120  const char *local_abspath;
1121  const char *name;
1122  const char *arg = NULL;
1123
1124  SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool));
1125
1126  SVN_ERR(get_next_argument(&name, os, pool, pool));
1127
1128  /* Which checkpoint number? */
1129  if (os->ind < os->argc)
1130    SVN_ERR(get_next_argument(&arg, os, pool, pool));
1131
1132  if (os->ind < os->argc)
1133    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1134                            _("Too many arguments"));
1135
1136  SVN_ERR(shelf_diff(name, arg, local_abspath,
1137                     opt_state->diff.summarize,
1138                     opt_state->depth, opt_state->ignore_ancestry,
1139                     ctx, pool));
1140
1141  return SVN_NO_ERROR;
1142}
1143
1144/* This implements the `svn_opt_subcommand_t' interface. */
1145static svn_error_t *
1146svn_cl__shelf_drop(apr_getopt_t *os,
1147                   void *baton,
1148                   apr_pool_t *pool)
1149{
1150  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
1151  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
1152  const char *name;
1153  apr_array_header_t *targets = NULL;
1154  apr_pool_t *iterpool = svn_pool_create(pool);
1155  int i;
1156
1157  SVN_ERR(get_next_argument(&name, os, pool, pool));
1158
1159  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
1160                                                      opt_state->targets,
1161                                                      ctx, FALSE, pool));
1162  svn_opt_push_implicit_dot_target(targets, pool);
1163
1164  for (i = 0; i < targets->nelts; ++i)
1165    {
1166      const char *local_abspath;
1167      const char *target = APR_ARRAY_IDX(targets, i, const char *);
1168
1169      svn_pool_clear(iterpool);
1170
1171      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1172      SVN_ERR(shelf_drop(name, local_abspath,
1173                         opt_state->dry_run, opt_state->quiet,
1174                         ctx, iterpool));
1175    }
1176
1177  svn_pool_destroy(iterpool);
1178
1179  return SVN_NO_ERROR;
1180}
1181
1182/* This implements the `svn_opt_subcommand_t' interface. */
1183static svn_error_t *
1184svn_cl__shelf_log(apr_getopt_t *os,
1185                  void *baton,
1186                  apr_pool_t *pool)
1187{
1188  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
1189  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
1190  const char *name;
1191  apr_array_header_t *targets = NULL;
1192  apr_pool_t *iterpool = svn_pool_create(pool);
1193  int i;
1194
1195  SVN_ERR(get_next_argument(&name, os, pool, pool));
1196
1197  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
1198                                                      opt_state->targets,
1199                                                      ctx, FALSE, pool));
1200  svn_opt_push_implicit_dot_target(targets, pool);
1201
1202  for (i = 0; i < targets->nelts; ++i)
1203    {
1204      const char *local_abspath;
1205      const char *target = APR_ARRAY_IDX(targets, i, const char *);
1206
1207      svn_pool_clear(iterpool);
1208
1209      SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1210      SVN_ERR(shelf_log(name, local_abspath, ctx, iterpool));
1211    }
1212
1213  svn_pool_destroy(iterpool);
1214
1215  return SVN_NO_ERROR;
1216}
1217
1218/**************************************************************************/
1219
1220/* This implements the `svn_opt_subcommand_t' interface. */
1221static svn_error_t *
1222svn_cl__wc_copy_mods(apr_getopt_t *os,
1223                     void *baton,
1224                     apr_pool_t *pool)
1225{
1226  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
1227  const char *src_wc_abspath, *dst_wc_abspath;
1228
1229  SVN_ERR(get_next_argument(&src_wc_abspath, os, pool, pool));
1230  SVN_ERR(svn_dirent_get_absolute(&src_wc_abspath, src_wc_abspath, pool));
1231
1232  SVN_ERR(get_next_argument(&dst_wc_abspath, os, pool, pool));
1233  SVN_ERR(svn_dirent_get_absolute(&dst_wc_abspath, dst_wc_abspath, pool));
1234
1235  SVN_ERR(svn_client__wc_copy_mods(src_wc_abspath, dst_wc_abspath,
1236                                   ctx->notify_func2, ctx->notify_baton2,
1237                                   ctx, pool));
1238
1239  return SVN_NO_ERROR;
1240}
1241
1242const svn_opt_subcommand_desc3_t
1243svn_cl__cmd_table_shelf3[] =
1244{
1245  { "x-shelf-diff", svn_cl__shelf_diff, {0}, {N_(
1246     "Show shelved changes as a diff.\n"
1247     "usage: x-shelf-diff SHELF [VERSION]\n"
1248     "\n"), N_(
1249     "  Show the changes in SHELF:VERSION (default: latest) as a diff.\n"
1250     "\n"), N_(
1251     "  See also: 'svn diff --cl=svn:shelf:SHELF' which supports most options of\n"
1252     "  'svn diff'.\n"
1253     "\n"), N_(
1254     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1255     "  in the next release, and there is no promise of backward compatibility.\n"
1256    )},
1257    {opt_summarize},
1258  },
1259
1260  { "x-shelf-drop", svn_cl__shelf_drop, {0}, {N_(
1261     "Delete a shelf.\n"
1262     "usage: x-shelf-drop SHELF [PATH ...]\n"
1263     "\n"), N_(
1264     "  Delete the shelves named SHELF from the working copies containing PATH\n"
1265     "  (default PATH is '.')\n"
1266     "\n"), N_(
1267     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1268     "  in the next release, and there is no promise of backward compatibility.\n"
1269    )},
1270  },
1271
1272  { "x-shelf-list", svn_cl__shelf_list, {"x-shelves"}, {N_(
1273     "List shelves.\n"
1274     "usage: x-shelf-list [PATH ...]\n"
1275     "\n"), N_(
1276     "  List shelves for each working copy containing PATH (default is '.')\n"
1277     "  Include the first line of any log message and some details about the\n"
1278     "  contents of the shelf, unless '-q' is given.\n"
1279     "\n"), N_(
1280     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1281     "  in the next release, and there is no promise of backward compatibility.\n"
1282    )},
1283    {'q', 'v'}
1284  },
1285
1286  { "x-shelf-list-by-paths", svn_cl__shelf_list_by_paths, {0}, {N_(
1287     "List which shelf affects each path.\n"
1288     "usage: x-shelf-list-by-paths [PATH...]\n"
1289     "\n"), N_(
1290     "  List which shelf most recently affects each path below the given PATHs.\n"
1291     "\n"), N_(
1292     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1293     "  in the next release, and there is no promise of backward compatibility.\n"
1294    )},
1295  },
1296
1297  { "x-shelf-log", svn_cl__shelf_log, {0}, {N_(
1298     "Show the versions of a shelf.\n"
1299     "usage: x-shelf-log SHELF [PATH...]\n"
1300     "\n"), N_(
1301     "  Show all versions of SHELF for each working copy containing PATH (the\n"
1302     "  default PATH is '.').\n"
1303     "\n"), N_(
1304     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1305     "  in the next release, and there is no promise of backward compatibility.\n"
1306    )},
1307    {'q', 'v'}
1308  },
1309
1310  { "x-shelf-save", svn_cl__shelf_save, {0}, {N_(
1311     "Copy local changes onto a new version of a shelf.\n"
1312     "usage: x-shelf-save SHELF [PATH...]\n"
1313     "\n"), N_(
1314     "  Save local changes in the given PATHs as a new version of SHELF.\n"
1315     "  The shelf's log message can be set with -m, -F, etc.\n"
1316     "\n"), N_(
1317     "  The same as 'svn shelve --keep-local'.\n"
1318     "\n"), N_(
1319     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1320     "  in the next release, and there is no promise of backward compatibility.\n"
1321    )},
1322    {'q', opt_dry_run,
1323     opt_depth, opt_targets, opt_changelist,
1324     SVN_CL__LOG_MSG_OPTIONS,
1325    }
1326  },
1327
1328  { "x-shelve", svn_cl__shelf_shelve, {0}, {N_(
1329     "Move local changes onto a shelf.\n"
1330     "usage: x-shelve [--keep-local] SHELF [PATH...]\n"
1331     "\n"), N_(
1332     "  Save the local changes in the given PATHs to a new or existing SHELF.\n"
1333     "  Revert those changes from the WC unless '--keep-local' is given.\n"
1334     "  The shelf's log message can be set with -m, -F, etc.\n"
1335     "\n"), N_(
1336     "  'svn shelve --keep-local' is the same as 'svn shelf-save'.\n"
1337     "\n"), N_(
1338     "  The kinds of change you can shelve are committable changes to files and\n"
1339     "  properties, except the following kinds which are not yet supported:\n"
1340     "     * copies and moves\n"
1341     "     * mkdir and rmdir\n"
1342     "  Uncommittable states such as conflicts, unversioned and missing cannot\n"
1343     "  be shelved.\n"
1344     "\n"), N_(
1345     "  To bring back shelved changes, use 'svn unshelve SHELF'.\n"
1346     "\n"), N_(
1347     "  Shelves are currently stored under <WC>/.svn/experimental/shelves/ .\n"
1348     "  (In Subversion 1.10, shelves were stored under <WC>/.svn/shelves/ as\n"
1349     "  patch files. To recover a shelf created by 1.10, either use a 1.10\n"
1350     "  client to find and unshelve it, or find the patch file and use any\n"
1351     "  1.10 or later 'svn patch' to apply it.)\n"
1352     "\n"), N_(
1353     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1354     "  in the next release, and there is no promise of backward compatibility.\n"
1355    )},
1356    {'q', opt_dry_run, opt_keep_local,
1357     opt_depth, opt_targets, opt_changelist,
1358     SVN_CL__LOG_MSG_OPTIONS,
1359    } },
1360
1361  { "x-unshelve", svn_cl__shelf_unshelve, {0}, {N_(
1362     "Copy shelved changes back into the WC.\n"
1363     "usage: x-unshelve [--drop] [SHELF [VERSION]]\n"
1364     "\n"), N_(
1365     "  Apply the changes stored in SHELF to the working copy.\n"
1366     "  SHELF defaults to the newest shelf.\n"
1367     "\n"), N_(
1368     "  Apply the newest version of the shelf, by default. If VERSION is\n"
1369     "  specified, apply that version and discard all versions newer than that.\n"
1370     "  In any case, retain the unshelved version and versions older than that\n"
1371     "  (unless --drop is specified).\n"
1372     "\n"), N_(
1373     "  With --drop, delete the entire shelf (like 'svn shelf-drop') after\n"
1374     "  successfully unshelving with no conflicts.\n"
1375     "\n"), N_(
1376     "  The working files involved should be in a clean, unmodified state\n"
1377     "  before using this command. To roll back to an older version of the\n"
1378     "  shelf, first ensure any current working changes are removed, such as\n"
1379     "  by shelving or reverting them, and then unshelve the desired version.\n"
1380     "\n"), N_(
1381     "  Unshelve normally refuses to apply any changes if any path involved is\n"
1382     "  already modified (or has any other abnormal status) in the WC. With\n"
1383     "  --force, it does not check and may error out and/or produce partial or\n"
1384     "  unexpected results.\n"
1385     "\n"), N_(
1386     "  The shelving feature is EXPERIMENTAL. This command is likely to change\n"
1387     "  in the next release, and there is no promise of backward compatibility.\n"
1388    )},
1389    {opt_drop, 'q', opt_dry_run, opt_force} },
1390
1391  { "x-wc-copy-mods", svn_cl__wc_copy_mods, {0}, {N_(
1392     "Copy local modifications from one WC to another.\n"
1393     "usage: x-wc-copy-mods SRC_WC_PATH DST_WC_PATH\n"
1394     "\n"), N_(
1395     "  The source and destination WC paths may be in the same WC or in different"
1396     "  WCs.\n"
1397     "\n"), N_(
1398     "  This feature is EXPERIMENTAL. This command is likely to change\n"
1399     "  in the next release, and there is no promise of backward compatibility.\n"
1400    )},
1401  },
1402
1403  { NULL, NULL, {0}, {NULL}, {0} }
1404};
1405
1406