]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svn/shelf-cmd.c
Update Subversion and dependencies to 1.14.0 LTS.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / svn / shelf-cmd.c
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. */
47 static svn_error_t *
48 get_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. */
66 static svn_error_t *
67 get_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  */
88 static svn_error_t *
89 targets_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. */
124 static svn_error_t *
125 targets_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  */
156 static char *
157 friendly_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. */
184 static int
185 compare_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  */
197 static svn_error_t *
198 list_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 /*  */
214 static svn_error_t *
215 stats(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 */
269 static svn_error_t *
270 shelves_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  */
309 static svn_error_t *
310 shelf_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  */
339 static svn_error_t *
340 name_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
360 struct 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. */
374 static svn_error_t *
375 print_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. */
396 static svn_error_t *
397 was_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. */
414 static svn_error_t *
415 was_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  */
441 static svn_error_t *
442 shelve(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().) */
561 static char
562 status_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. */
586 static svn_error_t *
587 test_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  */
630 static svn_error_t *
631 shelf_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
706 static svn_error_t *
707 shelf_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. */
787 static svn_error_t *
788 shelf_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 /*  */
805 static svn_error_t *
806 shelf_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
843 static svn_error_t *
844 svn_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. */
849 static svn_error_t *
850 svn_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. */
862 static svn_error_t *
863 svn_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. */
917 static svn_error_t *
918 svn_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. */
969 static svn_error_t *
970 svn_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  */
1009 static svn_error_t *
1010 shelf_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. */
1093 static svn_error_t *
1094 svn_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. */
1113 static svn_error_t *
1114 svn_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. */
1145 static svn_error_t *
1146 svn_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. */
1183 static svn_error_t *
1184 svn_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. */
1221 static svn_error_t *
1222 svn_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
1242 const svn_opt_subcommand_desc3_t
1243 svn_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