2 * shelf.c: implementation of shelving
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 /* ==================================================================== */
26 /* We define this here to remove any further warnings about the usage of
27 experimental functions in this file. */
28 #define SVN_EXPERIMENTAL
30 #include "svn_client.h"
32 #include "svn_pools.h"
33 #include "svn_dirent_uri.h"
37 #include "svn_ctype.h"
38 #include "svn_props.h"
41 #include "private/svn_client_shelf.h"
42 #include "private/svn_client_private.h"
43 #include "private/svn_wc_private.h"
44 #include "private/svn_sorts_private.h"
45 #include "svn_private_config.h"
49 shelf_name_encode(char **encoded_name_p,
51 apr_pool_t *result_pool)
54 = apr_palloc(result_pool, strlen(name) * 2 + 1);
55 char *out_pos = encoded_name;
58 return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
59 _("Shelf name cannot be the empty string"));
63 apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++));
66 *encoded_name_p = encoded_name;
71 shelf_name_decode(char **decoded_name_p,
73 apr_pool_t *result_pool)
76 = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool);
77 const char *input = codename;
83 int nitems = sscanf(input, "%02x%n", &c, &nchars);
85 if (nitems != 1 || nchars != 2)
86 return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
87 _("Shelve: Bad encoded name '%s'"), codename);
88 svn_stringbuf_appendbyte(sb, c);
91 *decoded_name_p = sb->data;
95 /* Set *NAME to the shelf name from FILENAME, if FILENAME names a '.current'
96 * file, else to NULL. */
98 shelf_name_from_filename(char **name,
100 apr_pool_t *result_pool)
102 size_t len = strlen(filename);
103 static const char suffix[] = ".current";
104 size_t suffix_len = sizeof(suffix) - 1;
106 if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0)
108 char *codename = apr_pstrndup(result_pool, filename, len - suffix_len);
109 SVN_ERR(shelf_name_decode(name, codename, result_pool));
118 /* Set *DIR to the shelf storage directory inside the WC's administrative
119 * area. Ensure the directory exists. */
121 get_shelves_dir(char **dir,
122 svn_wc_context_t *wc_ctx,
123 const char *local_abspath,
124 apr_pool_t *result_pool,
125 apr_pool_t *scratch_pool)
127 char *experimental_abspath;
129 SVN_ERR(svn_wc__get_experimental_dir(&experimental_abspath,
130 wc_ctx, local_abspath,
131 scratch_pool, scratch_pool));
132 *dir = svn_dirent_join(experimental_abspath, "shelves/v3", result_pool);
134 /* Ensure the directory exists. (Other versions of svn don't create it.) */
135 SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool));
140 /* Set *ABSPATH to the abspath of the file storage dir for SHELF
141 * version VERSION, no matter whether it exists.
144 shelf_version_files_dir_abspath(const char **abspath,
145 svn_client__shelf_t *shelf,
147 apr_pool_t *result_pool,
148 apr_pool_t *scratch_pool)
153 SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
154 filename = apr_psprintf(scratch_pool, "%s-%03d.wc", codename, version);
155 *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
159 /* Create a shelf-version object for a version that may or may not already
163 shelf_version_create(svn_client__shelf_version_t **new_version_p,
164 svn_client__shelf_t *shelf,
166 apr_pool_t *result_pool)
168 svn_client__shelf_version_t *shelf_version
169 = apr_pcalloc(result_pool, sizeof(*shelf_version));
171 shelf_version->shelf = shelf;
172 shelf_version->version_number = version_number;
173 SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath,
174 shelf, version_number,
175 result_pool, result_pool));
176 *new_version_p = shelf_version;
180 /* Delete the storage for SHELF:VERSION. */
182 shelf_version_delete(svn_client__shelf_t *shelf,
184 apr_pool_t *scratch_pool)
186 const char *files_dir_abspath;
188 SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath,
190 scratch_pool, scratch_pool));
191 SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/,
192 NULL, NULL, /*cancel*/
199 get_log_abspath(char **log_abspath,
200 svn_client__shelf_t *shelf,
201 apr_pool_t *result_pool,
202 apr_pool_t *scratch_pool)
205 const char *filename;
207 SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
208 filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL);
209 *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
213 /* Set SHELF->revprops by reading from its storage (the '.log' file).
214 * Set SHELF->revprops to empty if the storage file does not exist; this
218 shelf_read_revprops(svn_client__shelf_t *shelf,
219 apr_pool_t *scratch_pool)
223 svn_stream_t *stream;
225 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
227 shelf->revprops = apr_hash_make(shelf->pool);
228 err = svn_stream_open_readonly(&stream, log_abspath,
229 scratch_pool, scratch_pool);
230 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
232 svn_error_clear(err);
237 SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool));
238 SVN_ERR(svn_stream_close(stream));
242 /* Write SHELF's revprops to its file storage.
245 shelf_write_revprops(svn_client__shelf_t *shelf,
246 apr_pool_t *scratch_pool)
250 svn_stream_t *stream;
252 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
254 SVN_ERR(svn_io_file_open(&file, log_abspath,
255 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE,
256 APR_FPROT_OS_DEFAULT, scratch_pool));
257 stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
259 SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool));
260 SVN_ERR(svn_stream_close(stream));
265 svn_client__shelf_revprop_set(svn_client__shelf_t *shelf,
266 const char *prop_name,
267 const svn_string_t *prop_val,
268 apr_pool_t *scratch_pool)
270 svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name),
271 svn_string_dup(prop_val, shelf->pool));
272 SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
277 svn_client__shelf_revprop_set_all(svn_client__shelf_t *shelf,
278 apr_hash_t *revprop_table,
279 apr_pool_t *scratch_pool)
282 shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool);
284 shelf->revprops = apr_hash_make(shelf->pool);
286 SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
291 svn_client__shelf_revprop_get(svn_string_t **prop_val,
292 svn_client__shelf_t *shelf,
293 const char *prop_name,
294 apr_pool_t *result_pool)
296 *prop_val = svn_hash_gets(shelf->revprops, prop_name);
301 svn_client__shelf_revprop_list(apr_hash_t **props,
302 svn_client__shelf_t *shelf,
303 apr_pool_t *result_pool)
305 *props = shelf->revprops;
311 get_current_abspath(char **current_abspath,
312 svn_client__shelf_t *shelf,
313 apr_pool_t *result_pool)
318 SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
319 filename = apr_psprintf(result_pool, "%s.current", codename);
320 *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
324 /* Read SHELF->max_version from its storage (the '.current' file).
325 * Set SHELF->max_version to -1 if that file does not exist.
328 shelf_read_current(svn_client__shelf_t *shelf,
329 apr_pool_t *scratch_pool)
331 char *current_abspath;
334 SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool));
335 err = svn_io_read_version_file(&shelf->max_version,
336 current_abspath, scratch_pool);
339 shelf->max_version = -1;
340 svn_error_clear(err);
348 shelf_write_current(svn_client__shelf_t *shelf,
349 apr_pool_t *scratch_pool)
351 char *current_abspath;
353 SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool));
354 SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version,
359 /*-------------------------------------------------------------------------*/
360 /* Status Reporting */
362 /* Adjust a status STATUS_IN obtained from the shelf storage WC, to add
363 * shelf-related metadata:
364 * - changelist: 'svn:shelf:SHELFNAME'
367 status_augment(svn_wc_status3_t **status_p,
368 const svn_wc_status3_t *status_in,
369 svn_client__shelf_version_t *shelf_version,
370 apr_pool_t *result_pool)
372 *status_p = svn_wc_dup_status3(status_in, result_pool);
373 (*status_p)->changelist = apr_psprintf(result_pool, "svn:shelf:%s",
374 shelf_version->shelf->name);
378 /* Read status from shelf storage.
381 status_read(svn_wc_status3_t **status,
382 svn_client__shelf_version_t *shelf_version,
384 apr_pool_t *result_pool,
385 apr_pool_t *scratch_pool)
387 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
389 = svn_dirent_join(shelf_version->files_dir_abspath, relpath,
392 SVN_ERR(svn_wc_status3(status, ctx->wc_ctx, abspath,
393 result_pool, scratch_pool));
394 SVN_ERR(status_augment(status, *status, shelf_version, result_pool));
398 /* A visitor function type for use with shelf_status_walk().
399 * The same as svn_wc_status_func4_t except relpath instead of abspath.
401 typedef svn_error_t *(*shelf_status_visitor_t)(void *baton,
403 const svn_wc_status3_t *status,
404 apr_pool_t *scratch_pool);
406 /* Baton for shelved_files_walk_visitor(). */
407 struct shelf_status_baton_t
409 svn_client__shelf_version_t *shelf_version;
410 shelf_status_visitor_t walk_func;
414 /* Convert a svn_wc_status_func4_t callback invocation to call a
415 * shelf_status_visitor_t callback.
417 * Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved
418 * storage path ABSPATH, converting ABSPATH to a WC-relative path, and
419 * augmenting the STATUS.
421 * The opposite of wc_status_visitor().
423 * Implements svn_wc_status_func4_t. */
425 shelf_status_visitor(void *baton,
427 const svn_wc_status3_t *status,
428 apr_pool_t *scratch_pool)
430 struct shelf_status_baton_t *b = baton;
432 svn_wc_status3_t *new_status;
434 relpath = svn_dirent_skip_ancestor(b->shelf_version->files_dir_abspath,
436 SVN_ERR(status_augment(&new_status, status, b->shelf_version, scratch_pool));
437 SVN_ERR(b->walk_func(b->walk_baton, relpath, new_status, scratch_pool));
441 /* Report the shelved status of the path SHELF_VERSION:WC_RELPATH
442 * via WALK_FUNC(WALK_BATON, ...).
445 shelf_status_visit_path(svn_client__shelf_version_t *shelf_version,
446 const char *wc_relpath,
447 shelf_status_visitor_t walk_func,
449 apr_pool_t *scratch_pool)
451 svn_wc_status3_t *status;
453 SVN_ERR(status_read(&status, shelf_version, wc_relpath,
454 scratch_pool, scratch_pool));
455 SVN_ERR(walk_func(walk_baton, wc_relpath, status, scratch_pool));
459 /* Report the shelved status of all the shelved paths in SHELF_VERSION
460 * at and under WC_RELPATH, via WALK_FUNC(WALK_BATON, ...).
463 shelf_status_walk(svn_client__shelf_version_t *shelf_version,
464 const char *wc_relpath,
465 shelf_status_visitor_t walk_func,
467 apr_pool_t *scratch_pool)
469 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
470 char *walk_root_abspath
471 = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
473 struct shelf_status_baton_t baton;
476 baton.shelf_version = shelf_version;
477 baton.walk_func = walk_func;
478 baton.walk_baton = walk_baton;
479 err = svn_wc_walk_status(ctx->wc_ctx, walk_root_abspath,
483 FALSE /*ignore_text_mods*/,
484 NULL /*ignore_patterns: use the defaults*/,
485 shelf_status_visitor, &baton,
486 NULL, NULL, /*cancellation*/
488 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
489 svn_error_clear(err);
496 /* Baton for wc_status_visitor(). */
497 typedef struct wc_status_baton_t
499 svn_client__shelf_version_t *shelf_version;
500 svn_wc_status_func4_t walk_func;
504 /* Convert a shelf_status_visitor_t callback invocation to call a
505 * svn_wc_status_func4_t callback.
507 * Call BATON->walk_func(BATON->walk_baton, abspath, ...) for the WC-
508 * relative path RELPATH, converting RELPATH to an abspath in the user's WC.
510 * The opposite of shelf_status_visitor().
512 * Implements shelf_status_visitor_t. */
514 wc_status_visitor(void *baton,
516 const svn_wc_status3_t *status,
517 apr_pool_t *scratch_pool)
519 struct wc_status_baton_t *b = baton;
520 svn_client__shelf_t *shelf = b->shelf_version->shelf;
521 const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath,
523 SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool));
528 svn_client__shelf_version_status_walk(svn_client__shelf_version_t *shelf_version,
529 const char *wc_relpath,
530 svn_wc_status_func4_t walk_func,
532 apr_pool_t *scratch_pool)
534 wc_status_baton_t baton;
536 baton.shelf_version = shelf_version;
537 baton.walk_func = walk_func;
538 baton.walk_baton = walk_baton;
539 SVN_ERR(shelf_status_walk(shelf_version, wc_relpath,
540 wc_status_visitor, &baton,
545 /*-------------------------------------------------------------------------*/
548 /* Construct a shelf object representing an empty shelf: no versions,
549 * no revprops, no looking to see if such a shelf exists on disk.
552 shelf_construct(svn_client__shelf_t **shelf_p,
554 const char *local_abspath,
555 svn_client_ctx_t *ctx,
556 apr_pool_t *result_pool)
558 svn_client__shelf_t *shelf = apr_palloc(result_pool, sizeof(*shelf));
561 SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath,
563 result_pool, result_pool));
564 SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
565 result_pool, result_pool));
566 shelf->shelves_dir = shelves_dir;
568 shelf->pool = result_pool;
570 shelf->name = apr_pstrdup(result_pool, name);
571 shelf->revprops = apr_hash_make(result_pool);
572 shelf->max_version = 0;
579 svn_client__shelf_open_existing(svn_client__shelf_t **shelf_p,
581 const char *local_abspath,
582 svn_client_ctx_t *ctx,
583 apr_pool_t *result_pool)
585 SVN_ERR(shelf_construct(shelf_p, name,
586 local_abspath, ctx, result_pool));
587 SVN_ERR(shelf_read_revprops(*shelf_p, result_pool));
588 SVN_ERR(shelf_read_current(*shelf_p, result_pool));
589 if ((*shelf_p)->max_version < 0)
591 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
592 _("Shelf '%s' not found"),
599 svn_client__shelf_open_or_create(svn_client__shelf_t **shelf_p,
601 const char *local_abspath,
602 svn_client_ctx_t *ctx,
603 apr_pool_t *result_pool)
605 svn_client__shelf_t *shelf;
607 SVN_ERR(shelf_construct(&shelf, name,
608 local_abspath, ctx, result_pool));
609 SVN_ERR(shelf_read_revprops(shelf, result_pool));
610 SVN_ERR(shelf_read_current(shelf, result_pool));
611 if (shelf->max_version < 0)
613 shelf->max_version = 0;
614 SVN_ERR(shelf_write_current(shelf, result_pool));
621 svn_client__shelf_close(svn_client__shelf_t *shelf,
622 apr_pool_t *scratch_pool)
628 svn_client__shelf_delete(const char *name,
629 const char *local_abspath,
630 svn_boolean_t dry_run,
631 svn_client_ctx_t *ctx,
632 apr_pool_t *scratch_pool)
634 svn_client__shelf_t *shelf;
638 SVN_ERR(svn_client__shelf_open_existing(&shelf, name,
639 local_abspath, ctx, scratch_pool));
641 /* Remove the versions. */
642 for (i = shelf->max_version; i > 0; i--)
644 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
647 /* Remove the other files */
648 SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool));
649 SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
650 SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool));
651 SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
653 SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
657 /* Baton for paths_changed_visitor(). */
658 struct paths_changed_walk_baton_t
660 apr_hash_t *paths_hash;
661 const char *wc_root_abspath;
665 /* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file.
666 * Implements shelved_files_walk_func_t. */
668 paths_changed_visitor(void *baton,
670 const svn_wc_status3_t *s,
671 apr_pool_t *scratch_pool)
673 struct paths_changed_walk_baton_t *b = baton;
675 relpath = apr_pstrdup(b->pool, relpath);
676 svn_hash_sets(b->paths_hash, relpath, relpath);
680 /* Get the paths changed, relative to WC root or as abspaths, as a hash
681 * and/or an array (in no particular order).
684 shelf_paths_changed(apr_hash_t **paths_hash_p,
685 apr_array_header_t **paths_array_p,
686 svn_client__shelf_version_t *shelf_version,
687 apr_pool_t *result_pool,
688 apr_pool_t *scratch_pool)
690 svn_client__shelf_t *shelf = shelf_version->shelf;
691 apr_hash_t *paths_hash = apr_hash_make(result_pool);
692 struct paths_changed_walk_baton_t baton;
694 baton.paths_hash = paths_hash;
695 baton.wc_root_abspath = shelf->wc_root_abspath;
696 baton.pool = result_pool;
697 SVN_ERR(shelf_status_walk(shelf_version, "",
698 paths_changed_visitor, &baton,
702 *paths_hash_p = paths_hash;
704 SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool));
710 svn_client__shelf_paths_changed(apr_hash_t **affected_paths,
711 svn_client__shelf_version_t *shelf_version,
712 apr_pool_t *result_pool,
713 apr_pool_t *scratch_pool)
715 SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version,
716 result_pool, scratch_pool));
721 svn_client__shelf_replay(svn_client__shelf_version_t *shelf_version,
722 const char *top_relpath,
723 const svn_delta_editor_t *editor,
725 svn_wc_notify_func2_t notify_func,
727 apr_pool_t *scratch_pool)
729 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
730 apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1,
732 const char *src_wc_abspath
733 = svn_dirent_join(shelf_version->files_dir_abspath, top_relpath, scratch_pool);
735 APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath;
736 SVN_ERR(svn_client__wc_replay(src_wc_abspath,
737 src_targets, svn_depth_infinity, NULL,
739 notify_func, notify_baton,
744 /* Baton for test_apply_file_visitor(). */
745 struct test_apply_files_baton_t
747 svn_client__shelf_version_t *shelf_version;
748 svn_boolean_t conflict; /* would it conflict? */
749 svn_client_ctx_t *ctx;
752 /* Ideally, set BATON->conflict if we can't apply a change to WC
753 * at RELPATH without conflict. But in fact, just check
754 * if WC at RELPATH is locally modified.
756 * Implements shelved_files_walk_func_t. */
758 test_apply_file_visitor(void *baton,
760 const svn_wc_status3_t *s,
761 apr_pool_t *scratch_pool)
763 struct test_apply_files_baton_t *b = baton;
764 const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath;
765 const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath,
767 svn_wc_status3_t *status;
769 SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath,
770 scratch_pool, scratch_pool));
771 switch (status->node_status)
773 case svn_wc_status_normal:
774 case svn_wc_status_none:
784 svn_client__shelf_test_apply_file(svn_boolean_t *conflict_p,
785 svn_client__shelf_version_t *shelf_version,
786 const char *file_relpath,
787 apr_pool_t *scratch_pool)
789 struct test_apply_files_baton_t baton = {0};
791 baton.shelf_version = shelf_version;
792 baton.conflict = FALSE;
793 baton.ctx = shelf_version->shelf->ctx;
794 SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath,
795 test_apply_file_visitor, &baton,
797 *conflict_p = baton.conflict;
803 wc_mods_editor(const svn_delta_editor_t **editor_p,
805 const char *dst_wc_abspath,
806 svn_wc_notify_func2_t notify_func,
808 svn_client_ctx_t *ctx,
809 apr_pool_t *result_pool,
810 apr_pool_t *scratch_pool)
812 svn_client__pathrev_t *base;
813 const char *dst_wc_url;
814 svn_ra_session_t *ra_session;
816 /* We'll need an RA session to obtain the base of any copies */
817 SVN_ERR(svn_client__wc_node_get_base(&base,
818 dst_wc_abspath, ctx->wc_ctx,
819 scratch_pool, scratch_pool));
820 dst_wc_url = base->url;
821 SVN_ERR(svn_client_open_ra_session2(&ra_session,
822 dst_wc_url, dst_wc_abspath,
823 ctx, result_pool, scratch_pool));
824 SVN_ERR(svn_client__wc_editor(editor_p, edit_baton_p,
826 notify_func, notify_baton,
827 ra_session, ctx, result_pool));
832 svn_client__shelf_mods_editor(const svn_delta_editor_t **editor_p,
834 svn_client__shelf_version_t *shelf_version,
835 svn_wc_notify_func2_t notify_func,
837 svn_client_ctx_t *ctx,
838 apr_pool_t *result_pool)
840 SVN_ERR(wc_mods_editor(editor_p, edit_baton_p,
841 shelf_version->files_dir_abspath,
842 notify_func, notify_baton,
843 ctx, result_pool, result_pool));
848 svn_client__shelf_apply(svn_client__shelf_version_t *shelf_version,
849 svn_boolean_t dry_run,
850 apr_pool_t *scratch_pool)
852 svn_client__shelf_t *shelf = shelf_version->shelf;
853 const svn_delta_editor_t *editor;
856 SVN_ERR(wc_mods_editor(&editor, &edit_baton,
857 shelf->wc_root_abspath,
858 NULL, NULL, /*notification*/
859 shelf->ctx, scratch_pool, scratch_pool));
861 SVN_ERR(svn_client__shelf_replay(shelf_version, "",
863 shelf->ctx->notify_func2, shelf->ctx->notify_baton2,
866 svn_io_sleep_for_timestamps(shelf->wc_root_abspath,
871 /* Baton for paths_changed_visitor(). */
872 struct unapply_walk_baton_t
874 const char *wc_root_abspath;
875 svn_boolean_t dry_run;
876 svn_boolean_t use_commit_times;
877 svn_client_ctx_t *ctx;
881 /* Revert the change at RELPATH in the user's WC.
882 * Implements shelved_files_walk_func_t. */
884 unapply_visitor(void *baton,
886 const svn_wc_status3_t *s,
887 apr_pool_t *scratch_pool)
889 struct unapply_walk_baton_t *b = baton;
890 const char *abspath = svn_dirent_join(b->wc_root_abspath, relpath,
895 apr_array_header_t *targets
896 = apr_array_make(scratch_pool, 1, sizeof(char *));
899 APR_ARRAY_PUSH(targets, const char *) = abspath;
901 /* If the local modification is a "delete" then revert it all
902 (recursively). Otherwise we'd have to walk paths in
903 top-down order to revert a delete, whereas we need bottom-up
904 order to revert children of an added directory. */
905 if (s->node_status == svn_wc_status_deleted
906 || s->node_status == svn_wc_status_replaced
907 || s->node_status == svn_wc_status_added)
908 depth = svn_depth_infinity;
910 depth = svn_depth_empty;
911 SVN_ERR(svn_wc_revert6(b->ctx->wc_ctx,
915 NULL /*changelists*/,
916 FALSE /*clear_changelists*/,
917 FALSE /*metadata_only*/,
918 FALSE /*added_keep_local*/,
919 b->ctx->cancel_func, b->ctx->cancel_baton,
920 NULL, NULL, /*notification*/
927 svn_client__shelf_unapply(svn_client__shelf_version_t *shelf_version,
928 svn_boolean_t dry_run,
929 apr_pool_t *scratch_pool)
931 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
932 svn_client__shelf_t *shelf = shelf_version->shelf;
933 struct unapply_walk_baton_t baton;
936 baton.wc_root_abspath = shelf->wc_root_abspath;
937 baton.dry_run = dry_run;
939 baton.pool = scratch_pool;
941 cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
943 SVN_ERR(svn_config_get_bool(cfg, &baton.use_commit_times,
944 SVN_CONFIG_SECTION_MISCELLANY,
945 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
947 SVN_WC__CALL_WITH_WRITE_LOCK(
948 shelf_status_walk(shelf_version, "",
949 unapply_visitor, &baton,
951 ctx->wc_ctx, shelf_version->shelf->wc_root_abspath,
952 FALSE /*lock_anchor*/, scratch_pool);
957 svn_client__shelf_delete_newer_versions(svn_client__shelf_t *shelf,
958 svn_client__shelf_version_t *shelf_version,
959 apr_pool_t *scratch_pool)
961 int previous_version = shelf_version ? shelf_version->version_number : 0;
964 /* Delete any newer checkpoints */
965 for (i = shelf->max_version; i > previous_version; i--)
967 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
970 shelf->max_version = previous_version;
971 SVN_ERR(shelf_write_current(shelf, scratch_pool));
976 svn_client__shelf_diff(svn_client__shelf_version_t *shelf_version,
977 const char *shelf_relpath,
979 svn_boolean_t ignore_ancestry,
980 const svn_diff_tree_processor_t *diff_processor,
981 apr_pool_t *scratch_pool)
983 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
985 = svn_dirent_join(shelf_version->files_dir_abspath, shelf_relpath,
988 if (shelf_version->version_number == 0)
991 SVN_ERR(svn_wc__diff7(FALSE /*anchor_at_given_paths*/,
992 ctx->wc_ctx, local_abspath,
995 NULL /*changelists*/,
997 NULL, NULL, /*cancellation*/
998 scratch_pool, scratch_pool));
1002 /* Populate the storage a new shelf-version object NEW_SHELF_VERSION,
1003 * by creating a shelf storage WC with its base state copied from the
1006 static svn_error_t *
1007 shelf_copy_base(svn_client__shelf_version_t *new_shelf_version,
1008 apr_pool_t *scratch_pool)
1010 svn_client_ctx_t *ctx = new_shelf_version->shelf->ctx;
1011 const char *users_wc_abspath = new_shelf_version->shelf->wc_root_abspath;
1012 svn_client__pathrev_t *users_wc_root_base;
1013 svn_opt_revision_t users_wc_root_rev;
1014 svn_ra_session_t *ra_session = NULL;
1015 svn_boolean_t sleep_here = FALSE;
1017 SVN_ERR(svn_client__wc_node_get_base(&users_wc_root_base,
1018 users_wc_abspath, ctx->wc_ctx,
1019 scratch_pool, scratch_pool));
1021 /* ### We need to read and recreate the mixed-rev, switched-URL,
1022 mixed-depth WC state; but for a rough start we'll just use
1023 HEAD, unswitched, depth-infinity. */
1024 users_wc_root_rev.kind = svn_opt_revision_head;
1026 /* ### TODO: Create an RA session that reads from the user's WC.
1027 For a rough start, we'll just let 'checkout' read from the repo. */
1029 SVN_ERR(svn_client__checkout_internal(NULL /*result_rev*/, &sleep_here,
1030 users_wc_root_base->url,
1031 new_shelf_version->files_dir_abspath,
1032 &users_wc_root_rev, &users_wc_root_rev,
1034 TRUE /*ignore_externals*/,
1035 FALSE /*allow_unver_obstructions*/,
1037 ctx, scratch_pool));
1038 /* ### hopefully we won't eventually need to sleep_here... */
1040 svn_io_sleep_for_timestamps(new_shelf_version->files_dir_abspath,
1042 return SVN_NO_ERROR;
1046 struct shelf_save_notifer_baton_t
1048 svn_client__shelf_version_t *shelf_version;
1049 svn_wc_notify_func2_t notify_func;
1051 svn_client_status_func_t shelved_func;
1052 void *shelved_baton;
1053 svn_boolean_t any_shelved;
1058 shelf_save_notifier(void *baton,
1059 const svn_wc_notify_t *notify,
1062 struct shelf_save_notifer_baton_t *nb = baton;
1063 const char *wc_relpath
1064 = svn_dirent_skip_ancestor(nb->shelf_version->shelf->wc_root_abspath,
1066 svn_client_status_t *cst = NULL;
1068 svn_wc_status3_t *wc_status;
1070 svn_error_clear(status_read(&wc_status, nb->shelf_version, wc_relpath,
1072 svn_error_clear(svn_client__create_status(
1073 &cst, nb->shelf_version->shelf->ctx->wc_ctx,
1074 notify->path, wc_status, pool, pool));
1076 svn_error_clear(nb->shelved_func(nb->shelved_baton, wc_relpath, cst, pool));
1077 nb->any_shelved = TRUE;
1079 nb->notify_func(nb->notify_baton, notify, pool);
1083 svn_client__shelf_save_new_version3(svn_client__shelf_version_t **new_version_p,
1084 svn_client__shelf_t *shelf,
1085 const apr_array_header_t *paths,
1087 const apr_array_header_t *changelists,
1088 svn_client_status_func_t shelved_func,
1089 void *shelved_baton,
1090 svn_client_status_func_t not_shelved_func,
1091 void *not_shelved_baton,
1092 apr_pool_t *scratch_pool)
1094 svn_client_ctx_t *ctx = shelf->ctx;
1095 int next_version = shelf->max_version + 1;
1096 svn_client__shelf_version_t *new_shelf_version;
1097 struct shelf_save_notifer_baton_t nb;
1098 const svn_delta_editor_t *editor;
1101 SVN_ERR(shelf_version_create(&new_shelf_version,
1102 shelf, next_version, scratch_pool));
1103 SVN_ERR(shelf_copy_base(new_shelf_version, scratch_pool));
1105 nb.shelf_version = new_shelf_version;
1106 nb.notify_func = ctx->notify_func2;
1107 nb.notify_baton = ctx->notify_baton2;
1108 nb.shelved_func = shelved_func;
1109 nb.shelved_baton = shelved_baton;
1110 nb.any_shelved = FALSE;
1111 SVN_ERR(svn_client__shelf_mods_editor(&editor, &edit_baton,
1113 NULL, NULL, /*notification*/
1114 ctx, scratch_pool));
1115 SVN_ERR(svn_client__wc_replay(shelf->wc_root_abspath,
1116 paths, depth, changelists,
1118 shelf_save_notifier, &nb,
1119 ctx, scratch_pool));
1123 shelf->max_version = next_version;
1124 SVN_ERR(shelf_write_current(shelf, scratch_pool));
1127 SVN_ERR(svn_client__shelf_version_open(new_version_p, shelf, next_version,
1128 scratch_pool, scratch_pool));
1133 *new_version_p = NULL;
1135 return SVN_NO_ERROR;
1139 svn_client__shelf_get_log_message(char **log_message,
1140 svn_client__shelf_t *shelf,
1141 apr_pool_t *result_pool)
1143 svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG);
1146 *log_message = apr_pstrdup(result_pool, propval->data);
1148 *log_message = NULL;
1149 return SVN_NO_ERROR;
1153 svn_client__shelf_set_log_message(svn_client__shelf_t *shelf,
1154 const char *message,
1155 apr_pool_t *scratch_pool)
1157 svn_string_t *propval
1158 = message ? svn_string_create(message, shelf->pool) : NULL;
1160 SVN_ERR(svn_client__shelf_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval,
1162 return SVN_NO_ERROR;
1166 svn_client__shelf_list(apr_hash_t **shelf_infos,
1167 const char *local_abspath,
1168 svn_client_ctx_t *ctx,
1169 apr_pool_t *result_pool,
1170 apr_pool_t *scratch_pool)
1172 const char *wc_root_abspath;
1174 apr_hash_t *dirents;
1175 apr_hash_index_t *hi;
1177 SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath,
1178 scratch_pool, scratch_pool));
1179 SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
1180 scratch_pool, scratch_pool));
1181 SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
1182 result_pool, scratch_pool));
1184 *shelf_infos = apr_hash_make(result_pool);
1186 /* Remove non-shelves */
1187 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1189 const char *filename = apr_hash_this_key(hi);
1190 svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
1193 svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
1194 if (name && dirent->kind == svn_node_file)
1196 svn_client__shelf_info_t *info
1197 = apr_palloc(result_pool, sizeof(*info));
1199 info->mtime = dirent->mtime;
1200 svn_hash_sets(*shelf_infos, name, info);
1204 return SVN_NO_ERROR;
1208 svn_client__shelf_version_open(svn_client__shelf_version_t **shelf_version_p,
1209 svn_client__shelf_t *shelf,
1211 apr_pool_t *result_pool,
1212 apr_pool_t *scratch_pool)
1214 svn_client__shelf_version_t *shelf_version;
1215 const svn_io_dirent2_t *dirent;
1217 SVN_ERR(shelf_version_create(&shelf_version,
1218 shelf, version_number, result_pool));
1219 SVN_ERR(svn_io_stat_dirent2(&dirent,
1220 shelf_version->files_dir_abspath,
1221 FALSE /*verify_truename*/,
1222 TRUE /*ignore_enoent*/,
1223 result_pool, scratch_pool));
1224 if (dirent->kind == svn_node_none)
1226 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1227 _("Shelf '%s' version %d not found"),
1228 shelf->name, version_number);
1230 shelf_version->mtime = dirent->mtime;
1231 *shelf_version_p = shelf_version;
1232 return SVN_NO_ERROR;
1236 svn_client__shelf_get_newest_version(svn_client__shelf_version_t **shelf_version_p,
1237 svn_client__shelf_t *shelf,
1238 apr_pool_t *result_pool,
1239 apr_pool_t *scratch_pool)
1241 if (shelf->max_version == 0)
1243 *shelf_version_p = NULL;
1244 return SVN_NO_ERROR;
1247 SVN_ERR(svn_client__shelf_version_open(shelf_version_p,
1248 shelf, shelf->max_version,
1249 result_pool, scratch_pool));
1250 return SVN_NO_ERROR;
1254 svn_client__shelf_get_all_versions(apr_array_header_t **versions_p,
1255 svn_client__shelf_t *shelf,
1256 apr_pool_t *result_pool,
1257 apr_pool_t *scratch_pool)
1261 *versions_p = apr_array_make(result_pool, shelf->max_version - 1,
1262 sizeof(svn_client__shelf_version_t *));
1264 for (i = 1; i <= shelf->max_version; i++)
1266 svn_client__shelf_version_t *shelf_version;
1268 SVN_ERR(svn_client__shelf_version_open(&shelf_version,
1270 result_pool, scratch_pool));
1271 APR_ARRAY_PUSH(*versions_p, svn_client__shelf_version_t *) = shelf_version;
1273 return SVN_NO_ERROR;