1 /*
2 * shelf.c: implementation of shelving
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_shelf.h"
42 #include "private/svn_client_private.h"
43 #include "private/svn_wc_private.h"
44 #include "private/svn_sorts_private.h"
45 #include "svn_private_config.h"
46
47
48 static svn_error_t *
shelf_name_encode(char ** encoded_name_p,const char * name,apr_pool_t * result_pool)49 shelf_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
70 static svn_error_t *
shelf_name_decode(char ** decoded_name_p,const char * codename,apr_pool_t * result_pool)71 shelf_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. */
97 static svn_error_t *
shelf_name_from_filename(char ** name,const char * filename,apr_pool_t * result_pool)98 shelf_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 size_t 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. */
120 static svn_error_t *
get_shelves_dir(char ** dir,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
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/v3", 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 */
143 static svn_error_t *
shelf_version_files_dir_abspath(const char ** abspath,svn_client__shelf_t * shelf,int version,apr_pool_t * result_pool,apr_pool_t * scratch_pool)144 shelf_version_files_dir_abspath(const char **abspath,
145 svn_client__shelf_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.wc", 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 */
162 static svn_error_t *
shelf_version_create(svn_client__shelf_version_t ** new_version_p,svn_client__shelf_t * shelf,int version_number,apr_pool_t * result_pool)163 shelf_version_create(svn_client__shelf_version_t **new_version_p,
164 svn_client__shelf_t *shelf,
165 int version_number,
166 apr_pool_t *result_pool)
167 {
168 svn_client__shelf_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 /* Delete the storage for SHELF:VERSION. */
181 static svn_error_t *
shelf_version_delete(svn_client__shelf_t * shelf,int version,apr_pool_t * scratch_pool)182 shelf_version_delete(svn_client__shelf_t *shelf,
183 int version,
184 apr_pool_t *scratch_pool)
185 {
186 const char *files_dir_abspath;
187
188 SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath,
189 shelf, version,
190 scratch_pool, scratch_pool));
191 SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/,
192 NULL, NULL, /*cancel*/
193 scratch_pool));
194 return SVN_NO_ERROR;
195 }
196
197 /* */
198 static svn_error_t *
get_log_abspath(char ** log_abspath,svn_client__shelf_t * shelf,apr_pool_t * result_pool,apr_pool_t * scratch_pool)199 get_log_abspath(char **log_abspath,
200 svn_client__shelf_t *shelf,
201 apr_pool_t *result_pool,
202 apr_pool_t *scratch_pool)
203 {
204 char *codename;
205 const char *filename;
206
207 SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
208 filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL);
209 *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
210 return SVN_NO_ERROR;
211 }
212
213 /* Set SHELF->revprops by reading from its storage (the '.log' file).
214 * Set SHELF->revprops to empty if the storage file does not exist; this
215 * is not an error.
216 */
217 static svn_error_t *
shelf_read_revprops(svn_client__shelf_t * shelf,apr_pool_t * scratch_pool)218 shelf_read_revprops(svn_client__shelf_t *shelf,
219 apr_pool_t *scratch_pool)
220 {
221 char *log_abspath;
222 svn_error_t *err;
223 svn_stream_t *stream;
224
225 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
226
227 shelf->revprops = apr_hash_make(shelf->pool);
228 err = svn_stream_open_readonly(&stream, log_abspath,
229 scratch_pool, scratch_pool);
230 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
231 {
232 svn_error_clear(err);
233 return SVN_NO_ERROR;
234 }
235 else
236 SVN_ERR(err);
237 SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool));
238 SVN_ERR(svn_stream_close(stream));
239 return SVN_NO_ERROR;
240 }
241
242 /* Write SHELF's revprops to its file storage.
243 */
244 static svn_error_t *
shelf_write_revprops(svn_client__shelf_t * shelf,apr_pool_t * scratch_pool)245 shelf_write_revprops(svn_client__shelf_t *shelf,
246 apr_pool_t *scratch_pool)
247 {
248 char *log_abspath;
249 apr_file_t *file;
250 svn_stream_t *stream;
251
252 SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
253
254 SVN_ERR(svn_io_file_open(&file, log_abspath,
255 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE,
256 APR_FPROT_OS_DEFAULT, scratch_pool));
257 stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
258
259 SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool));
260 SVN_ERR(svn_stream_close(stream));
261 return SVN_NO_ERROR;
262 }
263
264 svn_error_t *
svn_client__shelf_revprop_set(svn_client__shelf_t * shelf,const char * prop_name,const svn_string_t * prop_val,apr_pool_t * scratch_pool)265 svn_client__shelf_revprop_set(svn_client__shelf_t *shelf,
266 const char *prop_name,
267 const svn_string_t *prop_val,
268 apr_pool_t *scratch_pool)
269 {
270 svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name),
271 svn_string_dup(prop_val, shelf->pool));
272 SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
273 return SVN_NO_ERROR;
274 }
275
276 svn_error_t *
svn_client__shelf_revprop_set_all(svn_client__shelf_t * shelf,apr_hash_t * revprop_table,apr_pool_t * scratch_pool)277 svn_client__shelf_revprop_set_all(svn_client__shelf_t *shelf,
278 apr_hash_t *revprop_table,
279 apr_pool_t *scratch_pool)
280 {
281 if (revprop_table)
282 shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool);
283 else
284 shelf->revprops = apr_hash_make(shelf->pool);
285
286 SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
287 return SVN_NO_ERROR;
288 }
289
290 svn_error_t *
svn_client__shelf_revprop_get(svn_string_t ** prop_val,svn_client__shelf_t * shelf,const char * prop_name,apr_pool_t * result_pool)291 svn_client__shelf_revprop_get(svn_string_t **prop_val,
292 svn_client__shelf_t *shelf,
293 const char *prop_name,
294 apr_pool_t *result_pool)
295 {
296 *prop_val = svn_hash_gets(shelf->revprops, prop_name);
297 return SVN_NO_ERROR;
298 }
299
300 svn_error_t *
svn_client__shelf_revprop_list(apr_hash_t ** props,svn_client__shelf_t * shelf,apr_pool_t * result_pool)301 svn_client__shelf_revprop_list(apr_hash_t **props,
302 svn_client__shelf_t *shelf,
303 apr_pool_t *result_pool)
304 {
305 *props = shelf->revprops;
306 return SVN_NO_ERROR;
307 }
308
309 /* */
310 static svn_error_t *
get_current_abspath(char ** current_abspath,svn_client__shelf_t * shelf,apr_pool_t * result_pool)311 get_current_abspath(char **current_abspath,
312 svn_client__shelf_t *shelf,
313 apr_pool_t *result_pool)
314 {
315 char *codename;
316 char *filename;
317
318 SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
319 filename = apr_psprintf(result_pool, "%s.current", codename);
320 *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
321 return SVN_NO_ERROR;
322 }
323
324 /* Read SHELF->max_version from its storage (the '.current' file).
325 * Set SHELF->max_version to -1 if that file does not exist.
326 */
327 static svn_error_t *
shelf_read_current(svn_client__shelf_t * shelf,apr_pool_t * scratch_pool)328 shelf_read_current(svn_client__shelf_t *shelf,
329 apr_pool_t *scratch_pool)
330 {
331 char *current_abspath;
332 svn_error_t *err;
333
334 SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool));
335 err = svn_io_read_version_file(&shelf->max_version,
336 current_abspath, scratch_pool);
337 if (err)
338 {
339 shelf->max_version = -1;
340 svn_error_clear(err);
341 return SVN_NO_ERROR;
342 }
343 return SVN_NO_ERROR;
344 }
345
346 /* */
347 static svn_error_t *
shelf_write_current(svn_client__shelf_t * shelf,apr_pool_t * scratch_pool)348 shelf_write_current(svn_client__shelf_t *shelf,
349 apr_pool_t *scratch_pool)
350 {
351 char *current_abspath;
352
353 SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool));
354 SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version,
355 scratch_pool));
356 return SVN_NO_ERROR;
357 }
358
359 /*-------------------------------------------------------------------------*/
360 /* Status Reporting */
361
362 /* Adjust a status STATUS_IN obtained from the shelf storage WC, to add
363 * shelf-related metadata:
364 * - changelist: 'svn:shelf:SHELFNAME'
365 */
366 static svn_error_t *
status_augment(svn_wc_status3_t ** status_p,const svn_wc_status3_t * status_in,svn_client__shelf_version_t * shelf_version,apr_pool_t * result_pool)367 status_augment(svn_wc_status3_t **status_p,
368 const svn_wc_status3_t *status_in,
369 svn_client__shelf_version_t *shelf_version,
370 apr_pool_t *result_pool)
371 {
372 *status_p = svn_wc_dup_status3(status_in, result_pool);
373 (*status_p)->changelist = apr_psprintf(result_pool, "svn:shelf:%s",
374 shelf_version->shelf->name);
375 return SVN_NO_ERROR;
376 }
377
378 /* Read status from shelf storage.
379 */
380 static svn_error_t *
status_read(svn_wc_status3_t ** status,svn_client__shelf_version_t * shelf_version,const char * relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)381 status_read(svn_wc_status3_t **status,
382 svn_client__shelf_version_t *shelf_version,
383 const char *relpath,
384 apr_pool_t *result_pool,
385 apr_pool_t *scratch_pool)
386 {
387 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
388 char *abspath
389 = svn_dirent_join(shelf_version->files_dir_abspath, relpath,
390 scratch_pool);
391
392 SVN_ERR(svn_wc_status3(status, ctx->wc_ctx, abspath,
393 result_pool, scratch_pool));
394 SVN_ERR(status_augment(status, *status, shelf_version, result_pool));
395 return SVN_NO_ERROR;
396 }
397
398 /* A visitor function type for use with shelf_status_walk().
399 * The same as svn_wc_status_func4_t except relpath instead of abspath.
400 */
401 typedef svn_error_t *(*shelf_status_visitor_t)(void *baton,
402 const char *relpath,
403 const svn_wc_status3_t *status,
404 apr_pool_t *scratch_pool);
405
406 /* Baton for shelved_files_walk_visitor(). */
407 struct shelf_status_baton_t
408 {
409 svn_client__shelf_version_t *shelf_version;
410 shelf_status_visitor_t walk_func;
411 void *walk_baton;
412 };
413
414 /* Convert a svn_wc_status_func4_t callback invocation to call a
415 * shelf_status_visitor_t callback.
416 *
417 * Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved
418 * storage path ABSPATH, converting ABSPATH to a WC-relative path, and
419 * augmenting the STATUS.
420 *
421 * The opposite of wc_status_visitor().
422 *
423 * Implements svn_wc_status_func4_t. */
424 static svn_error_t *
shelf_status_visitor(void * baton,const char * abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)425 shelf_status_visitor(void *baton,
426 const char *abspath,
427 const svn_wc_status3_t *status,
428 apr_pool_t *scratch_pool)
429 {
430 struct shelf_status_baton_t *b = baton;
431 const char *relpath;
432 svn_wc_status3_t *new_status;
433
434 relpath = svn_dirent_skip_ancestor(b->shelf_version->files_dir_abspath,
435 abspath);
436 SVN_ERR(status_augment(&new_status, status, b->shelf_version, scratch_pool));
437 SVN_ERR(b->walk_func(b->walk_baton, relpath, new_status, scratch_pool));
438 return SVN_NO_ERROR;
439 }
440
441 /* Report the shelved status of the path SHELF_VERSION:WC_RELPATH
442 * via WALK_FUNC(WALK_BATON, ...).
443 */
444 static svn_error_t *
shelf_status_visit_path(svn_client__shelf_version_t * shelf_version,const char * wc_relpath,shelf_status_visitor_t walk_func,void * walk_baton,apr_pool_t * scratch_pool)445 shelf_status_visit_path(svn_client__shelf_version_t *shelf_version,
446 const char *wc_relpath,
447 shelf_status_visitor_t walk_func,
448 void *walk_baton,
449 apr_pool_t *scratch_pool)
450 {
451 svn_wc_status3_t *status;
452
453 SVN_ERR(status_read(&status, shelf_version, wc_relpath,
454 scratch_pool, scratch_pool));
455 SVN_ERR(walk_func(walk_baton, wc_relpath, status, scratch_pool));
456 return SVN_NO_ERROR;
457 }
458
459 /* Report the shelved status of all the shelved paths in SHELF_VERSION
460 * at and under WC_RELPATH, via WALK_FUNC(WALK_BATON, ...).
461 */
462 static svn_error_t *
shelf_status_walk(svn_client__shelf_version_t * shelf_version,const char * wc_relpath,shelf_status_visitor_t walk_func,void * walk_baton,apr_pool_t * scratch_pool)463 shelf_status_walk(svn_client__shelf_version_t *shelf_version,
464 const char *wc_relpath,
465 shelf_status_visitor_t walk_func,
466 void *walk_baton,
467 apr_pool_t *scratch_pool)
468 {
469 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
470 char *walk_root_abspath
471 = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
472 scratch_pool);
473 struct shelf_status_baton_t baton;
474 svn_error_t *err;
475
476 baton.shelf_version = shelf_version;
477 baton.walk_func = walk_func;
478 baton.walk_baton = walk_baton;
479 err = svn_wc_walk_status(ctx->wc_ctx, walk_root_abspath,
480 svn_depth_infinity,
481 FALSE /*get_all*/,
482 TRUE /*no_ignore*/,
483 FALSE /*ignore_text_mods*/,
484 NULL /*ignore_patterns: use the defaults*/,
485 shelf_status_visitor, &baton,
486 NULL, NULL, /*cancellation*/
487 scratch_pool);
488 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
489 svn_error_clear(err);
490 else
491 SVN_ERR(err);
492
493 return SVN_NO_ERROR;
494 }
495
496 /* Baton for wc_status_visitor(). */
497 typedef struct wc_status_baton_t
498 {
499 svn_client__shelf_version_t *shelf_version;
500 svn_wc_status_func4_t walk_func;
501 void *walk_baton;
502 } wc_status_baton_t;
503
504 /* Convert a shelf_status_visitor_t callback invocation to call a
505 * svn_wc_status_func4_t callback.
506 *
507 * Call BATON->walk_func(BATON->walk_baton, abspath, ...) for the WC-
508 * relative path RELPATH, converting RELPATH to an abspath in the user's WC.
509 *
510 * The opposite of shelf_status_visitor().
511 *
512 * Implements shelf_status_visitor_t. */
513 static svn_error_t *
wc_status_visitor(void * baton,const char * relpath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)514 wc_status_visitor(void *baton,
515 const char *relpath,
516 const svn_wc_status3_t *status,
517 apr_pool_t *scratch_pool)
518 {
519 struct wc_status_baton_t *b = baton;
520 svn_client__shelf_t *shelf = b->shelf_version->shelf;
521 const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath,
522 scratch_pool);
523 SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool));
524 return SVN_NO_ERROR;
525 }
526
527 svn_error_t *
svn_client__shelf_version_status_walk(svn_client__shelf_version_t * shelf_version,const char * wc_relpath,svn_wc_status_func4_t walk_func,void * walk_baton,apr_pool_t * scratch_pool)528 svn_client__shelf_version_status_walk(svn_client__shelf_version_t *shelf_version,
529 const char *wc_relpath,
530 svn_wc_status_func4_t walk_func,
531 void *walk_baton,
532 apr_pool_t *scratch_pool)
533 {
534 wc_status_baton_t baton;
535
536 baton.shelf_version = shelf_version;
537 baton.walk_func = walk_func;
538 baton.walk_baton = walk_baton;
539 SVN_ERR(shelf_status_walk(shelf_version, wc_relpath,
540 wc_status_visitor, &baton,
541 scratch_pool));
542 return SVN_NO_ERROR;
543 }
544
545 /*-------------------------------------------------------------------------*/
546 /* Shelf Storage */
547
548 /* Construct a shelf object representing an empty shelf: no versions,
549 * no revprops, no looking to see if such a shelf exists on disk.
550 */
551 static svn_error_t *
shelf_construct(svn_client__shelf_t ** shelf_p,const char * name,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool)552 shelf_construct(svn_client__shelf_t **shelf_p,
553 const char *name,
554 const char *local_abspath,
555 svn_client_ctx_t *ctx,
556 apr_pool_t *result_pool)
557 {
558 svn_client__shelf_t *shelf = apr_palloc(result_pool, sizeof(*shelf));
559 char *shelves_dir;
560
561 SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath,
562 local_abspath, ctx,
563 result_pool, result_pool));
564 SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
565 result_pool, result_pool));
566 shelf->shelves_dir = shelves_dir;
567 shelf->ctx = ctx;
568 shelf->pool = result_pool;
569
570 shelf->name = apr_pstrdup(result_pool, name);
571 shelf->revprops = apr_hash_make(result_pool);
572 shelf->max_version = 0;
573
574 *shelf_p = shelf;
575 return SVN_NO_ERROR;
576 }
577
578 svn_error_t *
svn_client__shelf_open_existing(svn_client__shelf_t ** shelf_p,const char * name,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool)579 svn_client__shelf_open_existing(svn_client__shelf_t **shelf_p,
580 const char *name,
581 const char *local_abspath,
582 svn_client_ctx_t *ctx,
583 apr_pool_t *result_pool)
584 {
585 SVN_ERR(shelf_construct(shelf_p, name,
586 local_abspath, ctx, result_pool));
587 SVN_ERR(shelf_read_revprops(*shelf_p, result_pool));
588 SVN_ERR(shelf_read_current(*shelf_p, result_pool));
589 if ((*shelf_p)->max_version < 0)
590 {
591 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
592 _("Shelf '%s' not found"),
593 name);
594 }
595 return SVN_NO_ERROR;
596 }
597
598 svn_error_t *
svn_client__shelf_open_or_create(svn_client__shelf_t ** shelf_p,const char * name,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool)599 svn_client__shelf_open_or_create(svn_client__shelf_t **shelf_p,
600 const char *name,
601 const char *local_abspath,
602 svn_client_ctx_t *ctx,
603 apr_pool_t *result_pool)
604 {
605 svn_client__shelf_t *shelf;
606
607 SVN_ERR(shelf_construct(&shelf, name,
608 local_abspath, ctx, result_pool));
609 SVN_ERR(shelf_read_revprops(shelf, result_pool));
610 SVN_ERR(shelf_read_current(shelf, result_pool));
611 if (shelf->max_version < 0)
612 {
613 shelf->max_version = 0;
614 SVN_ERR(shelf_write_current(shelf, result_pool));
615 }
616 *shelf_p = shelf;
617 return SVN_NO_ERROR;
618 }
619
620 svn_error_t *
svn_client__shelf_close(svn_client__shelf_t * shelf,apr_pool_t * scratch_pool)621 svn_client__shelf_close(svn_client__shelf_t *shelf,
622 apr_pool_t *scratch_pool)
623 {
624 return SVN_NO_ERROR;
625 }
626
627 svn_error_t *
svn_client__shelf_delete(const char * name,const char * local_abspath,svn_boolean_t dry_run,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)628 svn_client__shelf_delete(const char *name,
629 const char *local_abspath,
630 svn_boolean_t dry_run,
631 svn_client_ctx_t *ctx,
632 apr_pool_t *scratch_pool)
633 {
634 svn_client__shelf_t *shelf;
635 int i;
636 char *abspath;
637
638 SVN_ERR(svn_client__shelf_open_existing(&shelf, name,
639 local_abspath, ctx, scratch_pool));
640
641 /* Remove the versions. */
642 for (i = shelf->max_version; i > 0; i--)
643 {
644 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
645 }
646
647 /* Remove the other files */
648 SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool));
649 SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
650 SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool));
651 SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
652
653 SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
654 return SVN_NO_ERROR;
655 }
656
657 /* Baton for paths_changed_visitor(). */
658 struct paths_changed_walk_baton_t
659 {
660 apr_hash_t *paths_hash;
661 const char *wc_root_abspath;
662 apr_pool_t *pool;
663 };
664
665 /* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file.
666 * Implements shelved_files_walk_func_t. */
667 static svn_error_t *
paths_changed_visitor(void * baton,const char * relpath,const svn_wc_status3_t * s,apr_pool_t * scratch_pool)668 paths_changed_visitor(void *baton,
669 const char *relpath,
670 const svn_wc_status3_t *s,
671 apr_pool_t *scratch_pool)
672 {
673 struct paths_changed_walk_baton_t *b = baton;
674
675 relpath = apr_pstrdup(b->pool, relpath);
676 svn_hash_sets(b->paths_hash, relpath, relpath);
677 return SVN_NO_ERROR;
678 }
679
680 /* Get the paths changed, relative to WC root or as abspaths, as a hash
681 * and/or an array (in no particular order).
682 */
683 static svn_error_t *
shelf_paths_changed(apr_hash_t ** paths_hash_p,apr_array_header_t ** paths_array_p,svn_client__shelf_version_t * shelf_version,apr_pool_t * result_pool,apr_pool_t * scratch_pool)684 shelf_paths_changed(apr_hash_t **paths_hash_p,
685 apr_array_header_t **paths_array_p,
686 svn_client__shelf_version_t *shelf_version,
687 apr_pool_t *result_pool,
688 apr_pool_t *scratch_pool)
689 {
690 svn_client__shelf_t *shelf = shelf_version->shelf;
691 apr_hash_t *paths_hash = apr_hash_make(result_pool);
692 struct paths_changed_walk_baton_t baton;
693
694 baton.paths_hash = paths_hash;
695 baton.wc_root_abspath = shelf->wc_root_abspath;
696 baton.pool = result_pool;
697 SVN_ERR(shelf_status_walk(shelf_version, "",
698 paths_changed_visitor, &baton,
699 scratch_pool));
700
701 if (paths_hash_p)
702 *paths_hash_p = paths_hash;
703 if (paths_array_p)
704 SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool));
705
706 return SVN_NO_ERROR;
707 }
708
709 svn_error_t *
svn_client__shelf_paths_changed(apr_hash_t ** affected_paths,svn_client__shelf_version_t * shelf_version,apr_pool_t * result_pool,apr_pool_t * scratch_pool)710 svn_client__shelf_paths_changed(apr_hash_t **affected_paths,
711 svn_client__shelf_version_t *shelf_version,
712 apr_pool_t *result_pool,
713 apr_pool_t *scratch_pool)
714 {
715 SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version,
716 result_pool, scratch_pool));
717 return SVN_NO_ERROR;
718 }
719
720 svn_error_t *
svn_client__shelf_replay(svn_client__shelf_version_t * shelf_version,const char * top_relpath,const svn_delta_editor_t * editor,void * edit_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)721 svn_client__shelf_replay(svn_client__shelf_version_t *shelf_version,
722 const char *top_relpath,
723 const svn_delta_editor_t *editor,
724 void *edit_baton,
725 svn_wc_notify_func2_t notify_func,
726 void *notify_baton,
727 apr_pool_t *scratch_pool)
728 {
729 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
730 apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1,
731 sizeof(char *));
732 const char *src_wc_abspath
733 = svn_dirent_join(shelf_version->files_dir_abspath, top_relpath, scratch_pool);
734
735 APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath;
736 SVN_ERR(svn_client__wc_replay(src_wc_abspath,
737 src_targets, svn_depth_infinity, NULL,
738 editor, edit_baton,
739 notify_func, notify_baton,
740 ctx, scratch_pool));
741 return SVN_NO_ERROR;
742 }
743
744 /* Baton for test_apply_file_visitor(). */
745 struct test_apply_files_baton_t
746 {
747 svn_client__shelf_version_t *shelf_version;
748 svn_boolean_t conflict; /* would it conflict? */
749 svn_client_ctx_t *ctx;
750 };
751
752 /* Ideally, set BATON->conflict if we can't apply a change to WC
753 * at RELPATH without conflict. But in fact, just check
754 * if WC at RELPATH is locally modified.
755 *
756 * Implements shelved_files_walk_func_t. */
757 static svn_error_t *
test_apply_file_visitor(void * baton,const char * relpath,const svn_wc_status3_t * s,apr_pool_t * scratch_pool)758 test_apply_file_visitor(void *baton,
759 const char *relpath,
760 const svn_wc_status3_t *s,
761 apr_pool_t *scratch_pool)
762 {
763 struct test_apply_files_baton_t *b = baton;
764 const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath;
765 const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath,
766 scratch_pool);
767 svn_wc_status3_t *status;
768
769 SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath,
770 scratch_pool, scratch_pool));
771 switch (status->node_status)
772 {
773 case svn_wc_status_normal:
774 case svn_wc_status_none:
775 break;
776 default:
777 b->conflict = TRUE;
778 }
779
780 return SVN_NO_ERROR;
781 }
782
783 svn_error_t *
svn_client__shelf_test_apply_file(svn_boolean_t * conflict_p,svn_client__shelf_version_t * shelf_version,const char * file_relpath,apr_pool_t * scratch_pool)784 svn_client__shelf_test_apply_file(svn_boolean_t *conflict_p,
785 svn_client__shelf_version_t *shelf_version,
786 const char *file_relpath,
787 apr_pool_t *scratch_pool)
788 {
789 struct test_apply_files_baton_t baton = {0};
790
791 baton.shelf_version = shelf_version;
792 baton.conflict = FALSE;
793 baton.ctx = shelf_version->shelf->ctx;
794 SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath,
795 test_apply_file_visitor, &baton,
796 scratch_pool));
797 *conflict_p = baton.conflict;
798
799 return SVN_NO_ERROR;
800 }
801
802 static svn_error_t *
wc_mods_editor(const svn_delta_editor_t ** editor_p,void ** edit_baton_p,const char * dst_wc_abspath,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)803 wc_mods_editor(const svn_delta_editor_t **editor_p,
804 void **edit_baton_p,
805 const char *dst_wc_abspath,
806 svn_wc_notify_func2_t notify_func,
807 void *notify_baton,
808 svn_client_ctx_t *ctx,
809 apr_pool_t *result_pool,
810 apr_pool_t *scratch_pool)
811 {
812 svn_client__pathrev_t *base;
813 const char *dst_wc_url;
814 svn_ra_session_t *ra_session;
815
816 /* We'll need an RA session to obtain the base of any copies */
817 SVN_ERR(svn_client__wc_node_get_base(&base,
818 dst_wc_abspath, ctx->wc_ctx,
819 scratch_pool, scratch_pool));
820 dst_wc_url = base->url;
821 SVN_ERR(svn_client_open_ra_session2(&ra_session,
822 dst_wc_url, dst_wc_abspath,
823 ctx, result_pool, scratch_pool));
824 SVN_ERR(svn_client__wc_editor(editor_p, edit_baton_p,
825 dst_wc_abspath,
826 notify_func, notify_baton,
827 ra_session, ctx, result_pool));
828 return SVN_NO_ERROR;
829 }
830
831 svn_error_t *
svn_client__shelf_mods_editor(const svn_delta_editor_t ** editor_p,void ** edit_baton_p,svn_client__shelf_version_t * shelf_version,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_client_ctx_t * ctx,apr_pool_t * result_pool)832 svn_client__shelf_mods_editor(const svn_delta_editor_t **editor_p,
833 void **edit_baton_p,
834 svn_client__shelf_version_t *shelf_version,
835 svn_wc_notify_func2_t notify_func,
836 void *notify_baton,
837 svn_client_ctx_t *ctx,
838 apr_pool_t *result_pool)
839 {
840 SVN_ERR(wc_mods_editor(editor_p, edit_baton_p,
841 shelf_version->files_dir_abspath,
842 notify_func, notify_baton,
843 ctx, result_pool, result_pool));
844 return SVN_NO_ERROR;
845 }
846
847 svn_error_t *
svn_client__shelf_apply(svn_client__shelf_version_t * shelf_version,svn_boolean_t dry_run,apr_pool_t * scratch_pool)848 svn_client__shelf_apply(svn_client__shelf_version_t *shelf_version,
849 svn_boolean_t dry_run,
850 apr_pool_t *scratch_pool)
851 {
852 svn_client__shelf_t *shelf = shelf_version->shelf;
853 const svn_delta_editor_t *editor;
854 void *edit_baton;
855
856 SVN_ERR(wc_mods_editor(&editor, &edit_baton,
857 shelf->wc_root_abspath,
858 NULL, NULL, /*notification*/
859 shelf->ctx, scratch_pool, scratch_pool));
860
861 SVN_ERR(svn_client__shelf_replay(shelf_version, "",
862 editor, edit_baton,
863 shelf->ctx->notify_func2, shelf->ctx->notify_baton2,
864 scratch_pool));
865
866 svn_io_sleep_for_timestamps(shelf->wc_root_abspath,
867 scratch_pool);
868 return SVN_NO_ERROR;
869 }
870
871 /* Baton for paths_changed_visitor(). */
872 struct unapply_walk_baton_t
873 {
874 const char *wc_root_abspath;
875 svn_boolean_t dry_run;
876 svn_boolean_t use_commit_times;
877 svn_client_ctx_t *ctx;
878 apr_pool_t *pool;
879 };
880
881 /* Revert the change at RELPATH in the user's WC.
882 * Implements shelved_files_walk_func_t. */
883 static svn_error_t *
unapply_visitor(void * baton,const char * relpath,const svn_wc_status3_t * s,apr_pool_t * scratch_pool)884 unapply_visitor(void *baton,
885 const char *relpath,
886 const svn_wc_status3_t *s,
887 apr_pool_t *scratch_pool)
888 {
889 struct unapply_walk_baton_t *b = baton;
890 const char *abspath = svn_dirent_join(b->wc_root_abspath, relpath,
891 scratch_pool);
892
893 if (!b->dry_run)
894 {
895 apr_array_header_t *targets
896 = apr_array_make(scratch_pool, 1, sizeof(char *));
897 svn_depth_t depth;
898
899 APR_ARRAY_PUSH(targets, const char *) = abspath;
900
901 /* If the local modification is a "delete" then revert it all
902 (recursively). Otherwise we'd have to walk paths in
903 top-down order to revert a delete, whereas we need bottom-up
904 order to revert children of an added directory. */
905 if (s->node_status == svn_wc_status_deleted
906 || s->node_status == svn_wc_status_replaced
907 || s->node_status == svn_wc_status_added)
908 depth = svn_depth_infinity;
909 else
910 depth = svn_depth_empty;
911 SVN_ERR(svn_wc_revert6(b->ctx->wc_ctx,
912 abspath,
913 depth,
914 b->use_commit_times,
915 NULL /*changelists*/,
916 FALSE /*clear_changelists*/,
917 FALSE /*metadata_only*/,
918 FALSE /*added_keep_local*/,
919 b->ctx->cancel_func, b->ctx->cancel_baton,
920 NULL, NULL, /*notification*/
921 scratch_pool));
922 }
923 return SVN_NO_ERROR;
924 }
925
926 svn_error_t *
svn_client__shelf_unapply(svn_client__shelf_version_t * shelf_version,svn_boolean_t dry_run,apr_pool_t * scratch_pool)927 svn_client__shelf_unapply(svn_client__shelf_version_t *shelf_version,
928 svn_boolean_t dry_run,
929 apr_pool_t *scratch_pool)
930 {
931 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
932 svn_client__shelf_t *shelf = shelf_version->shelf;
933 struct unapply_walk_baton_t baton;
934 svn_config_t *cfg;
935
936 baton.wc_root_abspath = shelf->wc_root_abspath;
937 baton.dry_run = dry_run;
938 baton.ctx = ctx;
939 baton.pool = scratch_pool;
940
941 cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
942 : NULL;
943 SVN_ERR(svn_config_get_bool(cfg, &baton.use_commit_times,
944 SVN_CONFIG_SECTION_MISCELLANY,
945 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
946
947 SVN_WC__CALL_WITH_WRITE_LOCK(
948 shelf_status_walk(shelf_version, "",
949 unapply_visitor, &baton,
950 scratch_pool),
951 ctx->wc_ctx, shelf_version->shelf->wc_root_abspath,
952 FALSE /*lock_anchor*/, scratch_pool);
953 return SVN_NO_ERROR;
954 }
955
956 svn_error_t *
svn_client__shelf_delete_newer_versions(svn_client__shelf_t * shelf,svn_client__shelf_version_t * shelf_version,apr_pool_t * scratch_pool)957 svn_client__shelf_delete_newer_versions(svn_client__shelf_t *shelf,
958 svn_client__shelf_version_t *shelf_version,
959 apr_pool_t *scratch_pool)
960 {
961 int previous_version = shelf_version ? shelf_version->version_number : 0;
962 int i;
963
964 /* Delete any newer checkpoints */
965 for (i = shelf->max_version; i > previous_version; i--)
966 {
967 SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
968 }
969
970 shelf->max_version = previous_version;
971 SVN_ERR(shelf_write_current(shelf, scratch_pool));
972 return SVN_NO_ERROR;
973 }
974
975 svn_error_t *
svn_client__shelf_diff(svn_client__shelf_version_t * shelf_version,const char * shelf_relpath,svn_depth_t depth,svn_boolean_t ignore_ancestry,const svn_diff_tree_processor_t * diff_processor,apr_pool_t * scratch_pool)976 svn_client__shelf_diff(svn_client__shelf_version_t *shelf_version,
977 const char *shelf_relpath,
978 svn_depth_t depth,
979 svn_boolean_t ignore_ancestry,
980 const svn_diff_tree_processor_t *diff_processor,
981 apr_pool_t *scratch_pool)
982 {
983 svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
984 char *local_abspath
985 = svn_dirent_join(shelf_version->files_dir_abspath, shelf_relpath,
986 scratch_pool);
987
988 if (shelf_version->version_number == 0)
989 return SVN_NO_ERROR;
990
991 SVN_ERR(svn_wc__diff7(FALSE /*anchor_at_given_paths*/,
992 ctx->wc_ctx, local_abspath,
993 depth,
994 ignore_ancestry,
995 NULL /*changelists*/,
996 diff_processor,
997 NULL, NULL, /*cancellation*/
998 scratch_pool, scratch_pool));
999 return SVN_NO_ERROR;
1000 }
1001
1002 /* Populate the storage a new shelf-version object NEW_SHELF_VERSION,
1003 * by creating a shelf storage WC with its base state copied from the
1004 * 'real' WC.
1005 */
1006 static svn_error_t *
shelf_copy_base(svn_client__shelf_version_t * new_shelf_version,apr_pool_t * scratch_pool)1007 shelf_copy_base(svn_client__shelf_version_t *new_shelf_version,
1008 apr_pool_t *scratch_pool)
1009 {
1010 svn_client_ctx_t *ctx = new_shelf_version->shelf->ctx;
1011 const char *users_wc_abspath = new_shelf_version->shelf->wc_root_abspath;
1012 svn_client__pathrev_t *users_wc_root_base;
1013 svn_opt_revision_t users_wc_root_rev;
1014 svn_ra_session_t *ra_session = NULL;
1015 svn_boolean_t sleep_here = FALSE;
1016
1017 SVN_ERR(svn_client__wc_node_get_base(&users_wc_root_base,
1018 users_wc_abspath, ctx->wc_ctx,
1019 scratch_pool, scratch_pool));
1020
1021 /* ### We need to read and recreate the mixed-rev, switched-URL,
1022 mixed-depth WC state; but for a rough start we'll just use
1023 HEAD, unswitched, depth-infinity. */
1024 users_wc_root_rev.kind = svn_opt_revision_head;
1025
1026 /* ### TODO: Create an RA session that reads from the user's WC.
1027 For a rough start, we'll just let 'checkout' read from the repo. */
1028
1029 SVN_ERR(svn_client__checkout_internal(NULL /*result_rev*/, &sleep_here,
1030 users_wc_root_base->url,
1031 new_shelf_version->files_dir_abspath,
1032 &users_wc_root_rev, &users_wc_root_rev,
1033 svn_depth_infinity,
1034 TRUE /*ignore_externals*/,
1035 FALSE /*allow_unver_obstructions*/,
1036 ra_session,
1037 ctx, scratch_pool));
1038 /* ### hopefully we won't eventually need to sleep_here... */
1039 if (sleep_here)
1040 svn_io_sleep_for_timestamps(new_shelf_version->files_dir_abspath,
1041 scratch_pool);
1042 return SVN_NO_ERROR;
1043 }
1044
1045 /* */
1046 struct shelf_save_notifer_baton_t
1047 {
1048 svn_client__shelf_version_t *shelf_version;
1049 svn_wc_notify_func2_t notify_func;
1050 void *notify_baton;
1051 svn_client_status_func_t shelved_func;
1052 void *shelved_baton;
1053 svn_boolean_t any_shelved;
1054 };
1055
1056 /* */
1057 static void
shelf_save_notifier(void * baton,const svn_wc_notify_t * notify,apr_pool_t * pool)1058 shelf_save_notifier(void *baton,
1059 const svn_wc_notify_t *notify,
1060 apr_pool_t *pool)
1061 {
1062 struct shelf_save_notifer_baton_t *nb = baton;
1063 const char *wc_relpath
1064 = svn_dirent_skip_ancestor(nb->shelf_version->shelf->wc_root_abspath,
1065 notify->path);
1066 svn_client_status_t *cst = NULL;
1067 #if 0
1068 svn_wc_status3_t *wc_status;
1069
1070 svn_error_clear(status_read(&wc_status, nb->shelf_version, wc_relpath,
1071 pool, pool));
1072 svn_error_clear(svn_client__create_status(
1073 &cst, nb->shelf_version->shelf->ctx->wc_ctx,
1074 notify->path, wc_status, pool, pool));
1075 #endif
1076 svn_error_clear(nb->shelved_func(nb->shelved_baton, wc_relpath, cst, pool));
1077 nb->any_shelved = TRUE;
1078
1079 nb->notify_func(nb->notify_baton, notify, pool);
1080 }
1081
1082 svn_error_t *
svn_client__shelf_save_new_version3(svn_client__shelf_version_t ** new_version_p,svn_client__shelf_t * shelf,const apr_array_header_t * paths,svn_depth_t depth,const apr_array_header_t * changelists,svn_client_status_func_t shelved_func,void * shelved_baton,svn_client_status_func_t not_shelved_func,void * not_shelved_baton,apr_pool_t * scratch_pool)1083 svn_client__shelf_save_new_version3(svn_client__shelf_version_t **new_version_p,
1084 svn_client__shelf_t *shelf,
1085 const apr_array_header_t *paths,
1086 svn_depth_t depth,
1087 const apr_array_header_t *changelists,
1088 svn_client_status_func_t shelved_func,
1089 void *shelved_baton,
1090 svn_client_status_func_t not_shelved_func,
1091 void *not_shelved_baton,
1092 apr_pool_t *scratch_pool)
1093 {
1094 svn_client_ctx_t *ctx = shelf->ctx;
1095 int next_version = shelf->max_version + 1;
1096 svn_client__shelf_version_t *new_shelf_version;
1097 struct shelf_save_notifer_baton_t nb;
1098 const svn_delta_editor_t *editor;
1099 void *edit_baton;
1100
1101 SVN_ERR(shelf_version_create(&new_shelf_version,
1102 shelf, next_version, scratch_pool));
1103 SVN_ERR(shelf_copy_base(new_shelf_version, scratch_pool));
1104
1105 nb.shelf_version = new_shelf_version;
1106 nb.notify_func = ctx->notify_func2;
1107 nb.notify_baton = ctx->notify_baton2;
1108 nb.shelved_func = shelved_func;
1109 nb.shelved_baton = shelved_baton;
1110 nb.any_shelved = FALSE;
1111 SVN_ERR(svn_client__shelf_mods_editor(&editor, &edit_baton,
1112 new_shelf_version,
1113 NULL, NULL, /*notification*/
1114 ctx, scratch_pool));
1115 SVN_ERR(svn_client__wc_replay(shelf->wc_root_abspath,
1116 paths, depth, changelists,
1117 editor, edit_baton,
1118 shelf_save_notifier, &nb,
1119 ctx, scratch_pool));
1120
1121 if (nb.any_shelved)
1122 {
1123 shelf->max_version = next_version;
1124 SVN_ERR(shelf_write_current(shelf, scratch_pool));
1125
1126 if (new_version_p)
1127 SVN_ERR(svn_client__shelf_version_open(new_version_p, shelf, next_version,
1128 scratch_pool, scratch_pool));
1129 }
1130 else
1131 {
1132 if (new_version_p)
1133 *new_version_p = NULL;
1134 }
1135 return SVN_NO_ERROR;
1136 }
1137
1138 svn_error_t *
svn_client__shelf_get_log_message(char ** log_message,svn_client__shelf_t * shelf,apr_pool_t * result_pool)1139 svn_client__shelf_get_log_message(char **log_message,
1140 svn_client__shelf_t *shelf,
1141 apr_pool_t *result_pool)
1142 {
1143 svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG);
1144
1145 if (propval)
1146 *log_message = apr_pstrdup(result_pool, propval->data);
1147 else
1148 *log_message = NULL;
1149 return SVN_NO_ERROR;
1150 }
1151
1152 svn_error_t *
svn_client__shelf_set_log_message(svn_client__shelf_t * shelf,const char * message,apr_pool_t * scratch_pool)1153 svn_client__shelf_set_log_message(svn_client__shelf_t *shelf,
1154 const char *message,
1155 apr_pool_t *scratch_pool)
1156 {
1157 svn_string_t *propval
1158 = message ? svn_string_create(message, shelf->pool) : NULL;
1159
1160 SVN_ERR(svn_client__shelf_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval,
1161 scratch_pool));
1162 return SVN_NO_ERROR;
1163 }
1164
1165 svn_error_t *
svn_client__shelf_list(apr_hash_t ** shelf_infos,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1166 svn_client__shelf_list(apr_hash_t **shelf_infos,
1167 const char *local_abspath,
1168 svn_client_ctx_t *ctx,
1169 apr_pool_t *result_pool,
1170 apr_pool_t *scratch_pool)
1171 {
1172 const char *wc_root_abspath;
1173 char *shelves_dir;
1174 apr_hash_t *dirents;
1175 apr_hash_index_t *hi;
1176
1177 SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath,
1178 scratch_pool, scratch_pool));
1179 SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
1180 scratch_pool, scratch_pool));
1181 SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
1182 result_pool, scratch_pool));
1183
1184 *shelf_infos = apr_hash_make(result_pool);
1185
1186 /* Remove non-shelves */
1187 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
1188 {
1189 const char *filename = apr_hash_this_key(hi);
1190 svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
1191 char *name = NULL;
1192
1193 svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
1194 if (name && dirent->kind == svn_node_file)
1195 {
1196 svn_client__shelf_info_t *info
1197 = apr_palloc(result_pool, sizeof(*info));
1198
1199 info->mtime = dirent->mtime;
1200 svn_hash_sets(*shelf_infos, name, info);
1201 }
1202 }
1203
1204 return SVN_NO_ERROR;
1205 }
1206
1207 svn_error_t *
svn_client__shelf_version_open(svn_client__shelf_version_t ** shelf_version_p,svn_client__shelf_t * shelf,int version_number,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1208 svn_client__shelf_version_open(svn_client__shelf_version_t **shelf_version_p,
1209 svn_client__shelf_t *shelf,
1210 int version_number,
1211 apr_pool_t *result_pool,
1212 apr_pool_t *scratch_pool)
1213 {
1214 svn_client__shelf_version_t *shelf_version;
1215 const svn_io_dirent2_t *dirent;
1216
1217 SVN_ERR(shelf_version_create(&shelf_version,
1218 shelf, version_number, result_pool));
1219 SVN_ERR(svn_io_stat_dirent2(&dirent,
1220 shelf_version->files_dir_abspath,
1221 FALSE /*verify_truename*/,
1222 TRUE /*ignore_enoent*/,
1223 result_pool, scratch_pool));
1224 if (dirent->kind == svn_node_none)
1225 {
1226 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1227 _("Shelf '%s' version %d not found"),
1228 shelf->name, version_number);
1229 }
1230 shelf_version->mtime = dirent->mtime;
1231 *shelf_version_p = shelf_version;
1232 return SVN_NO_ERROR;
1233 }
1234
1235 svn_error_t *
svn_client__shelf_get_newest_version(svn_client__shelf_version_t ** shelf_version_p,svn_client__shelf_t * shelf,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1236 svn_client__shelf_get_newest_version(svn_client__shelf_version_t **shelf_version_p,
1237 svn_client__shelf_t *shelf,
1238 apr_pool_t *result_pool,
1239 apr_pool_t *scratch_pool)
1240 {
1241 if (shelf->max_version == 0)
1242 {
1243 *shelf_version_p = NULL;
1244 return SVN_NO_ERROR;
1245 }
1246
1247 SVN_ERR(svn_client__shelf_version_open(shelf_version_p,
1248 shelf, shelf->max_version,
1249 result_pool, scratch_pool));
1250 return SVN_NO_ERROR;
1251 }
1252
1253 svn_error_t *
svn_client__shelf_get_all_versions(apr_array_header_t ** versions_p,svn_client__shelf_t * shelf,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1254 svn_client__shelf_get_all_versions(apr_array_header_t **versions_p,
1255 svn_client__shelf_t *shelf,
1256 apr_pool_t *result_pool,
1257 apr_pool_t *scratch_pool)
1258 {
1259 int i;
1260
1261 *versions_p = apr_array_make(result_pool, shelf->max_version - 1,
1262 sizeof(svn_client__shelf_version_t *));
1263
1264 for (i = 1; i <= shelf->max_version; i++)
1265 {
1266 svn_client__shelf_version_t *shelf_version;
1267
1268 SVN_ERR(svn_client__shelf_version_open(&shelf_version,
1269 shelf, i,
1270 result_pool, scratch_pool));
1271 APR_ARRAY_PUSH(*versions_p, svn_client__shelf_version_t *) = shelf_version;
1272 }
1273 return SVN_NO_ERROR;
1274 }
1275