]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_client/shelf.c
Update Subversion to 1.14.0 LTS. See contrib/subversion/CHANGES for a
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_client / shelf.c
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 }