2 * shelf-cmd.c -- Shelving commands.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* We define this here to remove any further warnings about the usage of
25 experimental functions in this file. */
26 #define SVN_EXPERIMENTAL
28 #include "svn_client.h"
29 #include "svn_error_codes.h"
30 #include "svn_error.h"
33 #include "svn_props.h"
34 #include "svn_pools.h"
37 #include "shelf-cmd.h"
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"
46 /* Open the newest version of SHELF; error if no versions found. */
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)
53 SVN_ERR(svn_client__shelf_get_newest_version(shelf_version_p, shelf,
54 result_pool, scratch_pool));
55 if (!*shelf_version_p)
57 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
58 _("Shelf '%s': no versions found"),
65 /* Fetch the next argument. */
67 get_next_argument(const char **arg,
69 apr_pool_t *result_pool,
70 apr_pool_t *scratch_pool)
72 apr_array_header_t *args;
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 *),
81 /* Parse the remaining arguments as paths relative to a WC.
83 * TARGETS are relative to current working directory.
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.
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)
95 apr_hash_t *targets_by_wcroot = apr_hash_make(result_pool);
98 /* Make each target relative to the WC root. */
99 for (i = 0; i < targets->nelts; i++)
101 const char *target = APR_ARRAY_IDX(targets, i, const char *);
102 const char *wcroot_abspath;
103 apr_array_header_t *paths;
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);
111 paths = apr_array_make(result_pool, 0, sizeof(char *));
112 svn_hash_sets(targets_by_wcroot, wcroot_abspath, paths);
114 target = svn_dirent_skip_ancestor(wcroot_abspath, target);
117 APR_ARRAY_PUSH(paths, const char *) = target;
119 *targets_by_wcroot_p = targets_by_wcroot;
123 /* Return targets relative to a WC. Error if they refer to more than one WC. */
125 targets_relative_to_a_wc(const char **wc_root_abspath_p,
126 apr_array_header_t **paths_p,
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)
133 apr_array_header_t *targets;
134 apr_hash_t *targets_by_wcroot;
135 apr_hash_index_t *hi;
137 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
139 ctx, FALSE, result_pool));
140 svn_opt_push_implicit_dot_target(targets, result_pool);
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"));
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);
154 /* Return a human-friendly description of DURATION.
157 friendly_age_str(apr_time_t mtime,
159 apr_pool_t *result_pool)
161 int minutes = (int)((time_now - mtime) / 1000000 / 60);
164 if (minutes >= 60 * 24)
165 s = apr_psprintf(result_pool,
166 Q_("%d day ago", "%d days ago",
169 else if (minutes >= 60)
170 s = apr_psprintf(result_pool,
171 Q_("%d hour ago", "%d hours ago",
175 s = apr_psprintf(result_pool,
176 Q_("%d minute ago", "%d minutes ago",
182 /* A comparison function for svn_sort__hash(), comparing the mtime of two
183 svn_client_shelf_info_t's. */
185 compare_shelf_infos_by_mtime(const svn_sort__item_t *a,
186 const svn_sort__item_t *b)
188 svn_client__shelf_info_t *a_val = a->value;
189 svn_client__shelf_info_t *b_val = b->value;
191 return (a_val->mtime < b_val->mtime)
192 ? -1 : (a_val->mtime > b_val->mtime) ? 1 : 0;
195 /* Return a list of shelves sorted by their mtime, oldest first.
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)
203 apr_hash_t *shelf_infos;
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,
215 stats(svn_client__shelf_t *shelf,
217 svn_client__shelf_version_t *shelf_version,
219 svn_boolean_t with_logmsg,
220 apr_pool_t *scratch_pool)
225 const char *paths_str = "";
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);
237 version_str = apr_psprintf(scratch_pool,
238 Q_("version %d of %d", "version %d of %d",
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));
255 SVN_ERR(svn_client__shelf_get_log_message(&log_message, shelf,
259 SVN_ERR(svn_cmdline_printf(scratch_pool,
268 /* Display a list of shelves */
270 shelves_list(const char *local_abspath,
272 svn_client_ctx_t *ctx,
273 apr_pool_t *scratch_pool)
275 apr_time_t time_now = apr_time_now();
276 apr_array_header_t *list;
279 SVN_ERR(list_sorted_by_date(&list,
280 local_abspath, ctx, scratch_pool));
282 for (i = 0; i < list->nelts; i++)
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;
289 SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
291 SVN_ERR(svn_client__shelf_get_newest_version(&shelf_version, shelf,
292 scratch_pool, scratch_pool));
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",
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));
307 /* Print info about each checkpoint of the shelf named NAME.
310 shelf_log(const char *name,
311 const char *local_abspath,
312 svn_client_ctx_t *ctx,
313 apr_pool_t *scratch_pool)
315 apr_time_t time_now = apr_time_now();
316 svn_client__shelf_t *shelf;
317 apr_array_header_t *versions;
320 SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
322 SVN_ERR(svn_client__shelf_get_all_versions(&versions, shelf,
323 scratch_pool, scratch_pool));
324 for (i = 0; i < versions->nelts; i++)
326 svn_client__shelf_version_t *shelf_version
327 = APR_ARRAY_IDX(versions, i, void *);
329 SVN_ERR(stats(shelf, i + 1, shelf_version, time_now,
330 FALSE /*with_logmsg*/, scratch_pool));
333 SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
337 /* Find the name of the youngest shelf.
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)
346 apr_array_header_t *list;
347 const svn_sort__item_t *youngest_item;
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"));
355 youngest_item = &APR_ARRAY_IDX(list, list->nelts - 1, svn_sort__item_t);
356 *name_p = apr_pstrdup(result_pool, youngest_item->key);
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;
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;
373 /* A status callback function for printing STATUS for PATH. */
375 print_status(void *baton,
377 const svn_client_status_t *status,
378 apr_pool_t *scratch_pool)
380 struct status_baton *sb = baton;
381 unsigned int conflicts;
383 return svn_cl__print_status(sb->target_abspath, sb->target_path,
385 TRUE /*suppress_externals_placeholders*/,
387 FALSE /*show_last_committed*/,
388 TRUE /*skip_unrecognized*/,
389 FALSE /*repos_locks*/,
390 &conflicts, &conflicts, &conflicts,
395 /* A callback function for shelved paths. */
397 was_shelved(void *baton,
399 const svn_client_status_t *status,
400 apr_pool_t *scratch_pool)
402 struct status_baton *sb = baton;
406 SVN_ERR(print_status(baton, path, status, scratch_pool));
409 ++sb->num_paths_shelved;
413 /* A callback function for not-shelved paths. */
415 was_not_shelved(void *baton,
417 const svn_client_status_t *status,
418 apr_pool_t *scratch_pool)
420 struct status_baton *sb = baton;
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;
428 /** Shelve/save a new version of changes.
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.
434 * If no local modifications are found, throw an error.
436 * If @a dry_run is true, don't actually do it.
438 * Report in @a *new_version_p the new version number (or, with dry run,
442 shelve(int *new_version_p,
444 const apr_array_header_t *paths,
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,
451 const char *local_abspath,
452 svn_client_ctx_t *ctx,
453 apr_pool_t *scratch_pool)
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;
460 SVN_ERR(svn_client__shelf_open_or_create(&shelf,
463 SVN_ERR(svn_client__shelf_get_newest_version(&previous_version, shelf,
464 scratch_pool, scratch_pool));
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));
476 sb.target_abspath = shelf->wc_root_abspath;
479 sb.num_paths_shelved = 0;
480 sb.num_paths_not_shelved = 0;
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,
490 was_not_shelved, &sb,
492 if (sb.num_paths_not_shelved > 0)
494 SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, previous_version,
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);
503 if (sb.num_paths_shelved == 0
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"));
512 /* Un-apply the changes, if required. */
515 SVN_ERR(svn_client__shelf_unapply(new_version,
516 dry_run, scratch_pool));
519 /* Fetch the log message and any other revprops */
520 if (ctx->log_msg_func3)
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 = "";
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. */
533 if (message && !dry_run)
535 svn_string_t *propval = svn_string_create(message, scratch_pool);
538 revprop_table = apr_hash_make(scratch_pool);
539 svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG, propval);
543 SVN_ERR(svn_client__shelf_revprop_set_all(shelf, revprop_table, scratch_pool));
546 *new_version_p = shelf->max_version;
550 SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, previous_version,
554 SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
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().) */
562 status_to_char(enum svn_wc_status_kind status)
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 ':';
584 /* Throw an error if any path affected by SHELF_VERSION gives a conflict
585 * when applied (as a dry-run) to the WC. */
587 test_apply(svn_client__shelf_version_t *shelf_version,
588 svn_client_ctx_t *ctx,
589 apr_pool_t *scratch_pool)
592 apr_hash_index_t *hi;
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))
598 const char *path = apr_hash_this_key(hi);
599 svn_boolean_t conflict;
601 SVN_ERR(svn_client__shelf_test_apply_file(&conflict, shelf_version, path,
606 = svn_dirent_join(shelf_version->shelf->wc_root_abspath, path,
608 svn_wc_status3_t *status;
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));
621 /** Restore/unshelve a given or newest version of changes.
623 * Restore local modifications from shelf @a name version @a arg,
624 * or the newest version is @a arg is null.
626 * If @a dry_run is true, don't actually do it.
628 * Error if any path would have a conflict, unless @a force_if_conflict.
631 shelf_restore(const char *name,
633 svn_boolean_t dry_run,
635 svn_boolean_t force_if_conflict,
636 const char *local_abspath,
637 svn_client_ctx_t *ctx,
638 apr_pool_t *scratch_pool)
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;
645 SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
648 old_version = shelf->max_version;
651 SVN_ERR(svn_cstring_atoi(&version, arg));
652 SVN_ERR(svn_client__shelf_version_open(&shelf_version,
654 scratch_pool, scratch_pool));
658 version = shelf->max_version;
659 SVN_ERR(get_newest_version_existing(&shelf_version, shelf,
660 scratch_pool, scratch_pool));
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));
671 if (! force_if_conflict)
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"));
679 SVN_ERR(svn_client__shelf_apply(shelf_version,
680 dry_run, scratch_pool));
684 SVN_ERR(svn_client__shelf_delete_newer_versions(shelf, shelf_version,
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));
697 SVN_ERR(svn_cmdline_printf(scratch_pool,
698 _("restored '%s' version %d (the newest version)\n"),
702 SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
707 shelf_diff(const char *name,
709 const char *local_abspath,
710 svn_boolean_t summarize,
712 svn_boolean_t ignore_ancestry,
713 svn_client_ctx_t *ctx,
714 apr_pool_t *scratch_pool)
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;
721 SVN_ERR(svn_client__shelf_open_existing(&shelf, name, local_abspath,
728 SVN_ERR(svn_cstring_atoi(&version, arg));
729 SVN_ERR(svn_client__shelf_version_open(&shelf_version,
731 scratch_pool, scratch_pool));
735 SVN_ERR(get_newest_version_existing(&shelf_version, shelf,
736 scratch_pool, scratch_pool));
739 SVN_ERR(svn_stream_for_stdout(&stream, scratch_pool));
740 errstream = svn_stream_empty(scratch_pool);
744 svn_client_diff_summarize_func_t func;
747 SVN_ERR(svn_cl__get_diff_summary_writer(&func, &baton,
749 FALSE /*ignore_properties*/,
750 "" /*anchor/prefix*/,
751 scratch_pool, scratch_pool));
752 SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor,
759 SVN_ERR(svn_client__get_diff_writer_svn(
762 "", "", /*orig_path_1, orig_path_2,*/
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),
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));
782 SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
786 /* This implements the `svn_opt_subcommand_t' interface. */
788 shelf_drop(const char *name,
789 const char *local_abspath,
790 svn_boolean_t dry_run,
792 svn_client_ctx_t *ctx,
793 apr_pool_t *scratch_pool)
795 SVN_ERR(svn_client__shelf_delete(name, local_abspath, dry_run,
798 SVN_ERR(svn_cmdline_printf(scratch_pool,
806 shelf_shelve(int *new_version,
808 apr_array_header_t *targets,
810 apr_array_header_t *changelists,
811 apr_hash_t *revprop_table,
812 svn_boolean_t keep_local,
813 svn_boolean_t dry_run,
815 svn_client_ctx_t *ctx,
816 apr_pool_t *scratch_pool)
818 const char *local_abspath;
820 if (depth == svn_depth_unknown)
821 depth = svn_depth_infinity;
823 SVN_ERR(svn_cl__check_targets_are_local_paths(targets));
825 SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool));
827 svn_opt_push_implicit_dot_target(targets, scratch_pool);
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 *),
834 SVN_ERR(shelve(new_version, name,
835 targets, depth, changelists,
837 keep_local, dry_run, quiet,
838 local_abspath, ctx, scratch_pool));
844 svn_cl__shelf_shelve(apr_getopt_t *os,
848 /* This implements the `svn_opt_subcommand_t' interface. */
850 svn_cl__shelf_save(apr_getopt_t *os,
854 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
856 opt_state->keep_local = TRUE;
857 SVN_ERR(svn_cl__shelf_shelve(os, baton, pool));
861 /* This implements the `svn_opt_subcommand_t' interface. */
863 svn_cl__shelf_shelve(apr_getopt_t *os,
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;
870 apr_array_header_t *targets;
872 if (opt_state->quiet)
873 ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */
875 SVN_ERR(get_next_argument(&name, os, pool, pool));
877 /* Parse the remaining arguments as paths. */
878 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
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,
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,
900 if (! opt_state->quiet)
902 if (opt_state->keep_local)
903 SVN_ERR(svn_cmdline_printf(pool,
904 _("saved '%s' version %d\n"),
907 SVN_ERR(svn_cmdline_printf(pool,
908 _("shelved '%s' version %d\n"),
916 /* This implements the `svn_opt_subcommand_t' interface. */
918 svn_cl__shelf_unshelve(apr_getopt_t *os,
920 apr_pool_t *scratch_pool)
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;
926 const char *arg = NULL;
928 SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", scratch_pool));
930 if (os->ind < os->argc)
932 SVN_ERR(get_next_argument(&name, os, scratch_pool, scratch_pool));
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"),
943 /* Which checkpoint number? */
944 if (os->ind < os->argc)
945 SVN_ERR(get_next_argument(&arg, os, scratch_pool, scratch_pool));
947 if (os->ind < os->argc)
948 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
949 _("Too many arguments"));
951 if (opt_state->quiet)
952 ctx->notify_func2 = NULL; /* Easy out: avoid unneeded work */
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));
961 SVN_ERR(shelf_drop(name, local_abspath,
962 opt_state->dry_run, opt_state->quiet,
968 /* This implements the `svn_opt_subcommand_t' interface. */
970 svn_cl__shelf_list(apr_getopt_t *os,
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);
980 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
983 /* Add "." if user passed 0 arguments */
984 svn_opt_push_implicit_dot_target(targets, pool);
986 for (i = 0; i < targets->nelts; ++i)
988 const char *local_abspath;
989 const char *target = APR_ARRAY_IDX(targets, i, const char *);
991 svn_pool_clear(iterpool);
993 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
995 SVN_ERR(shelves_list(local_abspath,
1000 svn_pool_destroy(iterpool);
1002 return SVN_NO_ERROR;
1005 /* "svn shelf-list-by-paths [PATH...]"
1007 * TARGET_RELPATHS are all within the same WC, relative to WC_ROOT_ABSPATH.
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)
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;
1020 SVN_ERR(list_sorted_by_date(&shelves,
1021 wc_root_abspath, ctx, scratch_pool));
1023 /* Check paths are valid */
1024 for (i = 0; i < target_relpaths->nelts; i++)
1026 char *target_relpath = APR_ARRAY_IDX(target_relpaths, i, char *);
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));
1034 /* Find the most recent shelf for each affected path */
1035 for (i = 0; i < shelves->nelts; i++)
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;
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));
1051 SVN_ERR(svn_client__shelf_paths_changed(&shelf_paths,
1053 scratch_pool, scratch_pool));
1054 for (j = 0; j < target_relpaths->nelts; j++)
1056 char *target_relpath = APR_ARRAY_IDX(target_relpaths, j, char *);
1057 apr_hash_index_t *hi;
1059 for (hi = apr_hash_first(scratch_pool, shelf_paths);
1060 hi; hi = apr_hash_next(hi))
1062 const char *shelf_path = apr_hash_this_key(hi);
1064 if (svn_relpath_skip_ancestor(target_relpath, shelf_path))
1066 if (! svn_hash_gets(paths_to_shelf_name, shelf_path))
1068 svn_hash_sets(paths_to_shelf_name, shelf_path, shelf->name);
1075 /* Print the results. */
1076 array = svn_sort__hash(paths_to_shelf_name,
1077 svn_sort_compare_items_as_paths,
1079 for (i = 0; i < array->nelts; i++)
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;
1085 SVN_ERR(svn_cmdline_printf(scratch_pool, "%-20.20s %s\n",
1087 svn_dirent_local_style(path, scratch_pool)));
1089 return SVN_NO_ERROR;
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,
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;
1103 /* Parse the remaining arguments as paths. */
1104 SVN_ERR(targets_relative_to_a_wc(&wc_root_abspath, &targets,
1105 os, opt_state->targets,
1108 SVN_ERR(shelf_list_by_paths(targets, wc_root_abspath, ctx, pool));
1109 return SVN_NO_ERROR;
1112 /* This implements the `svn_opt_subcommand_t' interface. */
1113 static svn_error_t *
1114 svn_cl__shelf_diff(apr_getopt_t *os,
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;
1122 const char *arg = NULL;
1124 SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", pool));
1126 SVN_ERR(get_next_argument(&name, os, pool, pool));
1128 /* Which checkpoint number? */
1129 if (os->ind < os->argc)
1130 SVN_ERR(get_next_argument(&arg, os, pool, pool));
1132 if (os->ind < os->argc)
1133 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1134 _("Too many arguments"));
1136 SVN_ERR(shelf_diff(name, arg, local_abspath,
1137 opt_state->diff.summarize,
1138 opt_state->depth, opt_state->ignore_ancestry,
1141 return SVN_NO_ERROR;
1144 /* This implements the `svn_opt_subcommand_t' interface. */
1145 static svn_error_t *
1146 svn_cl__shelf_drop(apr_getopt_t *os,
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;
1153 apr_array_header_t *targets = NULL;
1154 apr_pool_t *iterpool = svn_pool_create(pool);
1157 SVN_ERR(get_next_argument(&name, os, pool, pool));
1159 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
1162 svn_opt_push_implicit_dot_target(targets, pool);
1164 for (i = 0; i < targets->nelts; ++i)
1166 const char *local_abspath;
1167 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1169 svn_pool_clear(iterpool);
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,
1177 svn_pool_destroy(iterpool);
1179 return SVN_NO_ERROR;
1182 /* This implements the `svn_opt_subcommand_t' interface. */
1183 static svn_error_t *
1184 svn_cl__shelf_log(apr_getopt_t *os,
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;
1191 apr_array_header_t *targets = NULL;
1192 apr_pool_t *iterpool = svn_pool_create(pool);
1195 SVN_ERR(get_next_argument(&name, os, pool, pool));
1197 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
1200 svn_opt_push_implicit_dot_target(targets, pool);
1202 for (i = 0; i < targets->nelts; ++i)
1204 const char *local_abspath;
1205 const char *target = APR_ARRAY_IDX(targets, i, const char *);
1207 svn_pool_clear(iterpool);
1209 SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, iterpool));
1210 SVN_ERR(shelf_log(name, local_abspath, ctx, iterpool));
1213 svn_pool_destroy(iterpool);
1215 return SVN_NO_ERROR;
1218 /**************************************************************************/
1220 /* This implements the `svn_opt_subcommand_t' interface. */
1221 static svn_error_t *
1222 svn_cl__wc_copy_mods(apr_getopt_t *os,
1226 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
1227 const char *src_wc_abspath, *dst_wc_abspath;
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));
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));
1235 SVN_ERR(svn_client__wc_copy_mods(src_wc_abspath, dst_wc_abspath,
1236 ctx->notify_func2, ctx->notify_baton2,
1239 return SVN_NO_ERROR;
1242 const svn_opt_subcommand_desc3_t
1243 svn_cl__cmd_table_shelf3[] =
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"
1249 " Show the changes in SHELF:VERSION (default: latest) as a diff.\n"
1251 " See also: 'svn diff --cl=svn:shelf:SHELF' which supports most options of\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"
1260 { "x-shelf-drop", svn_cl__shelf_drop, {0}, {N_(
1262 "usage: x-shelf-drop SHELF [PATH ...]\n"
1264 " Delete the shelves named SHELF from the working copies containing PATH\n"
1265 " (default PATH is '.')\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"
1272 { "x-shelf-list", svn_cl__shelf_list, {"x-shelves"}, {N_(
1274 "usage: x-shelf-list [PATH ...]\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"
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"
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"
1290 " List which shelf most recently affects each path below the given PATHs.\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"
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"
1301 " Show all versions of SHELF for each working copy containing PATH (the\n"
1302 " default PATH is '.').\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"
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"
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"
1317 " The same as 'svn shelve --keep-local'.\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"
1323 opt_depth, opt_targets, opt_changelist,
1324 SVN_CL__LOG_MSG_OPTIONS,
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"
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"
1336 " 'svn shelve --keep-local' is the same as 'svn shelf-save'.\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"
1345 " To bring back shelved changes, use 'svn unshelve SHELF'.\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"
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"
1356 {'q', opt_dry_run, opt_keep_local,
1357 opt_depth, opt_targets, opt_changelist,
1358 SVN_CL__LOG_MSG_OPTIONS,
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"
1365 " Apply the changes stored in SHELF to the working copy.\n"
1366 " SHELF defaults to the newest shelf.\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"
1373 " With --drop, delete the entire shelf (like 'svn shelf-drop') after\n"
1374 " successfully unshelving with no conflicts.\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"
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"
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"
1389 {opt_drop, 'q', opt_dry_run, opt_force} },
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"
1395 " The source and destination WC paths may be in the same WC or in different"
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"
1403 { NULL, NULL, {0}, {NULL}, {0} }