/[Apache-SVN]/subversion/trunk/subversion/libsvn_client/shelf.c
ViewVC logotype

Contents of /subversion/trunk/subversion/libsvn_client/shelf.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1875037 - (show annotations) (download)
Tue Mar 10 12:16:26 2020 UTC (4 years, 4 months ago) by julianfoad
File MIME type: text/plain
File size: 44146 byte(s)
Merge the 'decouple-shelving-cli' branch to trunk.

Summary:

Add the shelving v2 implementation from Subversion 1.11, as an alternative
to the shelving v3 implementation from Subversion 1.12.

They have substantially different pros and cons, so it is beneficial for the
user to be able to choose.

Make the shelving CLI version selectable by an environment variable:
  env. var. not set                 => shelving v3 enabled
  SVN_EXPERIMENTAL_COMMANDS=shelf3  => shelving v3 enabled
  SVN_EXPERIMENTAL_COMMANDS=shelf2  => shelving v2 enabled
  SVN_EXPERIMENTAL_COMMANDS=        => no shelving CLI


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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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(&current_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 *
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(&current_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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 }

Properties

Name Value
svn:eol-style native

infrastructure at apache.org
ViewVC Help
Powered by ViewVC 1.1.26