shelf2.c revision 362181
1/* 2 * shelf2.c: implementation of shelving v2 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26/* We define this here to remove any further warnings about the usage of 27 experimental functions in this file. */ 28#define SVN_EXPERIMENTAL 29 30#include "svn_client.h" 31#include "svn_wc.h" 32#include "svn_pools.h" 33#include "svn_dirent_uri.h" 34#include "svn_path.h" 35#include "svn_hash.h" 36#include "svn_utf.h" 37#include "svn_ctype.h" 38#include "svn_props.h" 39 40#include "client.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" 46 47 48static svn_error_t * 49shelf_name_encode(char **encoded_name_p, 50 const char *name, 51 apr_pool_t *result_pool) 52{ 53 char *encoded_name 54 = apr_palloc(result_pool, strlen(name) * 2 + 1); 55 char *out_pos = encoded_name; 56 57 if (name[0] == '\0') 58 return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL, 59 _("Shelf name cannot be the empty string")); 60 61 while (*name) 62 { 63 apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++)); 64 out_pos += 2; 65 } 66 *encoded_name_p = encoded_name; 67 return SVN_NO_ERROR; 68} 69 70static svn_error_t * 71shelf_name_decode(char **decoded_name_p, 72 const char *codename, 73 apr_pool_t *result_pool) 74{ 75 svn_stringbuf_t *sb 76 = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool); 77 const char *input = codename; 78 79 while (*input) 80 { 81 int c; 82 int nchars; 83 int nitems = sscanf(input, "%02x%n", &c, &nchars); 84 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); 89 input += 2; 90 } 91 *decoded_name_p = sb->data; 92 return SVN_NO_ERROR; 93} 94 95/* Set *NAME to the shelf name from FILENAME, if FILENAME names a '.current' 96 * file, else to NULL. */ 97static svn_error_t * 98shelf_name_from_filename(char **name, 99 const char *filename, 100 apr_pool_t *result_pool) 101{ 102 size_t len = strlen(filename); 103 static const char suffix[] = ".current"; 104 int suffix_len = sizeof(suffix) - 1; 105 106 if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0) 107 { 108 char *codename = apr_pstrndup(result_pool, filename, len - suffix_len); 109 SVN_ERR(shelf_name_decode(name, codename, result_pool)); 110 } 111 else 112 { 113 *name = NULL; 114 } 115 return SVN_NO_ERROR; 116} 117 118/* Set *DIR to the shelf storage directory inside the WC's administrative 119 * area. Ensure the directory exists. */ 120static svn_error_t * 121get_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) 126{ 127 char *experimental_abspath; 128 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); 133 134 /* Ensure the directory exists. (Other versions of svn don't create it.) */ 135 SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool)); 136 137 return SVN_NO_ERROR; 138} 139 140/* Set *ABSPATH to the abspath of the file storage dir for SHELF 141 * version VERSION, no matter whether it exists. 142 */ 143static svn_error_t * 144shelf_version_files_dir_abspath(const char **abspath, 145 svn_client__shelf2_t *shelf, 146 int version, 147 apr_pool_t *result_pool, 148 apr_pool_t *scratch_pool) 149{ 150 char *codename; 151 char *filename; 152 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); 156 return SVN_NO_ERROR; 157} 158 159/* Create a shelf-version object for a version that may or may not already 160 * exist on disk. 161 */ 162static svn_error_t * 163shelf_version_create(svn_client__shelf2_version_t **new_version_p, 164 svn_client__shelf2_t *shelf, 165 int version_number, 166 apr_pool_t *result_pool) 167{ 168 svn_client__shelf2_version_t *shelf_version 169 = apr_pcalloc(result_pool, sizeof(*shelf_version)); 170 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; 177 return SVN_NO_ERROR; 178} 179 180/* Set *ABSPATH to the abspath of the metadata file for SHELF_VERSION 181 * node at RELPATH, no matter whether it exists. 182 */ 183static svn_error_t * 184get_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) 189{ 190 wc_relpath = apr_psprintf(scratch_pool, "%s.meta", wc_relpath); 191 *abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, 192 result_pool); 193 return SVN_NO_ERROR; 194} 195 196/* Set *ABSPATH to the abspath of the base text file for SHELF_VERSION 197 * node at RELPATH, no matter whether it exists. 198 */ 199static svn_error_t * 200get_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) 205{ 206 wc_relpath = apr_psprintf(scratch_pool, "%s.base", wc_relpath); 207 *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, 208 result_pool); 209 return SVN_NO_ERROR; 210} 211 212/* Set *ABSPATH to the abspath of the working text file for SHELF_VERSION 213 * node at RELPATH, no matter whether it exists. 214 */ 215static svn_error_t * 216get_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) 221{ 222 wc_relpath = apr_psprintf(scratch_pool, "%s.work", wc_relpath); 223 *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, 224 result_pool); 225 return SVN_NO_ERROR; 226} 227 228/* Set *ABSPATH to the abspath of the base props file for SHELF_VERSION 229 * node at RELPATH, no matter whether it exists. 230 */ 231static svn_error_t * 232get_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) 237{ 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, 240 result_pool); 241 return SVN_NO_ERROR; 242} 243 244/* Set *ABSPATH to the abspath of the working props file for SHELF_VERSION 245 * node at RELPATH, no matter whether it exists. 246 */ 247static svn_error_t * 248get_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) 253{ 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, 256 result_pool); 257 return SVN_NO_ERROR; 258} 259 260/* Delete the storage for SHELF:VERSION. */ 261static svn_error_t * 262shelf_version_delete(svn_client__shelf2_t *shelf, 263 int version, 264 apr_pool_t *scratch_pool) 265{ 266 const char *files_dir_abspath; 267 268 SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath, 269 shelf, version, 270 scratch_pool, scratch_pool)); 271 SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/, 272 NULL, NULL, /*cancel*/ 273 scratch_pool)); 274 return SVN_NO_ERROR; 275} 276 277/* */ 278static svn_error_t * 279get_log_abspath(char **log_abspath, 280 svn_client__shelf2_t *shelf, 281 apr_pool_t *result_pool, 282 apr_pool_t *scratch_pool) 283{ 284 char *codename; 285 const char *filename; 286 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); 290 return SVN_NO_ERROR; 291} 292 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 295 * is not an error. 296 */ 297static svn_error_t * 298shelf_read_revprops(svn_client__shelf2_t *shelf, 299 apr_pool_t *scratch_pool) 300{ 301 char *log_abspath; 302 svn_error_t *err; 303 svn_stream_t *stream; 304 305 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); 306 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)) 311 { 312 svn_error_clear(err); 313 return SVN_NO_ERROR; 314 } 315 else 316 SVN_ERR(err); 317 SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool)); 318 SVN_ERR(svn_stream_close(stream)); 319 return SVN_NO_ERROR; 320} 321 322/* Write SHELF's revprops to its file storage. 323 */ 324static svn_error_t * 325shelf_write_revprops(svn_client__shelf2_t *shelf, 326 apr_pool_t *scratch_pool) 327{ 328 char *log_abspath; 329 apr_file_t *file; 330 svn_stream_t *stream; 331 332 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); 333 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); 338 339 SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool)); 340 SVN_ERR(svn_stream_close(stream)); 341 return SVN_NO_ERROR; 342} 343 344svn_error_t * 345svn_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) 349{ 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)); 353 return SVN_NO_ERROR; 354} 355 356svn_error_t * 357svn_client__shelf2_revprop_set_all(svn_client__shelf2_t *shelf, 358 apr_hash_t *revprop_table, 359 apr_pool_t *scratch_pool) 360{ 361 if (revprop_table) 362 shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool); 363 else 364 shelf->revprops = apr_hash_make(shelf->pool); 365 366 SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); 367 return SVN_NO_ERROR; 368} 369 370svn_error_t * 371svn_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) 375{ 376 *prop_val = svn_hash_gets(shelf->revprops, prop_name); 377 return SVN_NO_ERROR; 378} 379 380svn_error_t * 381svn_client__shelf2_revprop_list(apr_hash_t **props, 382 svn_client__shelf2_t *shelf, 383 apr_pool_t *result_pool) 384{ 385 *props = shelf->revprops; 386 return SVN_NO_ERROR; 387} 388 389/* */ 390static svn_error_t * 391get_current_abspath(char **current_abspath, 392 svn_client__shelf2_t *shelf, 393 apr_pool_t *result_pool) 394{ 395 char *codename; 396 char *filename; 397 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); 401 return SVN_NO_ERROR; 402} 403 404/* Read SHELF->max_version from its storage (the '.current' file). 405 * Set SHELF->max_version to -1 if that file does not exist. 406 */ 407static svn_error_t * 408shelf_read_current(svn_client__shelf2_t *shelf, 409 apr_pool_t *scratch_pool) 410{ 411 char *current_abspath; 412 svn_error_t *err; 413 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); 417 if (err) 418 { 419 shelf->max_version = -1; 420 svn_error_clear(err); 421 return SVN_NO_ERROR; 422 } 423 return SVN_NO_ERROR; 424} 425 426/* */ 427static svn_error_t * 428shelf_write_current(svn_client__shelf2_t *shelf, 429 apr_pool_t *scratch_pool) 430{ 431 char *current_abspath; 432 433 SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool)); 434 SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version, 435 scratch_pool)); 436 return SVN_NO_ERROR; 437} 438 439/*-------------------------------------------------------------------------*/ 440/* Status Reporting */ 441 442/* Create a status struct with all fields initialized to valid values 443 * representing 'uninteresting' or 'unknown' status. 444 */ 445static svn_wc_status3_t * 446status_create(apr_pool_t *result_pool) 447{ 448 svn_wc_status3_t *s = apr_pcalloc(result_pool, sizeof(*s)); 449 450 s->filesize = SVN_INVALID_FILESIZE; 451 s->versioned = TRUE; 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; 461 return s; 462} 463 464/* Convert from svn_node_kind_t to a single character representation. */ 465static char 466kind_to_char(svn_node_kind_t kind) 467{ 468 return (kind == svn_node_dir ? 'd' 469 : kind == svn_node_file ? 'f' 470 : kind == svn_node_symlink ? 'l' 471 : '?'); 472} 473 474/* Convert to svn_node_kind_t from a single character representation. */ 475static svn_node_kind_t 476char_to_kind(char kind) 477{ 478 return (kind == 'd' ? svn_node_dir 479 : kind == 'f' ? svn_node_file 480 : kind == 'l' ? svn_node_symlink 481 : svn_node_unknown); 482} 483 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().) */ 487static char 488status_to_char(enum svn_wc_status_kind status) 489{ 490 switch (status) 491 { 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 ':'; 506 default: return '*'; 507 } 508} 509 510static enum svn_wc_status_kind 511char_to_status(char status) 512{ 513 switch (status) 514 { 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; 530 } 531} 532 533/* Write a serial representation of (some fields of) STATUS to STREAM. 534 */ 535static svn_error_t * 536wc_status_serialize(svn_stream_t *stream, 537 const svn_wc_status3_t *status, 538 apr_pool_t *scratch_pool) 539{ 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), 545 status->revision)); 546 return SVN_NO_ERROR; 547} 548 549/* Read a serial representation of (some fields of) STATUS from STREAM. 550 */ 551static svn_error_t * 552wc_status_unserialize(svn_wc_status3_t *status, 553 svn_stream_t *stream, 554 apr_pool_t *result_pool) 555{ 556 svn_stringbuf_t *sb; 557 char *string; 558 559 SVN_ERR(svn_stringbuf_from_stream(&sb, stream, 100, result_pool)); 560 string = sb->data; 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); 566 return SVN_NO_ERROR; 567} 568 569/* Write status to shelf storage. 570 */ 571static svn_error_t * 572status_write(svn_client__shelf2_version_t *shelf_version, 573 const char *relpath, 574 const svn_wc_status3_t *status, 575 apr_pool_t *scratch_pool) 576{ 577 char *file_abspath; 578 svn_stream_t *stream; 579 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)); 586 return SVN_NO_ERROR; 587} 588 589/* Read status from shelf storage. 590 */ 591static svn_error_t * 592status_read(svn_wc_status3_t **status, 593 svn_client__shelf2_version_t *shelf_version, 594 const char *relpath, 595 apr_pool_t *result_pool, 596 apr_pool_t *scratch_pool) 597{ 598 svn_wc_status3_t *s = status_create(result_pool); 599 char *file_abspath; 600 svn_stream_t *stream; 601 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)); 608 609 s->changelist = apr_psprintf(result_pool, "svn:shelf:%s", 610 shelf_version->shelf->name); 611 *status = s; 612 return SVN_NO_ERROR; 613} 614 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. 618 */ 619typedef svn_error_t *(*shelf_status_visitor_t)(void *baton, 620 const char *relpath, 621 svn_wc_status3_t *status, 622 apr_pool_t *scratch_pool); 623 624/* Baton for shelved_files_walk_visitor(). */ 625struct shelf_status_baton_t 626{ 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; 631 void *walk_baton; 632}; 633 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. */ 637static svn_error_t * 638shelf_status_visitor(void *baton, 639 const char *abspath, 640 const apr_finfo_t *finfo, 641 apr_pool_t *scratch_pool) 642{ 643 struct shelf_status_baton_t *b = baton; 644 const char *relpath; 645 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)) 649 { 650 svn_wc_status3_t *s; 651 652 relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5); 653 if (!svn_relpath_skip_ancestor(b->top_relpath, relpath)) 654 return SVN_NO_ERROR; 655 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)); 659 } 660 return SVN_NO_ERROR; 661} 662 663/* Report the shelved status of the path SHELF_VERSION:WC_RELPATH 664 * via WALK_FUNC(WALK_BATON, ...). 665 */ 666static svn_error_t * 667shelf_status_visit_path(svn_client__shelf2_version_t *shelf_version, 668 const char *wc_relpath, 669 shelf_status_visitor_t walk_func, 670 void *walk_baton, 671 apr_pool_t *scratch_pool) 672{ 673 struct shelf_status_baton_t baton; 674 char *abspath; 675 apr_finfo_t finfo; 676 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)); 686 return SVN_NO_ERROR; 687} 688 689/* Report the shelved status of all the shelved paths in SHELF_VERSION 690 * via WALK_FUNC(WALK_BATON, ...). 691 */ 692static svn_error_t * 693shelf_status_walk(svn_client__shelf2_version_t *shelf_version, 694 const char *wc_relpath, 695 shelf_status_visitor_t walk_func, 696 void *walk_baton, 697 apr_pool_t *scratch_pool) 698{ 699 struct shelf_status_baton_t baton; 700 svn_error_t *err; 701 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, 709 scratch_pool); 710 if (err && APR_STATUS_IS_ENOENT(err->apr_err)) 711 svn_error_clear(err); 712 else 713 SVN_ERR(err); 714 715 return SVN_NO_ERROR; 716} 717 718typedef struct wc_status_baton_t 719{ 720 svn_client__shelf2_version_t *shelf_version; 721 svn_wc_status_func4_t walk_func; 722 void *walk_baton; 723} wc_status_baton_t; 724 725static svn_error_t * 726wc_status_visitor(void *baton, 727 const char *relpath, 728 svn_wc_status3_t *status, 729 apr_pool_t *scratch_pool) 730{ 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, 734 scratch_pool); 735 SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool)); 736 return SVN_NO_ERROR; 737} 738 739svn_error_t * 740svn_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, 743 void *walk_baton, 744 apr_pool_t *scratch_pool) 745{ 746 wc_status_baton_t baton; 747 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, 753 scratch_pool)); 754 return SVN_NO_ERROR; 755} 756 757/*-------------------------------------------------------------------------*/ 758/* Shelf Storage */ 759 760/* A baton for use with write_changes_visitor(). */ 761typedef 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; 772 773/* */ 774static svn_error_t * 775notify_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) 780{ 781 if (wb->was_shelved_func) 782 { 783 svn_client_status_t *cst; 784 785 SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath, 786 wc_status, 787 scratch_pool, scratch_pool)); 788 SVN_ERR(wb->was_shelved_func(wb->was_shelved_baton, 789 wc_relpath, cst, scratch_pool)); 790 } 791 792 wb->any_shelved = TRUE; 793 return SVN_NO_ERROR; 794} 795 796/* */ 797static svn_error_t * 798notify_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) 803{ 804 if (wb->was_not_shelved_func) 805 { 806 svn_client_status_t *cst; 807 808 SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath, 809 wc_status, 810 scratch_pool, scratch_pool)); 811 SVN_ERR(wb->was_not_shelved_func(wb->was_not_shelved_baton, 812 wc_relpath, cst, scratch_pool)); 813 } 814 815 return SVN_NO_ERROR; 816} 817 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). 820 */ 821static svn_error_t * 822read_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) 829{ 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)); 833 else 834 *base_props = NULL; 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)); 838 else 839 *work_props = NULL; 840 return SVN_NO_ERROR; 841} 842 843/* Write BASE_PROPS and WORK_PROPS to storage in SHELF_VERSION:WC_RELPATH. 844 */ 845static svn_error_t * 846write_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) 851{ 852 char *stored_props_abspath; 853 svn_stream_t *stream; 854 855 if (base_props) 856 { 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)); 864 } 865 866 if (work_props) 867 { 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)); 875 } 876 877 return SVN_NO_ERROR; 878} 879 880/* Read BASE_PROPS and WORK_PROPS from storage in SHELF_VERSION:WC_RELPATH. 881 */ 882static svn_error_t * 883read_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) 890{ 891 char *stored_props_abspath; 892 svn_stream_t *stream; 893 894 if (node_status != svn_wc_status_added) 895 { 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)); 904 } 905 else 906 *base_props = NULL; 907 908 if (node_status != svn_wc_status_deleted) 909 { 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)); 918 } 919 else 920 *work_props = NULL; 921 922 return SVN_NO_ERROR; 923} 924 925/* Store metadata for any node, and base and working files if it's a file. 926 * 927 * Copy the WC base and working files at FROM_WC_ABSPATH to the storage 928 * area in SHELF_VERSION. 929 */ 930static svn_error_t * 931store_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) 937{ 938 char *stored_abspath; 939 apr_hash_t *base_props, *work_props; 940 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, 945 scratch_pool), 946 scratch_pool)); 947 SVN_ERR(status_write(shelf_version, wc_relpath, 948 status, scratch_pool)); 949 950 /* properties */ 951 SVN_ERR(read_props_from_wc(&base_props, &work_props, 952 status->node_status, 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, 957 scratch_pool)); 958 959 /* file text */ 960 if (status->kind == svn_node_file) 961 { 962 svn_stream_t *wc_base_stream; 963 svn_node_kind_t work_kind; 964 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)); 969 if (wc_base_stream) 970 { 971 char *stored_base_abspath; 972 svn_stream_t *stored_base_stream; 973 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, 978 stored_base_abspath, 979 scratch_pool, scratch_pool)); 980 SVN_ERR(svn_stream_copy3(wc_base_stream, stored_base_stream, 981 NULL, NULL, scratch_pool)); 982 } 983 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) 987 { 988 SVN_ERR(svn_io_copy_file(from_wc_abspath, stored_abspath, 989 TRUE /*copy_perms*/, scratch_pool)); 990 } 991 } 992 return SVN_NO_ERROR; 993} 994 995/* An implementation of svn_wc_status_func4_t. */ 996static svn_error_t * 997write_changes_visitor(void *baton, 998 const char *local_abspath, 999 const svn_wc_status3_t *status, 1000 apr_pool_t *scratch_pool) 1001{ 1002 write_changes_baton_t *wb = baton; 1003 const char *wc_relpath = svn_dirent_skip_ancestor(wb->wc_root_abspath, 1004 local_abspath); 1005 1006 /* Catch any conflict, even a tree conflict on a path that has 1007 node-status 'unversioned'. */ 1008 if (status->conflicted) 1009 { 1010 SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, 1011 status, scratch_pool)); 1012 } 1013 else switch (status->node_status) 1014 { 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 1019 || status->copied) 1020 { 1021 SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, 1022 status, scratch_pool)); 1023 break; 1024 } 1025 /* fall through */ 1026 case svn_wc_status_modified: 1027 { 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)); 1033 break; 1034 } 1035 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)) 1041 { 1042 /* Incomplete, but local modifications */ 1043 SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, 1044 status, scratch_pool)); 1045 } 1046 break; 1047 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)); 1053 break; 1054 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: 1060 default: 1061 break; 1062 } 1063 1064 return SVN_NO_ERROR; 1065} 1066 1067/* A baton for use with changelist_filter_func(). */ 1068struct changelist_filter_baton_t { 1069 apr_hash_t *changelist_hash; 1070 svn_wc_status_func4_t status_func; 1071 void *status_baton; 1072}; 1073 1074/* Filter out paths that are not in the requested changelist(s). 1075 * Implements svn_wc_status_func4_t. */ 1076static svn_error_t * 1077changelist_filter_func(void *baton, 1078 const char *local_abspath, 1079 const svn_wc_status3_t *status, 1080 apr_pool_t *scratch_pool) 1081{ 1082 struct changelist_filter_baton_t *b = baton; 1083 1084 if (b->changelist_hash 1085 && (! status->changelist 1086 || ! svn_hash_gets(b->changelist_hash, status->changelist))) 1087 { 1088 return SVN_NO_ERROR; 1089 } 1090 1091 SVN_ERR(b->status_func(b->status_baton, local_abspath, status, 1092 scratch_pool)); 1093 return SVN_NO_ERROR; 1094} 1095 1096/* 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). 1099 * 1100 * Call STATUS_FUNC(STATUS_BATON, ...) for each visited path. 1101 * 1102 * PATHS are absolute, or relative to CWD. 1103 */ 1104static svn_error_t * 1105wc_walk_status_multi(const apr_array_header_t *paths, 1106 svn_depth_t depth, 1107 const apr_array_header_t *changelists, 1108 svn_wc_status_func4_t status_func, 1109 void *status_baton, 1110 svn_client_ctx_t *ctx, 1111 apr_pool_t *scratch_pool) 1112{ 1113 struct changelist_filter_baton_t cb = {0}; 1114 int i; 1115 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; 1121 1122 for (i = 0; i < paths->nelts; i++) 1123 { 1124 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1125 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)); 1130 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, 1137 scratch_pool)); 1138 } 1139 1140 return SVN_NO_ERROR; 1141} 1142 1143/** Write local changes to the shelf storage. 1144 * 1145 * @a paths, @a depth, @a changelists: The selection of local paths to diff. 1146 * 1147 * @a paths are relative to CWD (or absolute). 1148 */ 1149static svn_error_t * 1150shelf_write_changes(svn_boolean_t *any_shelved, 1151 svn_client__shelf2_version_t *shelf_version, 1152 const apr_array_header_t *paths, 1153 svn_depth_t depth, 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) 1163{ 1164 write_changes_baton_t wb = { 0 }; 1165 1166 wb.wc_root_abspath = wc_root_abspath; 1167 wb.shelf_version = shelf_version; 1168 wb.ctx = ctx; 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; 1175 1176 /* Walk the WC */ 1177 SVN_ERR(wc_walk_status_multi(paths, depth, changelists, 1178 write_changes_visitor, &wb, 1179 ctx, scratch_pool)); 1180 1181 *any_shelved = wb.any_shelved; 1182 return SVN_NO_ERROR; 1183} 1184 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. 1187 */ 1188static svn_error_t * 1189shelf_construct(svn_client__shelf2_t **shelf_p, 1190 const char *name, 1191 const char *local_abspath, 1192 svn_client_ctx_t *ctx, 1193 apr_pool_t *result_pool) 1194{ 1195 svn_client__shelf2_t *shelf = apr_palloc(result_pool, sizeof(*shelf)); 1196 char *shelves_dir; 1197 1198 SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath, 1199 local_abspath, ctx, 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; 1205 shelf->ctx = ctx; 1206 shelf->pool = result_pool; 1207 1208 shelf->name = apr_pstrdup(result_pool, name); 1209 shelf->revprops = apr_hash_make(result_pool); 1210 shelf->max_version = 0; 1211 1212 *shelf_p = shelf; 1213 return SVN_NO_ERROR; 1214} 1215 1216svn_error_t * 1217svn_client__shelf2_open_existing(svn_client__shelf2_t **shelf_p, 1218 const char *name, 1219 const char *local_abspath, 1220 svn_client_ctx_t *ctx, 1221 apr_pool_t *result_pool) 1222{ 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) 1228 { 1229 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1230 _("Shelf '%s' not found"), 1231 name); 1232 } 1233 return SVN_NO_ERROR; 1234} 1235 1236svn_error_t * 1237svn_client__shelf2_open_or_create(svn_client__shelf2_t **shelf_p, 1238 const char *name, 1239 const char *local_abspath, 1240 svn_client_ctx_t *ctx, 1241 apr_pool_t *result_pool) 1242{ 1243 svn_client__shelf2_t *shelf; 1244 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) 1250 { 1251 shelf->max_version = 0; 1252 SVN_ERR(shelf_write_current(shelf, result_pool)); 1253 } 1254 *shelf_p = shelf; 1255 return SVN_NO_ERROR; 1256} 1257 1258svn_error_t * 1259svn_client__shelf2_close(svn_client__shelf2_t *shelf, 1260 apr_pool_t *scratch_pool) 1261{ 1262 return SVN_NO_ERROR; 1263} 1264 1265svn_error_t * 1266svn_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) 1271{ 1272 svn_client__shelf2_t *shelf; 1273 int i; 1274 char *abspath; 1275 1276 SVN_ERR(svn_client__shelf2_open_existing(&shelf, name, 1277 local_abspath, ctx, scratch_pool)); 1278 1279 /* Remove the versions. */ 1280 for (i = shelf->max_version; i > 0; i--) 1281 { 1282 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); 1283 } 1284 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)); 1290 1291 SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); 1292 return SVN_NO_ERROR; 1293} 1294 1295/* Baton for paths_changed_visitor(). */ 1296struct paths_changed_walk_baton_t 1297{ 1298 apr_hash_t *paths_hash; 1299 svn_boolean_t as_abspath; 1300 const char *wc_root_abspath; 1301 apr_pool_t *pool; 1302}; 1303 1304/* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file. 1305 * Implements shelved_files_walk_func_t. */ 1306static svn_error_t * 1307paths_changed_visitor(void *baton, 1308 const char *relpath, 1309 svn_wc_status3_t *s, 1310 apr_pool_t *scratch_pool) 1311{ 1312 struct paths_changed_walk_baton_t *b = baton; 1313 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; 1319} 1320 1321/* Get the paths changed, relative to WC root or as abspaths, as a hash 1322 * and/or an array (in no particular order). 1323 */ 1324static svn_error_t * 1325shelf_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) 1331{ 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; 1335 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, 1342 scratch_pool)); 1343 1344 if (paths_hash_p) 1345 *paths_hash_p = paths_hash; 1346 if (paths_array_p) 1347 SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool)); 1348 1349 return SVN_NO_ERROR; 1350} 1351 1352svn_error_t * 1353svn_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) 1357{ 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; 1362} 1363 1364/* Send a notification */ 1365static svn_error_t * 1366send_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, 1372 void *notify_baton, 1373 apr_pool_t *scratch_pool) 1374{ 1375 if (notify_func) 1376 { 1377 svn_wc_notify_t *notify 1378 = svn_wc_create_notify(local_abspath, action, scratch_pool); 1379 1380 notify->kind = kind; 1381 notify->content_state = content_state; 1382 notify->prop_state = prop_state; 1383 notify_func(notify_baton, notify, scratch_pool); 1384 } 1385 1386 return SVN_NO_ERROR; 1387} 1388 1389/* Merge a shelved change into WC_ABSPATH. 1390 */ 1391static svn_error_t * 1392wc_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) 1399{ 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; 1405 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")); 1411 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)); 1415 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, 1426 NULL, NULL, 1427 ctx->cancel_func, ctx->cancel_baton, 1428 scratch_pool), 1429 ctx->wc_ctx, wc_abspath, 1430 FALSE /*lock_anchor*/, scratch_pool); 1431 1432 return SVN_NO_ERROR; 1433} 1434 1435/* Merge a shelved change (of properties) into the dir at WC_ABSPATH. 1436 */ 1437static svn_error_t * 1438wc_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) 1444{ 1445 apr_array_header_t *prop_changes; 1446 svn_wc_notify_state_t property_state; 1447 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, 1451 wc_abspath, 1452 NULL, NULL, /*left, right conflict-versions*/ 1453 left_props, prop_changes, 1454 FALSE /*dry_run*/, 1455 NULL, NULL, 1456 ctx->cancel_func, ctx->cancel_baton, 1457 scratch_pool), 1458 ctx->wc_ctx, wc_abspath, 1459 FALSE /*lock_anchor*/, scratch_pool); 1460 1461 return SVN_NO_ERROR; 1462} 1463 1464/* Apply a shelved "delete" to TO_WC_ABSPATH. 1465 */ 1466static svn_error_t * 1467wc_node_delete(const char *to_wc_abspath, 1468 svn_client_ctx_t *ctx, 1469 apr_pool_t *scratch_pool) 1470{ 1471 SVN_WC__CALL_WITH_WRITE_LOCK( 1472 svn_wc_delete4(ctx->wc_ctx, 1473 to_wc_abspath, 1474 FALSE /*keep_local*/, 1475 TRUE /*delete_unversioned_target*/, 1476 NULL, NULL, NULL, NULL, /*cancel, notify*/ 1477 scratch_pool), 1478 ctx->wc_ctx, to_wc_abspath, 1479 TRUE /*lock_anchor*/, scratch_pool); 1480 return SVN_NO_ERROR; 1481} 1482 1483/* Apply a shelved "add" to TO_WC_ABSPATH. 1484 * The node must already exist on disk, in a versioned parent dir. 1485 */ 1486static svn_error_t * 1487wc_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) 1491{ 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; 1503} 1504 1505/* Baton for apply_file_visitor(). */ 1506struct apply_files_baton_t 1507{ 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; 1512}; 1513 1514/* Copy the file RELPATH from shelf binary file storage to the WC. 1515 * 1516 * If it is not already versioned, schedule the file for addition. 1517 * 1518 * Make any missing parent directories. 1519 * 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. 1523 * 1524 * Implements shelved_files_walk_func_t. */ 1525static svn_error_t * 1526apply_file_visitor(void *baton, 1527 const char *relpath, 1528 svn_wc_status3_t *s, 1529 apr_pool_t *scratch_pool) 1530{ 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, 1536 scratch_pool); 1537 const char *to_dir_abspath = svn_dirent_dirname(to_wc_abspath, scratch_pool); 1538 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, 1546 s->node_status, 1547 b->shelf_version, relpath, 1548 scratch_pool, scratch_pool)); 1549 1550 if (b->test_only) 1551 { 1552 svn_wc_status3_t *status; 1553 1554 SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath, 1555 scratch_pool, scratch_pool)); 1556 switch (status->node_status) 1557 { 1558 case svn_wc_status_normal: 1559 case svn_wc_status_none: 1560 break; 1561 default: 1562 b->conflict = TRUE; 1563 } 1564 1565 return SVN_NO_ERROR; 1566 } 1567 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) 1571 { 1572 SVN_ERR(wc_node_delete(to_wc_abspath, b->ctx, scratch_pool)); 1573 if (s->node_status != svn_wc_status_replaced) 1574 { 1575 SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_delete, 1576 s->kind, 1577 svn_wc_notify_state_inapplicable, 1578 svn_wc_notify_state_inapplicable, 1579 b->ctx->notify_func2, b->ctx->notify_baton2, 1580 scratch_pool)); 1581 } 1582 } 1583 1584 /* If we can merge a file, do so. */ 1585 if (s->node_status == svn_wc_status_modified) 1586 { 1587 if (s->kind == svn_node_dir) 1588 { 1589 SVN_ERR(wc_dir_props_merge(to_wc_abspath, 1590 base_props, work_props, 1591 b->ctx, scratch_pool, scratch_pool)); 1592 } 1593 else if (s->kind == svn_node_file) 1594 { 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)); 1599 } 1600 SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_update, 1601 s->kind, 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, 1609 scratch_pool)); 1610 } 1611 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) 1615 { 1616 if (s->kind == svn_node_dir) 1617 { 1618 SVN_ERR(svn_io_make_dir_recursively(to_wc_abspath, scratch_pool)); 1619 } 1620 else if (s->kind == svn_node_file) 1621 { 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)); 1625 } 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, 1631 s->kind, 1632 svn_wc_notify_state_inapplicable, 1633 svn_wc_notify_state_inapplicable, 1634 b->ctx->notify_func2, b->ctx->notify_baton2, 1635 scratch_pool)); 1636 } 1637 1638 return SVN_NO_ERROR; 1639} 1640 1641/*-------------------------------------------------------------------------*/ 1642/* Diff */ 1643 1644/* */ 1645static svn_error_t * 1646file_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, 1654 void *dir_baton, 1655 apr_pool_t *scratch_pool) 1656{ 1657 void *fb; 1658 svn_boolean_t skip = FALSE; 1659 1660 SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath, 1661 left_source, right_source, 1662 NULL /*copyfrom*/, 1663 dir_baton, diff_processor, 1664 scratch_pool, scratch_pool)); 1665 if (!skip) 1666 { 1667 apr_hash_t *left_props, *right_props; 1668 apr_array_header_t *prop_changes; 1669 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, 1674 scratch_pool)); 1675 SVN_ERR(diff_processor->file_changed( 1676 relpath, 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)); 1682 } 1683 1684 return SVN_NO_ERROR; 1685} 1686 1687/* */ 1688static svn_error_t * 1689file_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, 1695 void *dir_baton, 1696 apr_pool_t *scratch_pool) 1697{ 1698 void *fb; 1699 svn_boolean_t skip = FALSE; 1700 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)); 1705 if (!skip) 1706 { 1707 apr_hash_t *left_props, *right_props; 1708 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, 1713 left_source, 1714 left_stored_abspath, 1715 left_props, 1716 fb, diff_processor, 1717 scratch_pool)); 1718 } 1719 1720 return SVN_NO_ERROR; 1721} 1722 1723/* */ 1724static svn_error_t * 1725file_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, 1731 void *dir_baton, 1732 apr_pool_t *scratch_pool) 1733{ 1734 void *fb; 1735 svn_boolean_t skip = FALSE; 1736 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)); 1741 if (!skip) 1742 { 1743 apr_hash_t *left_props, *right_props; 1744 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( 1749 relpath, 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)); 1754 } 1755 1756 return SVN_NO_ERROR; 1757} 1758 1759/* Baton for diff_visitor(). */ 1760struct diff_baton_t 1761{ 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; 1766}; 1767 1768/* Drive BATON->diff_processor. 1769 * Implements svn_io_walk_func_t. */ 1770static svn_error_t * 1771diff_visitor(void *baton, 1772 const char *abspath, 1773 const apr_finfo_t *finfo, 1774 apr_pool_t *scratch_pool) 1775{ 1776 struct diff_baton_t *b = baton; 1777 const char *relpath; 1778 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)) 1782 { 1783 svn_wc_status3_t *s; 1784 void *db = NULL; 1785 svn_diff_source_t *left_source; 1786 svn_diff_source_t *right_source; 1787 char *left_stored_abspath, *right_stored_abspath; 1788 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; 1792 1793 SVN_ERR(status_read(&s, b->shelf_version, relpath, 1794 scratch_pool, scratch_pool)); 1795 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)); 1804 1805 switch (s->node_status) 1806 { 1807 case svn_wc_status_modified: 1808 SVN_ERR(file_changed(b->shelf_version, relpath, s, 1809 b->diff_processor, 1810 left_source, right_source, 1811 left_stored_abspath, right_stored_abspath, 1812 db, scratch_pool)); 1813 break; 1814 case svn_wc_status_added: 1815 SVN_ERR(file_added(b->shelf_version, relpath, s, 1816 b->diff_processor, 1817 right_source, right_stored_abspath, 1818 db, scratch_pool)); 1819 break; 1820 case svn_wc_status_deleted: 1821 SVN_ERR(file_deleted(b->shelf_version, relpath, s, 1822 b->diff_processor, 1823 left_source, left_stored_abspath, 1824 db, scratch_pool)); 1825 break; 1826 case svn_wc_status_replaced: 1827 SVN_ERR(file_deleted(b->shelf_version, relpath, s, 1828 b->diff_processor, 1829 left_source, left_stored_abspath, 1830 db, scratch_pool)); 1831 SVN_ERR(file_added(b->shelf_version, relpath, s, 1832 b->diff_processor, 1833 right_source, right_stored_abspath, 1834 db, scratch_pool)); 1835 default: 1836 break; 1837 } 1838 } 1839 return SVN_NO_ERROR; 1840} 1841 1842svn_error_t * 1843svn_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) 1847{ 1848 struct apply_files_baton_t baton = {0}; 1849 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, 1856 scratch_pool)); 1857 *conflict_p = baton.conflict; 1858 1859 return SVN_NO_ERROR; 1860} 1861 1862svn_error_t * 1863svn_client__shelf2_apply(svn_client__shelf2_version_t *shelf_version, 1864 svn_boolean_t dry_run, 1865 apr_pool_t *scratch_pool) 1866{ 1867 struct apply_files_baton_t baton = {0}; 1868 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, 1873 scratch_pool)); 1874 1875 svn_io_sleep_for_timestamps(shelf_version->shelf->wc_root_abspath, 1876 scratch_pool); 1877 return SVN_NO_ERROR; 1878} 1879 1880svn_error_t * 1881svn_client__shelf2_unapply(svn_client__shelf2_version_t *shelf_version, 1882 svn_boolean_t dry_run, 1883 apr_pool_t *scratch_pool) 1884{ 1885 apr_array_header_t *targets; 1886 1887 SVN_ERR(shelf_paths_changed(NULL, &targets, shelf_version, 1888 TRUE /*as_abspath*/, 1889 scratch_pool, scratch_pool)); 1890 if (!dry_run) 1891 { 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)); 1898 } 1899 return SVN_NO_ERROR; 1900} 1901 1902svn_error_t * 1903svn_client__shelf2_delete_newer_versions(svn_client__shelf2_t *shelf, 1904 svn_client__shelf2_version_t *shelf_version, 1905 apr_pool_t *scratch_pool) 1906{ 1907 int previous_version = shelf_version ? shelf_version->version_number : 0; 1908 int i; 1909 1910 /* Delete any newer checkpoints */ 1911 for (i = shelf->max_version; i > previous_version; i--) 1912 { 1913 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); 1914 } 1915 1916 shelf->max_version = previous_version; 1917 SVN_ERR(shelf_write_current(shelf, scratch_pool)); 1918 return SVN_NO_ERROR; 1919} 1920 1921svn_error_t * 1922svn_client__shelf2_diff(svn_client__shelf2_version_t *shelf_version, 1923 const char *shelf_relpath, 1924 svn_depth_t depth, 1925 svn_boolean_t ignore_ancestry, 1926 const svn_diff_tree_processor_t *diff_processor, 1927 apr_pool_t *scratch_pool) 1928{ 1929 struct diff_baton_t baton; 1930 1931 if (shelf_version->version_number == 0) 1932 return SVN_NO_ERROR; 1933 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, 1940 scratch_pool)); 1941 1942 return SVN_NO_ERROR; 1943} 1944 1945svn_error_t * 1946svn_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, 1949 svn_depth_t depth, 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) 1956{ 1957 int next_version = shelf->max_version + 1; 1958 svn_client__shelf2_version_t *new_shelf_version; 1959 svn_boolean_t any_shelved; 1960 1961 SVN_ERR(shelf_version_create(&new_shelf_version, 1962 shelf, next_version, scratch_pool)); 1963 SVN_ERR(shelf_write_changes(&any_shelved, 1964 new_shelf_version, 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)); 1970 1971 if (any_shelved) 1972 { 1973 shelf->max_version = next_version; 1974 SVN_ERR(shelf_write_current(shelf, scratch_pool)); 1975 1976 if (new_version_p) 1977 SVN_ERR(svn_client__shelf2_version_open(new_version_p, shelf, next_version, 1978 scratch_pool, scratch_pool)); 1979 } 1980 else 1981 { 1982 if (new_version_p) 1983 *new_version_p = NULL; 1984 } 1985 return SVN_NO_ERROR; 1986} 1987 1988svn_error_t * 1989svn_client__shelf2_get_log_message(char **log_message, 1990 svn_client__shelf2_t *shelf, 1991 apr_pool_t *result_pool) 1992{ 1993 svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG); 1994 1995 if (propval) 1996 *log_message = apr_pstrdup(result_pool, propval->data); 1997 else 1998 *log_message = NULL; 1999 return SVN_NO_ERROR; 2000} 2001 2002svn_error_t * 2003svn_client__shelf2_set_log_message(svn_client__shelf2_t *shelf, 2004 const char *message, 2005 apr_pool_t *scratch_pool) 2006{ 2007 svn_string_t *propval 2008 = message ? svn_string_create(message, shelf->pool) : NULL; 2009 2010 SVN_ERR(svn_client__shelf2_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval, 2011 scratch_pool)); 2012 return SVN_NO_ERROR; 2013} 2014 2015svn_error_t * 2016svn_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) 2021{ 2022 const char *wc_root_abspath; 2023 char *shelves_dir; 2024 apr_hash_t *dirents; 2025 apr_hash_index_t *hi; 2026 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)); 2033 2034 *shelf_infos = apr_hash_make(result_pool); 2035 2036 /* Remove non-shelves */ 2037 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) 2038 { 2039 const char *filename = apr_hash_this_key(hi); 2040 svn_io_dirent2_t *dirent = apr_hash_this_val(hi); 2041 char *name = NULL; 2042 2043 svn_error_clear(shelf_name_from_filename(&name, filename, result_pool)); 2044 if (name && dirent->kind == svn_node_file) 2045 { 2046 svn_client__shelf2_info_t *info 2047 = apr_palloc(result_pool, sizeof(*info)); 2048 2049 info->mtime = dirent->mtime; 2050 svn_hash_sets(*shelf_infos, name, info); 2051 } 2052 } 2053 2054 return SVN_NO_ERROR; 2055} 2056 2057svn_error_t * 2058svn_client__shelf2_version_open(svn_client__shelf2_version_t **shelf_version_p, 2059 svn_client__shelf2_t *shelf, 2060 int version_number, 2061 apr_pool_t *result_pool, 2062 apr_pool_t *scratch_pool) 2063{ 2064 svn_client__shelf2_version_t *shelf_version; 2065 const svn_io_dirent2_t *dirent; 2066 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) 2075 { 2076 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 2077 _("Shelf '%s' version %d not found"), 2078 shelf->name, version_number); 2079 } 2080 shelf_version->mtime = dirent->mtime; 2081 *shelf_version_p = shelf_version; 2082 return SVN_NO_ERROR; 2083} 2084 2085svn_error_t * 2086svn_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) 2090{ 2091 if (shelf->max_version == 0) 2092 { 2093 *shelf_version_p = NULL; 2094 return SVN_NO_ERROR; 2095 } 2096 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; 2101} 2102 2103svn_error_t * 2104svn_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) 2108{ 2109 int i; 2110 2111 *versions_p = apr_array_make(result_pool, shelf->max_version - 1, 2112 sizeof(svn_client__shelf2_version_t *)); 2113 2114 for (i = 1; i <= shelf->max_version; i++) 2115 { 2116 svn_client__shelf2_version_t *shelf_version; 2117 2118 SVN_ERR(svn_client__shelf2_version_open(&shelf_version, 2119 shelf, i, 2120 result_pool, scratch_pool)); 2121 APR_ARRAY_PUSH(*versions_p, svn_client__shelf2_version_t *) = shelf_version; 2122 } 2123 return SVN_NO_ERROR; 2124} 2125