2 * shelf2.c: implementation of shelving v2
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_shelf2.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 int 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/v2", 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__shelf2_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.d", 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__shelf2_version_t **new_version_p,
164 svn_client__shelf2_t *shelf,
166 apr_pool_t *result_pool)
168 svn_client__shelf2_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 /* Set *ABSPATH to the abspath of the metadata file for SHELF_VERSION
181 * node at RELPATH, no matter whether it exists.
184 get_metadata_abspath(char **abspath,
185 svn_client__shelf2_version_t *shelf_version,
186 const char *wc_relpath,
187 apr_pool_t *result_pool,
188 apr_pool_t *scratch_pool)
190 wc_relpath = apr_psprintf(scratch_pool, "%s.meta", wc_relpath);
191 *abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
196 /* Set *ABSPATH to the abspath of the base text file for SHELF_VERSION
197 * node at RELPATH, no matter whether it exists.
200 get_base_file_abspath(char **base_abspath,
201 svn_client__shelf2_version_t *shelf_version,
202 const char *wc_relpath,
203 apr_pool_t *result_pool,
204 apr_pool_t *scratch_pool)
206 wc_relpath = apr_psprintf(scratch_pool, "%s.base", wc_relpath);
207 *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
212 /* Set *ABSPATH to the abspath of the working text file for SHELF_VERSION
213 * node at RELPATH, no matter whether it exists.
216 get_working_file_abspath(char **work_abspath,
217 svn_client__shelf2_version_t *shelf_version,
218 const char *wc_relpath,
219 apr_pool_t *result_pool,
220 apr_pool_t *scratch_pool)
222 wc_relpath = apr_psprintf(scratch_pool, "%s.work", wc_relpath);
223 *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
228 /* Set *ABSPATH to the abspath of the base props file for SHELF_VERSION
229 * node at RELPATH, no matter whether it exists.
232 get_base_props_abspath(char **base_abspath,
233 svn_client__shelf2_version_t *shelf_version,
234 const char *wc_relpath,
235 apr_pool_t *result_pool,
236 apr_pool_t *scratch_pool)
238 wc_relpath = apr_psprintf(scratch_pool, "%s.base-props", wc_relpath);
239 *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
244 /* Set *ABSPATH to the abspath of the working props file for SHELF_VERSION
245 * node at RELPATH, no matter whether it exists.
248 get_working_props_abspath(char **work_abspath,
249 svn_client__shelf2_version_t *shelf_version,
250 const char *wc_relpath,
251 apr_pool_t *result_pool,
252 apr_pool_t *scratch_pool)
254 wc_relpath = apr_psprintf(scratch_pool, "%s.work-props", wc_relpath);
255 *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
260 /* Delete the storage for SHELF:VERSION. */
262 shelf_version_delete(svn_client__shelf2_t *shelf,
264 apr_pool_t *scratch_pool)
266 const char *files_dir_abspath;
268 SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath,
270 scratch_pool, scratch_pool));
271 SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/,
272 NULL, NULL, /*cancel*/
279 get_log_abspath(char **log_abspath,
280 svn_client__shelf2_t *shelf,
281 apr_pool_t *result_pool,
282 apr_pool_t *scratch_pool)
285 const char *filename;
287 SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
288 filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL);
289 *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
293 /* Set SHELF->revprops by reading from its storage (the '.log' file).
294 * Set SHELF->revprops to empty if the storage file does not exist; this
298 shelf_read_revprops(svn_client__shelf2_t *shelf,
299 apr_pool_t *scratch_pool)
303 svn_stream_t *stream;
305 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
307 shelf->revprops = apr_hash_make(shelf->pool);
308 err = svn_stream_open_readonly(&stream, log_abspath,
309 scratch_pool, scratch_pool);
310 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
312 svn_error_clear(err);
317 SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool));
318 SVN_ERR(svn_stream_close(stream));
322 /* Write SHELF's revprops to its file storage.
325 shelf_write_revprops(svn_client__shelf2_t *shelf,
326 apr_pool_t *scratch_pool)
330 svn_stream_t *stream;
332 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
334 SVN_ERR(svn_io_file_open(&file, log_abspath,
335 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE,
336 APR_FPROT_OS_DEFAULT, scratch_pool));
337 stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
339 SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool));
340 SVN_ERR(svn_stream_close(stream));
345 svn_client__shelf2_revprop_set(svn_client__shelf2_t *shelf,
346 const char *prop_name,
347 const svn_string_t *prop_val,
348 apr_pool_t *scratch_pool)
350 svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name),
351 svn_string_dup(prop_val, shelf->pool));
352 SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
357 svn_client__shelf2_revprop_set_all(svn_client__shelf2_t *shelf,
358 apr_hash_t *revprop_table,
359 apr_pool_t *scratch_pool)
362 shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool);
364 shelf->revprops = apr_hash_make(shelf->pool);
366 SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
371 svn_client__shelf2_revprop_get(svn_string_t **prop_val,
372 svn_client__shelf2_t *shelf,
373 const char *prop_name,
374 apr_pool_t *result_pool)
376 *prop_val = svn_hash_gets(shelf->revprops, prop_name);
381 svn_client__shelf2_revprop_list(apr_hash_t **props,
382 svn_client__shelf2_t *shelf,
383 apr_pool_t *result_pool)
385 *props = shelf->revprops;
391 get_current_abspath(char **current_abspath,
392 svn_client__shelf2_t *shelf,
393 apr_pool_t *result_pool)
398 SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
399 filename = apr_psprintf(result_pool, "%s.current", codename);
400 *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
404 /* Read SHELF->max_version from its storage (the '.current' file).
405 * Set SHELF->max_version to -1 if that file does not exist.
408 shelf_read_current(svn_client__shelf2_t *shelf,
409 apr_pool_t *scratch_pool)
411 char *current_abspath;
414 SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool));
415 err = svn_io_read_version_file(&shelf->max_version,
416 current_abspath, scratch_pool);
419 shelf->max_version = -1;
420 svn_error_clear(err);
428 shelf_write_current(svn_client__shelf2_t *shelf,
429 apr_pool_t *scratch_pool)
431 char *current_abspath;
433 SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool));
434 SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version,
439 /*-------------------------------------------------------------------------*/
440 /* Status Reporting */
442 /* Create a status struct with all fields initialized to valid values
443 * representing 'uninteresting' or 'unknown' status.
445 static svn_wc_status3_t *
446 status_create(apr_pool_t *result_pool)
448 svn_wc_status3_t *s = apr_pcalloc(result_pool, sizeof(*s));
450 s->filesize = SVN_INVALID_FILESIZE;
452 s->node_status = svn_wc_status_none;
453 s->text_status = svn_wc_status_none;
454 s->prop_status = svn_wc_status_none;
455 s->revision = SVN_INVALID_REVNUM;
456 s->changed_rev = SVN_INVALID_REVNUM;
457 s->repos_node_status = svn_wc_status_none;
458 s->repos_text_status = svn_wc_status_none;
459 s->repos_prop_status = svn_wc_status_none;
460 s->ood_changed_rev = SVN_INVALID_REVNUM;
464 /* Convert from svn_node_kind_t to a single character representation. */
466 kind_to_char(svn_node_kind_t kind)
468 return (kind == svn_node_dir ? 'd'
469 : kind == svn_node_file ? 'f'
470 : kind == svn_node_symlink ? 'l'
474 /* Convert to svn_node_kind_t from a single character representation. */
475 static svn_node_kind_t
476 char_to_kind(char kind)
478 return (kind == 'd' ? svn_node_dir
479 : kind == 'f' ? svn_node_file
480 : kind == 'l' ? svn_node_symlink
484 /* Return the single character representation of STATUS.
485 * (Similar to subversion/svn/status.c:generate_status_code()
486 * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */
488 status_to_char(enum svn_wc_status_kind status)
492 case svn_wc_status_none: return '.';
493 case svn_wc_status_unversioned: return '?';
494 case svn_wc_status_normal: return ' ';
495 case svn_wc_status_added: return 'A';
496 case svn_wc_status_missing: return '!';
497 case svn_wc_status_deleted: return 'D';
498 case svn_wc_status_replaced: return 'R';
499 case svn_wc_status_modified: return 'M';
500 case svn_wc_status_merged: return 'G';
501 case svn_wc_status_conflicted: return 'C';
502 case svn_wc_status_ignored: return 'I';
503 case svn_wc_status_obstructed: return '~';
504 case svn_wc_status_external: return 'X';
505 case svn_wc_status_incomplete: return ':';
510 static enum svn_wc_status_kind
511 char_to_status(char status)
515 case '.': return svn_wc_status_none;
516 case '?': return svn_wc_status_unversioned;
517 case ' ': return svn_wc_status_normal;
518 case 'A': return svn_wc_status_added;
519 case '!': return svn_wc_status_missing;
520 case 'D': return svn_wc_status_deleted;
521 case 'R': return svn_wc_status_replaced;
522 case 'M': return svn_wc_status_modified;
523 case 'G': return svn_wc_status_merged;
524 case 'C': return svn_wc_status_conflicted;
525 case 'I': return svn_wc_status_ignored;
526 case '~': return svn_wc_status_obstructed;
527 case 'X': return svn_wc_status_external;
528 case ':': return svn_wc_status_incomplete;
529 default: return (enum svn_wc_status_kind)0;
533 /* Write a serial representation of (some fields of) STATUS to STREAM.
536 wc_status_serialize(svn_stream_t *stream,
537 const svn_wc_status3_t *status,
538 apr_pool_t *scratch_pool)
540 SVN_ERR(svn_stream_printf(stream, scratch_pool, "%c %c%c%c %ld",
541 kind_to_char(status->kind),
542 status_to_char(status->node_status),
543 status_to_char(status->text_status),
544 status_to_char(status->prop_status),
549 /* Read a serial representation of (some fields of) STATUS from STREAM.
552 wc_status_unserialize(svn_wc_status3_t *status,
553 svn_stream_t *stream,
554 apr_pool_t *result_pool)
559 SVN_ERR(svn_stringbuf_from_stream(&sb, stream, 100, result_pool));
561 status->kind = char_to_kind(string[0]);
562 status->node_status = char_to_status(string[2]);
563 status->text_status = char_to_status(string[3]);
564 status->prop_status = char_to_status(string[4]);
565 sscanf(string + 6, "%ld", &status->revision);
569 /* Write status to shelf storage.
572 status_write(svn_client__shelf2_version_t *shelf_version,
574 const svn_wc_status3_t *status,
575 apr_pool_t *scratch_pool)
578 svn_stream_t *stream;
580 SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath,
581 scratch_pool, scratch_pool));
582 SVN_ERR(svn_stream_open_writable(&stream, file_abspath,
583 scratch_pool, scratch_pool));
584 SVN_ERR(wc_status_serialize(stream, status, scratch_pool));
585 SVN_ERR(svn_stream_close(stream));
589 /* Read status from shelf storage.
592 status_read(svn_wc_status3_t **status,
593 svn_client__shelf2_version_t *shelf_version,
595 apr_pool_t *result_pool,
596 apr_pool_t *scratch_pool)
598 svn_wc_status3_t *s = status_create(result_pool);
600 svn_stream_t *stream;
602 SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath,
603 scratch_pool, scratch_pool));
604 SVN_ERR(svn_stream_open_readonly(&stream, file_abspath,
605 scratch_pool, scratch_pool));
606 SVN_ERR(wc_status_unserialize(s, stream, result_pool));
607 SVN_ERR(svn_stream_close(stream));
609 s->changelist = apr_psprintf(result_pool, "svn:shelf:%s",
610 shelf_version->shelf->name);
615 /* A visitor function type for use with shelf_status_walk().
616 * The same as svn_wc_status_func4_t except relpath instead of abspath.
617 * Only some fields in STATUS are available.
619 typedef svn_error_t *(*shelf_status_visitor_t)(void *baton,
621 svn_wc_status3_t *status,
622 apr_pool_t *scratch_pool);
624 /* Baton for shelved_files_walk_visitor(). */
625 struct shelf_status_baton_t
627 svn_client__shelf2_version_t *shelf_version;
628 const char *top_relpath;
629 const char *walk_root_abspath;
630 shelf_status_visitor_t walk_func;
634 /* Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved
635 * 'binary' file stored at ABSPATH.
636 * Implements svn_io_walk_func_t. */
638 shelf_status_visitor(void *baton,
640 const apr_finfo_t *finfo,
641 apr_pool_t *scratch_pool)
643 struct shelf_status_baton_t *b = baton;
646 relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath);
647 if (finfo->filetype == APR_REG
648 && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0))
652 relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5);
653 if (!svn_relpath_skip_ancestor(b->top_relpath, relpath))
656 SVN_ERR(status_read(&s, b->shelf_version, relpath,
657 scratch_pool, scratch_pool));
658 SVN_ERR(b->walk_func(b->walk_baton, relpath, s, scratch_pool));
663 /* Report the shelved status of the path SHELF_VERSION:WC_RELPATH
664 * via WALK_FUNC(WALK_BATON, ...).
667 shelf_status_visit_path(svn_client__shelf2_version_t *shelf_version,
668 const char *wc_relpath,
669 shelf_status_visitor_t walk_func,
671 apr_pool_t *scratch_pool)
673 struct shelf_status_baton_t baton;
677 baton.shelf_version = shelf_version;
678 baton.top_relpath = wc_relpath;
679 baton.walk_root_abspath = shelf_version->files_dir_abspath;
680 baton.walk_func = walk_func;
681 baton.walk_baton = walk_baton;
682 SVN_ERR(get_metadata_abspath(&abspath, shelf_version, wc_relpath,
683 scratch_pool, scratch_pool));
684 SVN_ERR(svn_io_stat(&finfo, abspath, APR_FINFO_TYPE, scratch_pool));
685 SVN_ERR(shelf_status_visitor(&baton, abspath, &finfo, scratch_pool));
689 /* Report the shelved status of all the shelved paths in SHELF_VERSION
690 * via WALK_FUNC(WALK_BATON, ...).
693 shelf_status_walk(svn_client__shelf2_version_t *shelf_version,
694 const char *wc_relpath,
695 shelf_status_visitor_t walk_func,
697 apr_pool_t *scratch_pool)
699 struct shelf_status_baton_t baton;
702 baton.shelf_version = shelf_version;
703 baton.top_relpath = wc_relpath;
704 baton.walk_root_abspath = shelf_version->files_dir_abspath;
705 baton.walk_func = walk_func;
706 baton.walk_baton = walk_baton;
707 err = svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/,
708 shelf_status_visitor, &baton,
710 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
711 svn_error_clear(err);
718 typedef struct wc_status_baton_t
720 svn_client__shelf2_version_t *shelf_version;
721 svn_wc_status_func4_t walk_func;
726 wc_status_visitor(void *baton,
728 svn_wc_status3_t *status,
729 apr_pool_t *scratch_pool)
731 struct wc_status_baton_t *b = baton;
732 svn_client__shelf2_t *shelf = b->shelf_version->shelf;
733 const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath,
735 SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool));
740 svn_client__shelf2_version_status_walk(svn_client__shelf2_version_t *shelf_version,
741 const char *wc_relpath,
742 svn_wc_status_func4_t walk_func,
744 apr_pool_t *scratch_pool)
746 wc_status_baton_t baton;
748 baton.shelf_version = shelf_version;
749 baton.walk_func = walk_func;
750 baton.walk_baton = walk_baton;
751 SVN_ERR(shelf_status_walk(shelf_version, wc_relpath,
752 wc_status_visitor, &baton,
757 /*-------------------------------------------------------------------------*/
760 /* A baton for use with write_changes_visitor(). */
761 typedef struct write_changes_baton_t {
762 const char *wc_root_abspath;
763 svn_client__shelf2_version_t *shelf_version;
764 svn_client_ctx_t *ctx;
765 svn_boolean_t any_shelved; /* were any paths successfully shelved? */
766 svn_client_status_func_t was_shelved_func;
767 void *was_shelved_baton;
768 svn_client_status_func_t was_not_shelved_func;
769 void *was_not_shelved_baton;
770 apr_pool_t *pool; /* pool for data in 'unshelvable', etc. */
771 } write_changes_baton_t;
775 notify_shelved(write_changes_baton_t *wb,
776 const char *wc_relpath,
777 const char *local_abspath,
778 const svn_wc_status3_t *wc_status,
779 apr_pool_t *scratch_pool)
781 if (wb->was_shelved_func)
783 svn_client_status_t *cst;
785 SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath,
787 scratch_pool, scratch_pool));
788 SVN_ERR(wb->was_shelved_func(wb->was_shelved_baton,
789 wc_relpath, cst, scratch_pool));
792 wb->any_shelved = TRUE;
798 notify_not_shelved(write_changes_baton_t *wb,
799 const char *wc_relpath,
800 const char *local_abspath,
801 const svn_wc_status3_t *wc_status,
802 apr_pool_t *scratch_pool)
804 if (wb->was_not_shelved_func)
806 svn_client_status_t *cst;
808 SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath,
810 scratch_pool, scratch_pool));
811 SVN_ERR(wb->was_not_shelved_func(wb->was_not_shelved_baton,
812 wc_relpath, cst, scratch_pool));
818 /* Read BASE_PROPS and WORK_PROPS from the WC, setting each to null if
819 * the node has no base or working version (respectively).
822 read_props_from_wc(apr_hash_t **base_props,
823 apr_hash_t **work_props,
824 enum svn_wc_status_kind node_status,
825 const char *from_wc_abspath,
826 svn_client_ctx_t *ctx,
827 apr_pool_t *result_pool,
828 apr_pool_t *scratch_pool)
830 if (node_status != svn_wc_status_added)
831 SVN_ERR(svn_wc_get_pristine_props(base_props, ctx->wc_ctx, from_wc_abspath,
832 result_pool, scratch_pool));
835 if (node_status != svn_wc_status_deleted)
836 SVN_ERR(svn_wc_prop_list2(work_props, ctx->wc_ctx, from_wc_abspath,
837 result_pool, scratch_pool));
843 /* Write BASE_PROPS and WORK_PROPS to storage in SHELF_VERSION:WC_RELPATH.
846 write_props_to_shelf(svn_client__shelf2_version_t *shelf_version,
847 const char *wc_relpath,
848 apr_hash_t *base_props,
849 apr_hash_t *work_props,
850 apr_pool_t *scratch_pool)
852 char *stored_props_abspath;
853 svn_stream_t *stream;
857 SVN_ERR(get_base_props_abspath(&stored_props_abspath,
858 shelf_version, wc_relpath,
859 scratch_pool, scratch_pool));
860 SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath,
861 scratch_pool, scratch_pool));
862 SVN_ERR(svn_hash_write2(base_props, stream, NULL, scratch_pool));
863 SVN_ERR(svn_stream_close(stream));
868 SVN_ERR(get_working_props_abspath(&stored_props_abspath,
869 shelf_version, wc_relpath,
870 scratch_pool, scratch_pool));
871 SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath,
872 scratch_pool, scratch_pool));
873 SVN_ERR(svn_hash_write2(work_props, stream, NULL, scratch_pool));
874 SVN_ERR(svn_stream_close(stream));
880 /* Read BASE_PROPS and WORK_PROPS from storage in SHELF_VERSION:WC_RELPATH.
883 read_props_from_shelf(apr_hash_t **base_props,
884 apr_hash_t **work_props,
885 enum svn_wc_status_kind node_status,
886 svn_client__shelf2_version_t *shelf_version,
887 const char *wc_relpath,
888 apr_pool_t *result_pool,
889 apr_pool_t *scratch_pool)
891 char *stored_props_abspath;
892 svn_stream_t *stream;
894 if (node_status != svn_wc_status_added)
896 *base_props = apr_hash_make(result_pool);
897 SVN_ERR(get_base_props_abspath(&stored_props_abspath,
898 shelf_version, wc_relpath,
899 scratch_pool, scratch_pool));
900 SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath,
901 scratch_pool, scratch_pool));
902 SVN_ERR(svn_hash_read2(*base_props, stream, NULL, scratch_pool));
903 SVN_ERR(svn_stream_close(stream));
908 if (node_status != svn_wc_status_deleted)
910 *work_props = apr_hash_make(result_pool);
911 SVN_ERR(get_working_props_abspath(&stored_props_abspath,
912 shelf_version, wc_relpath,
913 scratch_pool, scratch_pool));
914 SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath,
915 scratch_pool, scratch_pool));
916 SVN_ERR(svn_hash_read2(*work_props, stream, NULL, scratch_pool));
917 SVN_ERR(svn_stream_close(stream));
925 /* Store metadata for any node, and base and working files if it's a file.
927 * Copy the WC base and working files at FROM_WC_ABSPATH to the storage
928 * area in SHELF_VERSION.
931 store_file(const char *from_wc_abspath,
932 const char *wc_relpath,
933 svn_client__shelf2_version_t *shelf_version,
934 const svn_wc_status3_t *status,
935 svn_client_ctx_t *ctx,
936 apr_pool_t *scratch_pool)
938 char *stored_abspath;
939 apr_hash_t *base_props, *work_props;
941 SVN_ERR(get_working_file_abspath(&stored_abspath,
942 shelf_version, wc_relpath,
943 scratch_pool, scratch_pool));
944 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(stored_abspath,
947 SVN_ERR(status_write(shelf_version, wc_relpath,
948 status, scratch_pool));
951 SVN_ERR(read_props_from_wc(&base_props, &work_props,
953 from_wc_abspath, ctx,
954 scratch_pool, scratch_pool));
955 SVN_ERR(write_props_to_shelf(shelf_version, wc_relpath,
956 base_props, work_props,
960 if (status->kind == svn_node_file)
962 svn_stream_t *wc_base_stream;
963 svn_node_kind_t work_kind;
965 /* Copy the base file (copy-from base, if copied/moved), if present */
966 SVN_ERR(svn_wc_get_pristine_contents2(&wc_base_stream,
967 ctx->wc_ctx, from_wc_abspath,
968 scratch_pool, scratch_pool));
971 char *stored_base_abspath;
972 svn_stream_t *stored_base_stream;
974 SVN_ERR(get_base_file_abspath(&stored_base_abspath,
975 shelf_version, wc_relpath,
976 scratch_pool, scratch_pool));
977 SVN_ERR(svn_stream_open_writable(&stored_base_stream,
979 scratch_pool, scratch_pool));
980 SVN_ERR(svn_stream_copy3(wc_base_stream, stored_base_stream,
981 NULL, NULL, scratch_pool));
984 /* Copy the working file, if present */
985 SVN_ERR(svn_io_check_path(from_wc_abspath, &work_kind, scratch_pool));
986 if (work_kind == svn_node_file)
988 SVN_ERR(svn_io_copy_file(from_wc_abspath, stored_abspath,
989 TRUE /*copy_perms*/, scratch_pool));
995 /* An implementation of svn_wc_status_func4_t. */
997 write_changes_visitor(void *baton,
998 const char *local_abspath,
999 const svn_wc_status3_t *status,
1000 apr_pool_t *scratch_pool)
1002 write_changes_baton_t *wb = baton;
1003 const char *wc_relpath = svn_dirent_skip_ancestor(wb->wc_root_abspath,
1006 /* Catch any conflict, even a tree conflict on a path that has
1007 node-status 'unversioned'. */
1008 if (status->conflicted)
1010 SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1011 status, scratch_pool));
1013 else switch (status->node_status)
1015 case svn_wc_status_deleted:
1016 case svn_wc_status_added:
1017 case svn_wc_status_replaced:
1018 if (status->kind != svn_node_file
1021 SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1022 status, scratch_pool));
1026 case svn_wc_status_modified:
1028 /* Store metadata, and base and working versions if it's a file */
1029 SVN_ERR(store_file(local_abspath, wc_relpath, wb->shelf_version,
1030 status, wb->ctx, scratch_pool));
1031 SVN_ERR(notify_shelved(wb, wc_relpath, local_abspath,
1032 status, scratch_pool));
1036 case svn_wc_status_incomplete:
1037 if ((status->text_status != svn_wc_status_normal
1038 && status->text_status != svn_wc_status_none)
1039 || (status->prop_status != svn_wc_status_normal
1040 && status->prop_status != svn_wc_status_none))
1042 /* Incomplete, but local modifications */
1043 SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1044 status, scratch_pool));
1048 case svn_wc_status_conflicted:
1049 case svn_wc_status_missing:
1050 case svn_wc_status_obstructed:
1051 SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1052 status, scratch_pool));
1055 case svn_wc_status_normal:
1056 case svn_wc_status_ignored:
1057 case svn_wc_status_none:
1058 case svn_wc_status_external:
1059 case svn_wc_status_unversioned:
1064 return SVN_NO_ERROR;
1067 /* A baton for use with changelist_filter_func(). */
1068 struct changelist_filter_baton_t {
1069 apr_hash_t *changelist_hash;
1070 svn_wc_status_func4_t status_func;
1074 /* Filter out paths that are not in the requested changelist(s).
1075 * Implements svn_wc_status_func4_t. */
1076 static svn_error_t *
1077 changelist_filter_func(void *baton,
1078 const char *local_abspath,
1079 const svn_wc_status3_t *status,
1080 apr_pool_t *scratch_pool)
1082 struct changelist_filter_baton_t *b = baton;
1084 if (b->changelist_hash
1085 && (! status->changelist
1086 || ! svn_hash_gets(b->changelist_hash, status->changelist)))
1088 return SVN_NO_ERROR;
1091 SVN_ERR(b->status_func(b->status_baton, local_abspath, status,
1093 return SVN_NO_ERROR;
1097 * Walk the WC tree(s) rooted at PATHS, to depth DEPTH, omitting paths that
1098 * are not in one of the CHANGELISTS (if not null).
1100 * Call STATUS_FUNC(STATUS_BATON, ...) for each visited path.
1102 * PATHS are absolute, or relative to CWD.
1104 static svn_error_t *
1105 wc_walk_status_multi(const apr_array_header_t *paths,
1107 const apr_array_header_t *changelists,
1108 svn_wc_status_func4_t status_func,
1110 svn_client_ctx_t *ctx,
1111 apr_pool_t *scratch_pool)
1113 struct changelist_filter_baton_t cb = {0};
1116 if (changelists && changelists->nelts)
1117 SVN_ERR(svn_hash_from_cstring_keys(&cb.changelist_hash,
1118 changelists, scratch_pool));
1119 cb.status_func = status_func;
1120 cb.status_baton = status_baton;
1122 for (i = 0; i < paths->nelts; i++)
1124 const char *path = APR_ARRAY_IDX(paths, i, const char *);
1126 if (svn_path_is_url(path))
1127 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1128 _("'%s' is not a local path"), path);
1129 SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool));
1131 SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, path, depth,
1132 FALSE /*get_all*/, FALSE /*no_ignore*/,
1133 FALSE /*ignore_text_mods*/,
1134 NULL /*ignore_patterns*/,
1135 changelist_filter_func, &cb,
1136 ctx->cancel_func, ctx->cancel_baton,
1140 return SVN_NO_ERROR;
1143 /** Write local changes to the shelf storage.
1145 * @a paths, @a depth, @a changelists: The selection of local paths to diff.
1147 * @a paths are relative to CWD (or absolute).
1149 static svn_error_t *
1150 shelf_write_changes(svn_boolean_t *any_shelved,
1151 svn_client__shelf2_version_t *shelf_version,
1152 const apr_array_header_t *paths,
1154 const apr_array_header_t *changelists,
1155 svn_client_status_func_t shelved_func,
1156 void *shelved_baton,
1157 svn_client_status_func_t not_shelved_func,
1158 void *not_shelved_baton,
1159 const char *wc_root_abspath,
1160 svn_client_ctx_t *ctx,
1161 apr_pool_t *result_pool,
1162 apr_pool_t *scratch_pool)
1164 write_changes_baton_t wb = { 0 };
1166 wb.wc_root_abspath = wc_root_abspath;
1167 wb.shelf_version = shelf_version;
1169 wb.any_shelved = FALSE;
1170 wb.was_shelved_func = shelved_func;
1171 wb.was_shelved_baton = shelved_baton;
1172 wb.was_not_shelved_func = not_shelved_func;
1173 wb.was_not_shelved_baton = not_shelved_baton;
1174 wb.pool = result_pool;
1177 SVN_ERR(wc_walk_status_multi(paths, depth, changelists,
1178 write_changes_visitor, &wb,
1179 ctx, scratch_pool));
1181 *any_shelved = wb.any_shelved;
1182 return SVN_NO_ERROR;
1185 /* Construct a shelf object representing an empty shelf: no versions,
1186 * no revprops, no looking to see if such a shelf exists on disk.
1188 static svn_error_t *
1189 shelf_construct(svn_client__shelf2_t **shelf_p,
1191 const char *local_abspath,
1192 svn_client_ctx_t *ctx,
1193 apr_pool_t *result_pool)
1195 svn_client__shelf2_t *shelf = apr_palloc(result_pool, sizeof(*shelf));
1198 SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath,
1200 result_pool, result_pool));
1201 SVN_ERR(get_shelves_dir(&shelves_dir,
1202 ctx->wc_ctx, local_abspath,
1203 result_pool, result_pool));
1204 shelf->shelves_dir = shelves_dir;
1206 shelf->pool = result_pool;
1208 shelf->name = apr_pstrdup(result_pool, name);
1209 shelf->revprops = apr_hash_make(result_pool);
1210 shelf->max_version = 0;
1213 return SVN_NO_ERROR;
1217 svn_client__shelf2_open_existing(svn_client__shelf2_t **shelf_p,
1219 const char *local_abspath,
1220 svn_client_ctx_t *ctx,
1221 apr_pool_t *result_pool)
1223 SVN_ERR(shelf_construct(shelf_p, name,
1224 local_abspath, ctx, result_pool));
1225 SVN_ERR(shelf_read_revprops(*shelf_p, result_pool));
1226 SVN_ERR(shelf_read_current(*shelf_p, result_pool));
1227 if ((*shelf_p)->max_version < 0)
1229 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1230 _("Shelf '%s' not found"),
1233 return SVN_NO_ERROR;
1237 svn_client__shelf2_open_or_create(svn_client__shelf2_t **shelf_p,
1239 const char *local_abspath,
1240 svn_client_ctx_t *ctx,
1241 apr_pool_t *result_pool)
1243 svn_client__shelf2_t *shelf;
1245 SVN_ERR(shelf_construct(&shelf, name,
1246 local_abspath, ctx, result_pool));
1247 SVN_ERR(shelf_read_revprops(shelf, result_pool));
1248 SVN_ERR(shelf_read_current(shelf, result_pool));
1249 if (shelf->max_version < 0)
1251 shelf->max_version = 0;
1252 SVN_ERR(shelf_write_current(shelf, result_pool));
1255 return SVN_NO_ERROR;
1259 svn_client__shelf2_close(svn_client__shelf2_t *shelf,
1260 apr_pool_t *scratch_pool)
1262 return SVN_NO_ERROR;
1266 svn_client__shelf2_delete(const char *name,
1267 const char *local_abspath,
1268 svn_boolean_t dry_run,
1269 svn_client_ctx_t *ctx,
1270 apr_pool_t *scratch_pool)
1272 svn_client__shelf2_t *shelf;
1276 SVN_ERR(svn_client__shelf2_open_existing(&shelf, name,
1277 local_abspath, ctx, scratch_pool));
1279 /* Remove the versions. */
1280 for (i = shelf->max_version; i > 0; i--)
1282 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
1285 /* Remove the other files */
1286 SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool));
1287 SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
1288 SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool));
1289 SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
1291 SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool));
1292 return SVN_NO_ERROR;
1295 /* Baton for paths_changed_visitor(). */
1296 struct paths_changed_walk_baton_t
1298 apr_hash_t *paths_hash;
1299 svn_boolean_t as_abspath;
1300 const char *wc_root_abspath;
1304 /* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file.
1305 * Implements shelved_files_walk_func_t. */
1306 static svn_error_t *
1307 paths_changed_visitor(void *baton,
1308 const char *relpath,
1309 svn_wc_status3_t *s,
1310 apr_pool_t *scratch_pool)
1312 struct paths_changed_walk_baton_t *b = baton;
1314 relpath = (b->as_abspath
1315 ? svn_dirent_join(b->wc_root_abspath, relpath, b->pool)
1316 : apr_pstrdup(b->pool, relpath));
1317 svn_hash_sets(b->paths_hash, relpath, relpath);
1318 return SVN_NO_ERROR;
1321 /* Get the paths changed, relative to WC root or as abspaths, as a hash
1322 * and/or an array (in no particular order).
1324 static svn_error_t *
1325 shelf_paths_changed(apr_hash_t **paths_hash_p,
1326 apr_array_header_t **paths_array_p,
1327 svn_client__shelf2_version_t *shelf_version,
1328 svn_boolean_t as_abspath,
1329 apr_pool_t *result_pool,
1330 apr_pool_t *scratch_pool)
1332 svn_client__shelf2_t *shelf = shelf_version->shelf;
1333 apr_hash_t *paths_hash = apr_hash_make(result_pool);
1334 struct paths_changed_walk_baton_t baton;
1336 baton.paths_hash = paths_hash;
1337 baton.as_abspath = as_abspath;
1338 baton.wc_root_abspath = shelf->wc_root_abspath;
1339 baton.pool = result_pool;
1340 SVN_ERR(shelf_status_walk(shelf_version, "",
1341 paths_changed_visitor, &baton,
1345 *paths_hash_p = paths_hash;
1347 SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool));
1349 return SVN_NO_ERROR;
1353 svn_client__shelf2_paths_changed(apr_hash_t **affected_paths,
1354 svn_client__shelf2_version_t *shelf_version,
1355 apr_pool_t *result_pool,
1356 apr_pool_t *scratch_pool)
1358 SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version,
1359 FALSE /*as_abspath*/,
1360 result_pool, scratch_pool));
1361 return SVN_NO_ERROR;
1364 /* Send a notification */
1365 static svn_error_t *
1366 send_notification(const char *local_abspath,
1367 svn_wc_notify_action_t action,
1368 svn_node_kind_t kind,
1369 svn_wc_notify_state_t content_state,
1370 svn_wc_notify_state_t prop_state,
1371 svn_wc_notify_func2_t notify_func,
1373 apr_pool_t *scratch_pool)
1377 svn_wc_notify_t *notify
1378 = svn_wc_create_notify(local_abspath, action, scratch_pool);
1380 notify->kind = kind;
1381 notify->content_state = content_state;
1382 notify->prop_state = prop_state;
1383 notify_func(notify_baton, notify, scratch_pool);
1386 return SVN_NO_ERROR;
1389 /* Merge a shelved change into WC_ABSPATH.
1391 static svn_error_t *
1392 wc_file_merge(const char *wc_abspath,
1393 const char *left_file,
1394 const char *right_file,
1395 /*const*/ apr_hash_t *left_props,
1396 /*const*/ apr_hash_t *right_props,
1397 svn_client_ctx_t *ctx,
1398 apr_pool_t *scratch_pool)
1400 svn_wc_notify_state_t property_state;
1401 svn_boolean_t has_local_mods;
1402 enum svn_wc_merge_outcome_t content_outcome;
1403 const char *target_label, *left_label, *right_label;
1404 apr_array_header_t *prop_changes;
1406 /* xgettext: the '.working', '.merge-left' and '.merge-right' strings
1407 are used to tag onto a file name in case of a merge conflict */
1408 target_label = apr_psprintf(scratch_pool, _(".working"));
1409 left_label = apr_psprintf(scratch_pool, _(".merge-left"));
1410 right_label = apr_psprintf(scratch_pool, _(".merge-right"));
1412 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1413 SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx,
1414 wc_abspath, FALSE, scratch_pool));
1416 /* Do property merge and text merge in one step so that keyword expansion
1417 takes into account the new property values. */
1418 SVN_WC__CALL_WITH_WRITE_LOCK(
1419 svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx,
1420 left_file, right_file, wc_abspath,
1421 left_label, right_label, target_label,
1422 NULL, NULL, /*left, right conflict-versions*/
1423 FALSE /*dry_run*/, NULL /*diff3_cmd*/,
1424 NULL /*merge_options*/,
1425 left_props, prop_changes,
1427 ctx->cancel_func, ctx->cancel_baton,
1429 ctx->wc_ctx, wc_abspath,
1430 FALSE /*lock_anchor*/, scratch_pool);
1432 return SVN_NO_ERROR;
1435 /* Merge a shelved change (of properties) into the dir at WC_ABSPATH.
1437 static svn_error_t *
1438 wc_dir_props_merge(const char *wc_abspath,
1439 /*const*/ apr_hash_t *left_props,
1440 /*const*/ apr_hash_t *right_props,
1441 svn_client_ctx_t *ctx,
1442 apr_pool_t *result_pool,
1443 apr_pool_t *scratch_pool)
1445 apr_array_header_t *prop_changes;
1446 svn_wc_notify_state_t property_state;
1448 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1449 SVN_WC__CALL_WITH_WRITE_LOCK(
1450 svn_wc_merge_props3(&property_state, ctx->wc_ctx,
1452 NULL, NULL, /*left, right conflict-versions*/
1453 left_props, prop_changes,
1456 ctx->cancel_func, ctx->cancel_baton,
1458 ctx->wc_ctx, wc_abspath,
1459 FALSE /*lock_anchor*/, scratch_pool);
1461 return SVN_NO_ERROR;
1464 /* Apply a shelved "delete" to TO_WC_ABSPATH.
1466 static svn_error_t *
1467 wc_node_delete(const char *to_wc_abspath,
1468 svn_client_ctx_t *ctx,
1469 apr_pool_t *scratch_pool)
1471 SVN_WC__CALL_WITH_WRITE_LOCK(
1472 svn_wc_delete4(ctx->wc_ctx,
1474 FALSE /*keep_local*/,
1475 TRUE /*delete_unversioned_target*/,
1476 NULL, NULL, NULL, NULL, /*cancel, notify*/
1478 ctx->wc_ctx, to_wc_abspath,
1479 TRUE /*lock_anchor*/, scratch_pool);
1480 return SVN_NO_ERROR;
1483 /* Apply a shelved "add" to TO_WC_ABSPATH.
1484 * The node must already exist on disk, in a versioned parent dir.
1486 static svn_error_t *
1487 wc_node_add(const char *to_wc_abspath,
1488 apr_hash_t *work_props,
1489 svn_client_ctx_t *ctx,
1490 apr_pool_t *scratch_pool)
1492 /* If it was not already versioned, schedule the node for addition.
1493 (Do not apply autoprops, because this isn't a user-facing "add" but
1494 restoring a previously saved state.) */
1495 SVN_WC__CALL_WITH_WRITE_LOCK(
1496 svn_wc_add_from_disk3(ctx->wc_ctx,
1497 to_wc_abspath, work_props,
1498 FALSE /* skip checks */,
1499 NULL, NULL, scratch_pool),
1500 ctx->wc_ctx, to_wc_abspath,
1501 TRUE /*lock_anchor*/, scratch_pool);
1502 return SVN_NO_ERROR;
1505 /* Baton for apply_file_visitor(). */
1506 struct apply_files_baton_t
1508 svn_client__shelf2_version_t *shelf_version;
1509 svn_boolean_t test_only; /* only check whether it would conflict */
1510 svn_boolean_t conflict; /* would it conflict? */
1511 svn_client_ctx_t *ctx;
1514 /* Copy the file RELPATH from shelf binary file storage to the WC.
1516 * If it is not already versioned, schedule the file for addition.
1518 * Make any missing parent directories.
1520 * In test mode (BATON->test_only): set BATON->conflict if we can't apply
1521 * the change to WC at RELPATH without conflict. But in fact, just check
1522 * if WC at RELPATH is locally modified.
1524 * Implements shelved_files_walk_func_t. */
1525 static svn_error_t *
1526 apply_file_visitor(void *baton,
1527 const char *relpath,
1528 svn_wc_status3_t *s,
1529 apr_pool_t *scratch_pool)
1531 struct apply_files_baton_t *b = baton;
1532 const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath;
1533 char *stored_base_abspath, *stored_work_abspath;
1534 apr_hash_t *base_props, *work_props;
1535 const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath,
1537 const char *to_dir_abspath = svn_dirent_dirname(to_wc_abspath, scratch_pool);
1539 SVN_ERR(get_base_file_abspath(&stored_base_abspath,
1540 b->shelf_version, relpath,
1541 scratch_pool, scratch_pool));
1542 SVN_ERR(get_working_file_abspath(&stored_work_abspath,
1543 b->shelf_version, relpath,
1544 scratch_pool, scratch_pool));
1545 SVN_ERR(read_props_from_shelf(&base_props, &work_props,
1547 b->shelf_version, relpath,
1548 scratch_pool, scratch_pool));
1552 svn_wc_status3_t *status;
1554 SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath,
1555 scratch_pool, scratch_pool));
1556 switch (status->node_status)
1558 case svn_wc_status_normal:
1559 case svn_wc_status_none:
1565 return SVN_NO_ERROR;
1568 /* Handle 'delete' and the delete half of 'replace' */
1569 if (s->node_status == svn_wc_status_deleted
1570 || s->node_status == svn_wc_status_replaced)
1572 SVN_ERR(wc_node_delete(to_wc_abspath, b->ctx, scratch_pool));
1573 if (s->node_status != svn_wc_status_replaced)
1575 SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_delete,
1577 svn_wc_notify_state_inapplicable,
1578 svn_wc_notify_state_inapplicable,
1579 b->ctx->notify_func2, b->ctx->notify_baton2,
1584 /* If we can merge a file, do so. */
1585 if (s->node_status == svn_wc_status_modified)
1587 if (s->kind == svn_node_dir)
1589 SVN_ERR(wc_dir_props_merge(to_wc_abspath,
1590 base_props, work_props,
1591 b->ctx, scratch_pool, scratch_pool));
1593 else if (s->kind == svn_node_file)
1595 SVN_ERR(wc_file_merge(to_wc_abspath,
1596 stored_base_abspath, stored_work_abspath,
1597 base_props, work_props,
1598 b->ctx, scratch_pool));
1600 SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_update,
1602 (s->kind == svn_node_dir)
1603 ? svn_wc_notify_state_inapplicable
1604 : svn_wc_notify_state_merged,
1605 (s->kind == svn_node_dir)
1606 ? svn_wc_notify_state_merged
1607 : svn_wc_notify_state_unknown,
1608 b->ctx->notify_func2, b->ctx->notify_baton2,
1612 /* For an added file, copy it into the WC and ensure it's versioned. */
1613 if (s->node_status == svn_wc_status_added
1614 || s->node_status == svn_wc_status_replaced)
1616 if (s->kind == svn_node_dir)
1618 SVN_ERR(svn_io_make_dir_recursively(to_wc_abspath, scratch_pool));
1620 else if (s->kind == svn_node_file)
1622 SVN_ERR(svn_io_make_dir_recursively(to_dir_abspath, scratch_pool));
1623 SVN_ERR(svn_io_copy_file(stored_work_abspath, to_wc_abspath,
1624 TRUE /*copy_perms*/, scratch_pool));
1626 SVN_ERR(wc_node_add(to_wc_abspath, work_props, b->ctx, scratch_pool));
1627 SVN_ERR(send_notification(to_wc_abspath,
1628 (s->node_status == svn_wc_status_replaced)
1629 ? svn_wc_notify_update_replace
1630 : svn_wc_notify_update_add,
1632 svn_wc_notify_state_inapplicable,
1633 svn_wc_notify_state_inapplicable,
1634 b->ctx->notify_func2, b->ctx->notify_baton2,
1638 return SVN_NO_ERROR;
1641 /*-------------------------------------------------------------------------*/
1645 static svn_error_t *
1646 file_changed(svn_client__shelf2_version_t *shelf_version,
1647 const char *relpath,
1648 svn_wc_status3_t *s,
1649 const svn_diff_tree_processor_t *diff_processor,
1650 svn_diff_source_t *left_source,
1651 svn_diff_source_t *right_source,
1652 const char *left_stored_abspath,
1653 const char *right_stored_abspath,
1655 apr_pool_t *scratch_pool)
1658 svn_boolean_t skip = FALSE;
1660 SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
1661 left_source, right_source,
1663 dir_baton, diff_processor,
1664 scratch_pool, scratch_pool));
1667 apr_hash_t *left_props, *right_props;
1668 apr_array_header_t *prop_changes;
1670 SVN_ERR(read_props_from_shelf(&left_props, &right_props,
1671 s->node_status, shelf_version, relpath,
1672 scratch_pool, scratch_pool));
1673 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
1675 SVN_ERR(diff_processor->file_changed(
1677 left_source, right_source,
1678 left_stored_abspath, right_stored_abspath,
1679 left_props, right_props,
1680 TRUE /*file_modified*/, prop_changes,
1681 fb, diff_processor, scratch_pool));
1684 return SVN_NO_ERROR;
1688 static svn_error_t *
1689 file_deleted(svn_client__shelf2_version_t *shelf_version,
1690 const char *relpath,
1691 svn_wc_status3_t *s,
1692 const svn_diff_tree_processor_t *diff_processor,
1693 svn_diff_source_t *left_source,
1694 const char *left_stored_abspath,
1696 apr_pool_t *scratch_pool)
1699 svn_boolean_t skip = FALSE;
1701 SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
1702 left_source, NULL, NULL /*copyfrom*/,
1703 dir_baton, diff_processor,
1704 scratch_pool, scratch_pool));
1707 apr_hash_t *left_props, *right_props;
1709 SVN_ERR(read_props_from_shelf(&left_props, &right_props,
1710 s->node_status, shelf_version, relpath,
1711 scratch_pool, scratch_pool));
1712 SVN_ERR(diff_processor->file_deleted(relpath,
1714 left_stored_abspath,
1720 return SVN_NO_ERROR;
1724 static svn_error_t *
1725 file_added(svn_client__shelf2_version_t *shelf_version,
1726 const char *relpath,
1727 svn_wc_status3_t *s,
1728 const svn_diff_tree_processor_t *diff_processor,
1729 svn_diff_source_t *right_source,
1730 const char *right_stored_abspath,
1732 apr_pool_t *scratch_pool)
1735 svn_boolean_t skip = FALSE;
1737 SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
1738 NULL, right_source, NULL /*copyfrom*/,
1739 dir_baton, diff_processor,
1740 scratch_pool, scratch_pool));
1743 apr_hash_t *left_props, *right_props;
1745 SVN_ERR(read_props_from_shelf(&left_props, &right_props,
1746 s->node_status, shelf_version, relpath,
1747 scratch_pool, scratch_pool));
1748 SVN_ERR(diff_processor->file_added(
1750 NULL /*copyfrom_source*/, right_source,
1751 NULL /*copyfrom_abspath*/, right_stored_abspath,
1752 NULL /*copyfrom_props*/, right_props,
1753 fb, diff_processor, scratch_pool));
1756 return SVN_NO_ERROR;
1759 /* Baton for diff_visitor(). */
1762 svn_client__shelf2_version_t *shelf_version;
1763 const char *top_relpath; /* top of diff, relative to shelf */
1764 const char *walk_root_abspath;
1765 const svn_diff_tree_processor_t *diff_processor;
1768 /* Drive BATON->diff_processor.
1769 * Implements svn_io_walk_func_t. */
1770 static svn_error_t *
1771 diff_visitor(void *baton,
1772 const char *abspath,
1773 const apr_finfo_t *finfo,
1774 apr_pool_t *scratch_pool)
1776 struct diff_baton_t *b = baton;
1777 const char *relpath;
1779 relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath);
1780 if (finfo->filetype == APR_REG
1781 && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0))
1783 svn_wc_status3_t *s;
1785 svn_diff_source_t *left_source;
1786 svn_diff_source_t *right_source;
1787 char *left_stored_abspath, *right_stored_abspath;
1789 relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5);
1790 if (!svn_relpath_skip_ancestor(b->top_relpath, relpath))
1791 return SVN_NO_ERROR;
1793 SVN_ERR(status_read(&s, b->shelf_version, relpath,
1794 scratch_pool, scratch_pool));
1796 left_source = svn_diff__source_create(s->revision, scratch_pool);
1797 right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
1798 SVN_ERR(get_base_file_abspath(&left_stored_abspath,
1799 b->shelf_version, relpath,
1800 scratch_pool, scratch_pool));
1801 SVN_ERR(get_working_file_abspath(&right_stored_abspath,
1802 b->shelf_version, relpath,
1803 scratch_pool, scratch_pool));
1805 switch (s->node_status)
1807 case svn_wc_status_modified:
1808 SVN_ERR(file_changed(b->shelf_version, relpath, s,
1810 left_source, right_source,
1811 left_stored_abspath, right_stored_abspath,
1814 case svn_wc_status_added:
1815 SVN_ERR(file_added(b->shelf_version, relpath, s,
1817 right_source, right_stored_abspath,
1820 case svn_wc_status_deleted:
1821 SVN_ERR(file_deleted(b->shelf_version, relpath, s,
1823 left_source, left_stored_abspath,
1826 case svn_wc_status_replaced:
1827 SVN_ERR(file_deleted(b->shelf_version, relpath, s,
1829 left_source, left_stored_abspath,
1831 SVN_ERR(file_added(b->shelf_version, relpath, s,
1833 right_source, right_stored_abspath,
1839 return SVN_NO_ERROR;
1843 svn_client__shelf2_test_apply_file(svn_boolean_t *conflict_p,
1844 svn_client__shelf2_version_t *shelf_version,
1845 const char *file_relpath,
1846 apr_pool_t *scratch_pool)
1848 struct apply_files_baton_t baton = {0};
1850 baton.shelf_version = shelf_version;
1851 baton.test_only = TRUE;
1852 baton.conflict = FALSE;
1853 baton.ctx = shelf_version->shelf->ctx;
1854 SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath,
1855 apply_file_visitor, &baton,
1857 *conflict_p = baton.conflict;
1859 return SVN_NO_ERROR;
1863 svn_client__shelf2_apply(svn_client__shelf2_version_t *shelf_version,
1864 svn_boolean_t dry_run,
1865 apr_pool_t *scratch_pool)
1867 struct apply_files_baton_t baton = {0};
1869 baton.shelf_version = shelf_version;
1870 baton.ctx = shelf_version->shelf->ctx;
1871 SVN_ERR(shelf_status_walk(shelf_version, "",
1872 apply_file_visitor, &baton,
1875 svn_io_sleep_for_timestamps(shelf_version->shelf->wc_root_abspath,
1877 return SVN_NO_ERROR;
1881 svn_client__shelf2_unapply(svn_client__shelf2_version_t *shelf_version,
1882 svn_boolean_t dry_run,
1883 apr_pool_t *scratch_pool)
1885 apr_array_header_t *targets;
1887 SVN_ERR(shelf_paths_changed(NULL, &targets, shelf_version,
1888 TRUE /*as_abspath*/,
1889 scratch_pool, scratch_pool));
1892 SVN_ERR(svn_client_revert4(targets, svn_depth_empty,
1893 NULL /*changelists*/,
1894 FALSE /*clear_changelists*/,
1895 FALSE /*metadata_only*/,
1896 FALSE /*added_keep_local*/,
1897 shelf_version->shelf->ctx, scratch_pool));
1899 return SVN_NO_ERROR;
1903 svn_client__shelf2_delete_newer_versions(svn_client__shelf2_t *shelf,
1904 svn_client__shelf2_version_t *shelf_version,
1905 apr_pool_t *scratch_pool)
1907 int previous_version = shelf_version ? shelf_version->version_number : 0;
1910 /* Delete any newer checkpoints */
1911 for (i = shelf->max_version; i > previous_version; i--)
1913 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
1916 shelf->max_version = previous_version;
1917 SVN_ERR(shelf_write_current(shelf, scratch_pool));
1918 return SVN_NO_ERROR;
1922 svn_client__shelf2_diff(svn_client__shelf2_version_t *shelf_version,
1923 const char *shelf_relpath,
1925 svn_boolean_t ignore_ancestry,
1926 const svn_diff_tree_processor_t *diff_processor,
1927 apr_pool_t *scratch_pool)
1929 struct diff_baton_t baton;
1931 if (shelf_version->version_number == 0)
1932 return SVN_NO_ERROR;
1934 baton.shelf_version = shelf_version;
1935 baton.top_relpath = shelf_relpath;
1936 baton.walk_root_abspath = shelf_version->files_dir_abspath;
1937 baton.diff_processor = diff_processor;
1938 SVN_ERR(svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/,
1939 diff_visitor, &baton,
1942 return SVN_NO_ERROR;
1946 svn_client__shelf2_save_new_version3(svn_client__shelf2_version_t **new_version_p,
1947 svn_client__shelf2_t *shelf,
1948 const apr_array_header_t *paths,
1950 const apr_array_header_t *changelists,
1951 svn_client_status_func_t shelved_func,
1952 void *shelved_baton,
1953 svn_client_status_func_t not_shelved_func,
1954 void *not_shelved_baton,
1955 apr_pool_t *scratch_pool)
1957 int next_version = shelf->max_version + 1;
1958 svn_client__shelf2_version_t *new_shelf_version;
1959 svn_boolean_t any_shelved;
1961 SVN_ERR(shelf_version_create(&new_shelf_version,
1962 shelf, next_version, scratch_pool));
1963 SVN_ERR(shelf_write_changes(&any_shelved,
1965 paths, depth, changelists,
1966 shelved_func, shelved_baton,
1967 not_shelved_func, not_shelved_baton,
1968 shelf->wc_root_abspath,
1969 shelf->ctx, scratch_pool, scratch_pool));
1973 shelf->max_version = next_version;
1974 SVN_ERR(shelf_write_current(shelf, scratch_pool));
1977 SVN_ERR(svn_client__shelf2_version_open(new_version_p, shelf, next_version,
1978 scratch_pool, scratch_pool));
1983 *new_version_p = NULL;
1985 return SVN_NO_ERROR;
1989 svn_client__shelf2_get_log_message(char **log_message,
1990 svn_client__shelf2_t *shelf,
1991 apr_pool_t *result_pool)
1993 svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG);
1996 *log_message = apr_pstrdup(result_pool, propval->data);
1998 *log_message = NULL;
1999 return SVN_NO_ERROR;
2003 svn_client__shelf2_set_log_message(svn_client__shelf2_t *shelf,
2004 const char *message,
2005 apr_pool_t *scratch_pool)
2007 svn_string_t *propval
2008 = message ? svn_string_create(message, shelf->pool) : NULL;
2010 SVN_ERR(svn_client__shelf2_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval,
2012 return SVN_NO_ERROR;
2016 svn_client__shelf2_list(apr_hash_t **shelf_infos,
2017 const char *local_abspath,
2018 svn_client_ctx_t *ctx,
2019 apr_pool_t *result_pool,
2020 apr_pool_t *scratch_pool)
2022 const char *wc_root_abspath;
2024 apr_hash_t *dirents;
2025 apr_hash_index_t *hi;
2027 SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath,
2028 scratch_pool, scratch_pool));
2029 SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
2030 scratch_pool, scratch_pool));
2031 SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
2032 result_pool, scratch_pool));
2034 *shelf_infos = apr_hash_make(result_pool);
2036 /* Remove non-shelves */
2037 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
2039 const char *filename = apr_hash_this_key(hi);
2040 svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
2043 svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
2044 if (name && dirent->kind == svn_node_file)
2046 svn_client__shelf2_info_t *info
2047 = apr_palloc(result_pool, sizeof(*info));
2049 info->mtime = dirent->mtime;
2050 svn_hash_sets(*shelf_infos, name, info);
2054 return SVN_NO_ERROR;
2058 svn_client__shelf2_version_open(svn_client__shelf2_version_t **shelf_version_p,
2059 svn_client__shelf2_t *shelf,
2061 apr_pool_t *result_pool,
2062 apr_pool_t *scratch_pool)
2064 svn_client__shelf2_version_t *shelf_version;
2065 const svn_io_dirent2_t *dirent;
2067 SVN_ERR(shelf_version_create(&shelf_version,
2068 shelf, version_number, result_pool));
2069 SVN_ERR(svn_io_stat_dirent2(&dirent,
2070 shelf_version->files_dir_abspath,
2071 FALSE /*verify_truename*/,
2072 TRUE /*ignore_enoent*/,
2073 result_pool, scratch_pool));
2074 if (dirent->kind == svn_node_none)
2076 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
2077 _("Shelf '%s' version %d not found"),
2078 shelf->name, version_number);
2080 shelf_version->mtime = dirent->mtime;
2081 *shelf_version_p = shelf_version;
2082 return SVN_NO_ERROR;
2086 svn_client__shelf2_get_newest_version(svn_client__shelf2_version_t **shelf_version_p,
2087 svn_client__shelf2_t *shelf,
2088 apr_pool_t *result_pool,
2089 apr_pool_t *scratch_pool)
2091 if (shelf->max_version == 0)
2093 *shelf_version_p = NULL;
2094 return SVN_NO_ERROR;
2097 SVN_ERR(svn_client__shelf2_version_open(shelf_version_p,
2098 shelf, shelf->max_version,
2099 result_pool, scratch_pool));
2100 return SVN_NO_ERROR;
2104 svn_client__shelf2_get_all_versions(apr_array_header_t **versions_p,
2105 svn_client__shelf2_t *shelf,
2106 apr_pool_t *result_pool,
2107 apr_pool_t *scratch_pool)
2111 *versions_p = apr_array_make(result_pool, shelf->max_version - 1,
2112 sizeof(svn_client__shelf2_version_t *));
2114 for (i = 1; i <= shelf->max_version; i++)
2116 svn_client__shelf2_version_t *shelf_version;
2118 SVN_ERR(svn_client__shelf2_version_open(&shelf_version,
2120 result_pool, scratch_pool));
2121 APR_ARRAY_PUSH(*versions_p, svn_client__shelf2_version_t *) = shelf_version;
2123 return SVN_NO_ERROR;