1 /*
2  * shelf2.c:  implementation of shelving v2
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 /* ==================================================================== */
25 
26 /* We define this here to remove any further warnings about the usage of
27    experimental functions in this file. */
28 #define SVN_EXPERIMENTAL
29 
30 #include "svn_client.h"
31 #include "svn_wc.h"
32 #include "svn_pools.h"
33 #include "svn_dirent_uri.h"
34 #include "svn_path.h"
35 #include "svn_hash.h"
36 #include "svn_utf.h"
37 #include "svn_ctype.h"
38 #include "svn_props.h"
39 
40 #include "client.h"
41 #include "private/svn_client_shelf2.h"
42 #include "private/svn_client_private.h"
43 #include "private/svn_wc_private.h"
44 #include "private/svn_sorts_private.h"
45 #include "svn_private_config.h"
46 
47 
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   int suffix_len = sizeof(suffix) - 1;
105 
106   if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0)
107     {
108       char *codename = apr_pstrndup(result_pool, filename, len - suffix_len);
109       SVN_ERR(shelf_name_decode(name, codename, result_pool));
110     }
111   else
112     {
113       *name = NULL;
114     }
115   return SVN_NO_ERROR;
116 }
117 
118 /* Set *DIR to the shelf storage directory inside the WC's administrative
119  * area. Ensure the directory exists. */
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/v2", result_pool);
133 
134   /* Ensure the directory exists. (Other versions of svn don't create it.) */
135   SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool));
136 
137   return SVN_NO_ERROR;
138 }
139 
140 /* Set *ABSPATH to the abspath of the file storage dir for SHELF
141  * version VERSION, no matter whether it exists.
142  */
143 static svn_error_t *
shelf_version_files_dir_abspath(const char ** abspath,svn_client__shelf2_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__shelf2_t *shelf,
146                                 int version,
147                                 apr_pool_t *result_pool,
148                                 apr_pool_t *scratch_pool)
149 {
150   char *codename;
151   char *filename;
152 
153   SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
154   filename = apr_psprintf(scratch_pool, "%s-%03d.d", codename, version);
155   *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
156   return SVN_NO_ERROR;
157 }
158 
159 /* Create a shelf-version object for a version that may or may not already
160  * exist on disk.
161  */
162 static svn_error_t *
shelf_version_create(svn_client__shelf2_version_t ** new_version_p,svn_client__shelf2_t * shelf,int version_number,apr_pool_t * result_pool)163 shelf_version_create(svn_client__shelf2_version_t **new_version_p,
164                      svn_client__shelf2_t *shelf,
165                      int version_number,
166                      apr_pool_t *result_pool)
167 {
168   svn_client__shelf2_version_t *shelf_version
169     = apr_pcalloc(result_pool, sizeof(*shelf_version));
170 
171   shelf_version->shelf = shelf;
172   shelf_version->version_number = version_number;
173   SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath,
174                                           shelf, version_number,
175                                           result_pool, result_pool));
176   *new_version_p = shelf_version;
177   return SVN_NO_ERROR;
178 }
179 
180 /* Set *ABSPATH to the abspath of the metadata file for SHELF_VERSION
181  * node at RELPATH, no matter whether it exists.
182  */
183 static svn_error_t *
get_metadata_abspath(char ** abspath,svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)184 get_metadata_abspath(char **abspath,
185                      svn_client__shelf2_version_t *shelf_version,
186                      const char *wc_relpath,
187                      apr_pool_t *result_pool,
188                      apr_pool_t *scratch_pool)
189 {
190   wc_relpath = apr_psprintf(scratch_pool, "%s.meta", wc_relpath);
191   *abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
192                              result_pool);
193   return SVN_NO_ERROR;
194 }
195 
196 /* Set *ABSPATH to the abspath of the base text file for SHELF_VERSION
197  * node at RELPATH, no matter whether it exists.
198  */
199 static svn_error_t *
get_base_file_abspath(char ** base_abspath,svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)200 get_base_file_abspath(char **base_abspath,
201                       svn_client__shelf2_version_t *shelf_version,
202                       const char *wc_relpath,
203                       apr_pool_t *result_pool,
204                       apr_pool_t *scratch_pool)
205 {
206   wc_relpath = apr_psprintf(scratch_pool, "%s.base", wc_relpath);
207   *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
208                                   result_pool);
209   return SVN_NO_ERROR;
210 }
211 
212 /* Set *ABSPATH to the abspath of the working text file for SHELF_VERSION
213  * node at RELPATH, no matter whether it exists.
214  */
215 static svn_error_t *
get_working_file_abspath(char ** work_abspath,svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)216 get_working_file_abspath(char **work_abspath,
217                          svn_client__shelf2_version_t *shelf_version,
218                          const char *wc_relpath,
219                          apr_pool_t *result_pool,
220                          apr_pool_t *scratch_pool)
221 {
222   wc_relpath = apr_psprintf(scratch_pool, "%s.work", wc_relpath);
223   *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
224                                   result_pool);
225   return SVN_NO_ERROR;
226 }
227 
228 /* Set *ABSPATH to the abspath of the base props file for SHELF_VERSION
229  * node at RELPATH, no matter whether it exists.
230  */
231 static svn_error_t *
get_base_props_abspath(char ** base_abspath,svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)232 get_base_props_abspath(char **base_abspath,
233                        svn_client__shelf2_version_t *shelf_version,
234                        const char *wc_relpath,
235                        apr_pool_t *result_pool,
236                        apr_pool_t *scratch_pool)
237 {
238   wc_relpath = apr_psprintf(scratch_pool, "%s.base-props", wc_relpath);
239   *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
240                                   result_pool);
241   return SVN_NO_ERROR;
242 }
243 
244 /* Set *ABSPATH to the abspath of the working props file for SHELF_VERSION
245  * node at RELPATH, no matter whether it exists.
246  */
247 static svn_error_t *
get_working_props_abspath(char ** work_abspath,svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)248 get_working_props_abspath(char **work_abspath,
249                           svn_client__shelf2_version_t *shelf_version,
250                           const char *wc_relpath,
251                           apr_pool_t *result_pool,
252                           apr_pool_t *scratch_pool)
253 {
254   wc_relpath = apr_psprintf(scratch_pool, "%s.work-props", wc_relpath);
255   *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
256                                   result_pool);
257   return SVN_NO_ERROR;
258 }
259 
260 /* Delete the storage for SHELF:VERSION. */
261 static svn_error_t *
shelf_version_delete(svn_client__shelf2_t * shelf,int version,apr_pool_t * scratch_pool)262 shelf_version_delete(svn_client__shelf2_t *shelf,
263                      int version,
264                      apr_pool_t *scratch_pool)
265 {
266   const char *files_dir_abspath;
267 
268   SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath,
269                                           shelf, version,
270                                           scratch_pool, scratch_pool));
271   SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/,
272                              NULL, NULL, /*cancel*/
273                              scratch_pool));
274   return SVN_NO_ERROR;
275 }
276 
277 /*  */
278 static svn_error_t *
get_log_abspath(char ** log_abspath,svn_client__shelf2_t * shelf,apr_pool_t * result_pool,apr_pool_t * scratch_pool)279 get_log_abspath(char **log_abspath,
280                 svn_client__shelf2_t *shelf,
281                 apr_pool_t *result_pool,
282                 apr_pool_t *scratch_pool)
283 {
284   char *codename;
285   const char *filename;
286 
287   SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
288   filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL);
289   *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
290   return SVN_NO_ERROR;
291 }
292 
293 /* Set SHELF->revprops by reading from its storage (the '.log' file).
294  * Set SHELF->revprops to empty if the storage file does not exist; this
295  * is not an error.
296  */
297 static svn_error_t *
shelf_read_revprops(svn_client__shelf2_t * shelf,apr_pool_t * scratch_pool)298 shelf_read_revprops(svn_client__shelf2_t *shelf,
299                     apr_pool_t *scratch_pool)
300 {
301   char *log_abspath;
302   svn_error_t *err;
303   svn_stream_t *stream;
304 
305   SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
306 
307   shelf->revprops = apr_hash_make(shelf->pool);
308   err = svn_stream_open_readonly(&stream, log_abspath,
309                                  scratch_pool, scratch_pool);
310   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
311     {
312       svn_error_clear(err);
313       return SVN_NO_ERROR;
314     }
315   else
316     SVN_ERR(err);
317   SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool));
318   SVN_ERR(svn_stream_close(stream));
319   return SVN_NO_ERROR;
320 }
321 
322 /* Write SHELF's revprops to its file storage.
323  */
324 static svn_error_t *
shelf_write_revprops(svn_client__shelf2_t * shelf,apr_pool_t * scratch_pool)325 shelf_write_revprops(svn_client__shelf2_t *shelf,
326                      apr_pool_t *scratch_pool)
327 {
328   char *log_abspath;
329   apr_file_t *file;
330   svn_stream_t *stream;
331 
332   SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
333 
334   SVN_ERR(svn_io_file_open(&file, log_abspath,
335                            APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE,
336                            APR_FPROT_OS_DEFAULT, scratch_pool));
337   stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
338 
339   SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool));
340   SVN_ERR(svn_stream_close(stream));
341   return SVN_NO_ERROR;
342 }
343 
344 svn_error_t *
svn_client__shelf2_revprop_set(svn_client__shelf2_t * shelf,const char * prop_name,const svn_string_t * prop_val,apr_pool_t * scratch_pool)345 svn_client__shelf2_revprop_set(svn_client__shelf2_t *shelf,
346                                const char *prop_name,
347                                const svn_string_t *prop_val,
348                                apr_pool_t *scratch_pool)
349 {
350   svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name),
351                 svn_string_dup(prop_val, shelf->pool));
352   SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
353   return SVN_NO_ERROR;
354 }
355 
356 svn_error_t *
svn_client__shelf2_revprop_set_all(svn_client__shelf2_t * shelf,apr_hash_t * revprop_table,apr_pool_t * scratch_pool)357 svn_client__shelf2_revprop_set_all(svn_client__shelf2_t *shelf,
358                                    apr_hash_t *revprop_table,
359                                    apr_pool_t *scratch_pool)
360 {
361   if (revprop_table)
362     shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool);
363   else
364     shelf->revprops = apr_hash_make(shelf->pool);
365 
366   SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
367   return SVN_NO_ERROR;
368 }
369 
370 svn_error_t *
svn_client__shelf2_revprop_get(svn_string_t ** prop_val,svn_client__shelf2_t * shelf,const char * prop_name,apr_pool_t * result_pool)371 svn_client__shelf2_revprop_get(svn_string_t **prop_val,
372                                svn_client__shelf2_t *shelf,
373                                const char *prop_name,
374                                apr_pool_t *result_pool)
375 {
376   *prop_val = svn_hash_gets(shelf->revprops, prop_name);
377   return SVN_NO_ERROR;
378 }
379 
380 svn_error_t *
svn_client__shelf2_revprop_list(apr_hash_t ** props,svn_client__shelf2_t * shelf,apr_pool_t * result_pool)381 svn_client__shelf2_revprop_list(apr_hash_t **props,
382                                 svn_client__shelf2_t *shelf,
383                                 apr_pool_t *result_pool)
384 {
385   *props = shelf->revprops;
386   return SVN_NO_ERROR;
387 }
388 
389 /*  */
390 static svn_error_t *
get_current_abspath(char ** current_abspath,svn_client__shelf2_t * shelf,apr_pool_t * result_pool)391 get_current_abspath(char **current_abspath,
392                     svn_client__shelf2_t *shelf,
393                     apr_pool_t *result_pool)
394 {
395   char *codename;
396   char *filename;
397 
398   SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
399   filename = apr_psprintf(result_pool, "%s.current", codename);
400   *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
401   return SVN_NO_ERROR;
402 }
403 
404 /* Read SHELF->max_version from its storage (the '.current' file).
405  * Set SHELF->max_version to -1 if that file does not exist.
406  */
407 static svn_error_t *
shelf_read_current(svn_client__shelf2_t * shelf,apr_pool_t * scratch_pool)408 shelf_read_current(svn_client__shelf2_t *shelf,
409                    apr_pool_t *scratch_pool)
410 {
411   char *current_abspath;
412   svn_error_t *err;
413 
414   SVN_ERR(get_current_abspath(&current_abspath, shelf, scratch_pool));
415   err = svn_io_read_version_file(&shelf->max_version,
416                                  current_abspath, scratch_pool);
417   if (err)
418     {
419       shelf->max_version = -1;
420       svn_error_clear(err);
421       return SVN_NO_ERROR;
422     }
423   return SVN_NO_ERROR;
424 }
425 
426 /*  */
427 static svn_error_t *
shelf_write_current(svn_client__shelf2_t * shelf,apr_pool_t * scratch_pool)428 shelf_write_current(svn_client__shelf2_t *shelf,
429                     apr_pool_t *scratch_pool)
430 {
431   char *current_abspath;
432 
433   SVN_ERR(get_current_abspath(&current_abspath, shelf, scratch_pool));
434   SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version,
435                                     scratch_pool));
436   return SVN_NO_ERROR;
437 }
438 
439 /*-------------------------------------------------------------------------*/
440 /* Status Reporting */
441 
442 /* Create a status struct with all fields initialized to valid values
443  * representing 'uninteresting' or 'unknown' status.
444  */
445 static svn_wc_status3_t *
status_create(apr_pool_t * result_pool)446 status_create(apr_pool_t *result_pool)
447 {
448   svn_wc_status3_t *s = apr_pcalloc(result_pool, sizeof(*s));
449 
450   s->filesize = SVN_INVALID_FILESIZE;
451   s->versioned = TRUE;
452   s->node_status = svn_wc_status_none;
453   s->text_status = svn_wc_status_none;
454   s->prop_status = svn_wc_status_none;
455   s->revision = SVN_INVALID_REVNUM;
456   s->changed_rev = SVN_INVALID_REVNUM;
457   s->repos_node_status = svn_wc_status_none;
458   s->repos_text_status = svn_wc_status_none;
459   s->repos_prop_status = svn_wc_status_none;
460   s->ood_changed_rev = SVN_INVALID_REVNUM;
461   return s;
462 }
463 
464 /* Convert from svn_node_kind_t to a single character representation. */
465 static char
kind_to_char(svn_node_kind_t kind)466 kind_to_char(svn_node_kind_t kind)
467 {
468   return (kind == svn_node_dir ? 'd'
469             : kind == svn_node_file ? 'f'
470                 : kind == svn_node_symlink ? 'l'
471                     : '?');
472 }
473 
474 /* Convert to svn_node_kind_t from a single character representation. */
475 static svn_node_kind_t
char_to_kind(char kind)476 char_to_kind(char kind)
477 {
478   return (kind == 'd' ? svn_node_dir
479             : kind == 'f' ? svn_node_file
480                 : kind == 'l' ? svn_node_symlink
481                     : svn_node_unknown);
482 }
483 
484 /* Return the single character representation of STATUS.
485  * (Similar to subversion/svn/status.c:generate_status_code()
486  * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */
487 static char
status_to_char(enum svn_wc_status_kind status)488 status_to_char(enum svn_wc_status_kind status)
489 {
490   switch (status)
491     {
492     case svn_wc_status_none:        return '.';
493     case svn_wc_status_unversioned: return '?';
494     case svn_wc_status_normal:      return ' ';
495     case svn_wc_status_added:       return 'A';
496     case svn_wc_status_missing:     return '!';
497     case svn_wc_status_deleted:     return 'D';
498     case svn_wc_status_replaced:    return 'R';
499     case svn_wc_status_modified:    return 'M';
500     case svn_wc_status_merged:      return 'G';
501     case svn_wc_status_conflicted:  return 'C';
502     case svn_wc_status_ignored:     return 'I';
503     case svn_wc_status_obstructed:  return '~';
504     case svn_wc_status_external:    return 'X';
505     case svn_wc_status_incomplete:  return ':';
506     default:                        return '*';
507     }
508 }
509 
510 static enum svn_wc_status_kind
char_to_status(char status)511 char_to_status(char status)
512 {
513   switch (status)
514     {
515     case '.': return svn_wc_status_none;
516     case '?': return svn_wc_status_unversioned;
517     case ' ': return svn_wc_status_normal;
518     case 'A': return svn_wc_status_added;
519     case '!': return svn_wc_status_missing;
520     case 'D': return svn_wc_status_deleted;
521     case 'R': return svn_wc_status_replaced;
522     case 'M': return svn_wc_status_modified;
523     case 'G': return svn_wc_status_merged;
524     case 'C': return svn_wc_status_conflicted;
525     case 'I': return svn_wc_status_ignored;
526     case '~': return svn_wc_status_obstructed;
527     case 'X': return svn_wc_status_external;
528     case ':': return svn_wc_status_incomplete;
529     default:  return (enum svn_wc_status_kind)0;
530     }
531 }
532 
533 /* Write a serial representation of (some fields of) STATUS to STREAM.
534  */
535 static svn_error_t *
wc_status_serialize(svn_stream_t * stream,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)536 wc_status_serialize(svn_stream_t *stream,
537                     const svn_wc_status3_t *status,
538                     apr_pool_t *scratch_pool)
539 {
540   SVN_ERR(svn_stream_printf(stream, scratch_pool, "%c %c%c%c %ld",
541                             kind_to_char(status->kind),
542                             status_to_char(status->node_status),
543                             status_to_char(status->text_status),
544                             status_to_char(status->prop_status),
545                             status->revision));
546   return SVN_NO_ERROR;
547 }
548 
549 /* Read a serial representation of (some fields of) STATUS from STREAM.
550  */
551 static svn_error_t *
wc_status_unserialize(svn_wc_status3_t * status,svn_stream_t * stream,apr_pool_t * result_pool)552 wc_status_unserialize(svn_wc_status3_t *status,
553                       svn_stream_t *stream,
554                       apr_pool_t *result_pool)
555 {
556   svn_stringbuf_t *sb;
557   char *string;
558 
559   SVN_ERR(svn_stringbuf_from_stream(&sb, stream, 100, result_pool));
560   string = sb->data;
561   status->kind = char_to_kind(string[0]);
562   status->node_status = char_to_status(string[2]);
563   status->text_status = char_to_status(string[3]);
564   status->prop_status = char_to_status(string[4]);
565   sscanf(string + 6, "%ld", &status->revision);
566   return SVN_NO_ERROR;
567 }
568 
569 /* Write status to shelf storage.
570  */
571 static svn_error_t *
status_write(svn_client__shelf2_version_t * shelf_version,const char * relpath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)572 status_write(svn_client__shelf2_version_t *shelf_version,
573              const char *relpath,
574              const svn_wc_status3_t *status,
575              apr_pool_t *scratch_pool)
576 {
577   char *file_abspath;
578   svn_stream_t *stream;
579 
580   SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath,
581                                scratch_pool, scratch_pool));
582   SVN_ERR(svn_stream_open_writable(&stream, file_abspath,
583                                    scratch_pool, scratch_pool));
584   SVN_ERR(wc_status_serialize(stream, status, scratch_pool));
585   SVN_ERR(svn_stream_close(stream));
586   return SVN_NO_ERROR;
587 }
588 
589 /* Read status from shelf storage.
590  */
591 static svn_error_t *
status_read(svn_wc_status3_t ** status,svn_client__shelf2_version_t * shelf_version,const char * relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)592 status_read(svn_wc_status3_t **status,
593             svn_client__shelf2_version_t *shelf_version,
594             const char *relpath,
595             apr_pool_t *result_pool,
596             apr_pool_t *scratch_pool)
597 {
598   svn_wc_status3_t *s = status_create(result_pool);
599   char *file_abspath;
600   svn_stream_t *stream;
601 
602   SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath,
603                                scratch_pool, scratch_pool));
604   SVN_ERR(svn_stream_open_readonly(&stream, file_abspath,
605                                    scratch_pool, scratch_pool));
606   SVN_ERR(wc_status_unserialize(s, stream, result_pool));
607   SVN_ERR(svn_stream_close(stream));
608 
609   s->changelist = apr_psprintf(result_pool, "svn:shelf:%s",
610                                shelf_version->shelf->name);
611   *status = s;
612   return SVN_NO_ERROR;
613 }
614 
615 /* A visitor function type for use with shelf_status_walk().
616  * The same as svn_wc_status_func4_t except relpath instead of abspath.
617  * Only some fields in STATUS are available.
618  */
619 typedef svn_error_t *(*shelf_status_visitor_t)(void *baton,
620                                                const char *relpath,
621                                                svn_wc_status3_t *status,
622                                                apr_pool_t *scratch_pool);
623 
624 /* Baton for shelved_files_walk_visitor(). */
625 struct shelf_status_baton_t
626 {
627   svn_client__shelf2_version_t *shelf_version;
628   const char *top_relpath;
629   const char *walk_root_abspath;
630   shelf_status_visitor_t walk_func;
631   void *walk_baton;
632 };
633 
634 /* Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved
635  * 'binary' file stored at ABSPATH.
636  * Implements svn_io_walk_func_t. */
637 static svn_error_t *
shelf_status_visitor(void * baton,const char * abspath,const apr_finfo_t * finfo,apr_pool_t * scratch_pool)638 shelf_status_visitor(void *baton,
639                      const char *abspath,
640                      const apr_finfo_t *finfo,
641                      apr_pool_t *scratch_pool)
642 {
643   struct shelf_status_baton_t *b = baton;
644   const char *relpath;
645 
646   relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath);
647   if (finfo->filetype == APR_REG
648       && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0))
649     {
650       svn_wc_status3_t *s;
651 
652       relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5);
653       if (!svn_relpath_skip_ancestor(b->top_relpath, relpath))
654         return SVN_NO_ERROR;
655 
656       SVN_ERR(status_read(&s, b->shelf_version, relpath,
657                           scratch_pool, scratch_pool));
658       SVN_ERR(b->walk_func(b->walk_baton, relpath, s, scratch_pool));
659     }
660   return SVN_NO_ERROR;
661 }
662 
663 /* Report the shelved status of the path SHELF_VERSION:WC_RELPATH
664  * via WALK_FUNC(WALK_BATON, ...).
665  */
666 static svn_error_t *
shelf_status_visit_path(svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,shelf_status_visitor_t walk_func,void * walk_baton,apr_pool_t * scratch_pool)667 shelf_status_visit_path(svn_client__shelf2_version_t *shelf_version,
668                         const char *wc_relpath,
669                         shelf_status_visitor_t walk_func,
670                         void *walk_baton,
671                         apr_pool_t *scratch_pool)
672 {
673   struct shelf_status_baton_t baton;
674   char *abspath;
675   apr_finfo_t finfo;
676 
677   baton.shelf_version = shelf_version;
678   baton.top_relpath = wc_relpath;
679   baton.walk_root_abspath = shelf_version->files_dir_abspath;
680   baton.walk_func = walk_func;
681   baton.walk_baton = walk_baton;
682   SVN_ERR(get_metadata_abspath(&abspath, shelf_version, wc_relpath,
683                                scratch_pool, scratch_pool));
684   SVN_ERR(svn_io_stat(&finfo, abspath, APR_FINFO_TYPE, scratch_pool));
685   SVN_ERR(shelf_status_visitor(&baton, abspath, &finfo, scratch_pool));
686   return SVN_NO_ERROR;
687 }
688 
689 /* Report the shelved status of all the shelved paths in SHELF_VERSION
690  * via WALK_FUNC(WALK_BATON, ...).
691  */
692 static svn_error_t *
shelf_status_walk(svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,shelf_status_visitor_t walk_func,void * walk_baton,apr_pool_t * scratch_pool)693 shelf_status_walk(svn_client__shelf2_version_t *shelf_version,
694                   const char *wc_relpath,
695                   shelf_status_visitor_t walk_func,
696                   void *walk_baton,
697                   apr_pool_t *scratch_pool)
698 {
699   struct shelf_status_baton_t baton;
700   svn_error_t *err;
701 
702   baton.shelf_version = shelf_version;
703   baton.top_relpath = wc_relpath;
704   baton.walk_root_abspath = shelf_version->files_dir_abspath;
705   baton.walk_func = walk_func;
706   baton.walk_baton = walk_baton;
707   err = svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/,
708                          shelf_status_visitor, &baton,
709                          scratch_pool);
710   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
711     svn_error_clear(err);
712   else
713     SVN_ERR(err);
714 
715   return SVN_NO_ERROR;
716 }
717 
718 typedef struct wc_status_baton_t
719 {
720   svn_client__shelf2_version_t *shelf_version;
721   svn_wc_status_func4_t walk_func;
722   void *walk_baton;
723 } wc_status_baton_t;
724 
725 static svn_error_t *
wc_status_visitor(void * baton,const char * relpath,svn_wc_status3_t * status,apr_pool_t * scratch_pool)726 wc_status_visitor(void *baton,
727                       const char *relpath,
728                       svn_wc_status3_t *status,
729                       apr_pool_t *scratch_pool)
730 {
731   struct wc_status_baton_t *b = baton;
732   svn_client__shelf2_t *shelf = b->shelf_version->shelf;
733   const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath,
734                                         scratch_pool);
735   SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool));
736   return SVN_NO_ERROR;
737 }
738 
739 svn_error_t *
svn_client__shelf2_version_status_walk(svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,svn_wc_status_func4_t walk_func,void * walk_baton,apr_pool_t * scratch_pool)740 svn_client__shelf2_version_status_walk(svn_client__shelf2_version_t *shelf_version,
741                                        const char *wc_relpath,
742                                        svn_wc_status_func4_t walk_func,
743                                        void *walk_baton,
744                                        apr_pool_t *scratch_pool)
745 {
746   wc_status_baton_t baton;
747 
748   baton.shelf_version = shelf_version;
749   baton.walk_func = walk_func;
750   baton.walk_baton = walk_baton;
751   SVN_ERR(shelf_status_walk(shelf_version, wc_relpath,
752                             wc_status_visitor, &baton,
753                             scratch_pool));
754   return SVN_NO_ERROR;
755 }
756 
757 /*-------------------------------------------------------------------------*/
758 /* Shelf Storage */
759 
760 /* A baton for use with write_changes_visitor(). */
761 typedef struct write_changes_baton_t {
762   const char *wc_root_abspath;
763   svn_client__shelf2_version_t *shelf_version;
764   svn_client_ctx_t *ctx;
765   svn_boolean_t any_shelved;  /* were any paths successfully shelved? */
766   svn_client_status_func_t was_shelved_func;
767   void *was_shelved_baton;
768   svn_client_status_func_t was_not_shelved_func;
769   void *was_not_shelved_baton;
770   apr_pool_t *pool;  /* pool for data in 'unshelvable', etc. */
771 } write_changes_baton_t;
772 
773 /*  */
774 static svn_error_t *
notify_shelved(write_changes_baton_t * wb,const char * wc_relpath,const char * local_abspath,const svn_wc_status3_t * wc_status,apr_pool_t * scratch_pool)775 notify_shelved(write_changes_baton_t *wb,
776                const char *wc_relpath,
777                const char *local_abspath,
778                const svn_wc_status3_t *wc_status,
779                apr_pool_t *scratch_pool)
780 {
781   if (wb->was_shelved_func)
782     {
783       svn_client_status_t *cst;
784 
785       SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath,
786                                         wc_status,
787                                         scratch_pool, scratch_pool));
788       SVN_ERR(wb->was_shelved_func(wb->was_shelved_baton,
789                                    wc_relpath, cst, scratch_pool));
790     }
791 
792   wb->any_shelved = TRUE;
793   return SVN_NO_ERROR;
794 }
795 
796 /*  */
797 static svn_error_t *
notify_not_shelved(write_changes_baton_t * wb,const char * wc_relpath,const char * local_abspath,const svn_wc_status3_t * wc_status,apr_pool_t * scratch_pool)798 notify_not_shelved(write_changes_baton_t *wb,
799                    const char *wc_relpath,
800                    const char *local_abspath,
801                    const svn_wc_status3_t *wc_status,
802                    apr_pool_t *scratch_pool)
803 {
804   if (wb->was_not_shelved_func)
805     {
806       svn_client_status_t *cst;
807 
808       SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath,
809                                         wc_status,
810                                         scratch_pool, scratch_pool));
811       SVN_ERR(wb->was_not_shelved_func(wb->was_not_shelved_baton,
812                                        wc_relpath, cst, scratch_pool));
813     }
814 
815   return SVN_NO_ERROR;
816 }
817 
818 /* Read BASE_PROPS and WORK_PROPS from the WC, setting each to null if
819  * the node has no base or working version (respectively).
820  */
821 static svn_error_t *
read_props_from_wc(apr_hash_t ** base_props,apr_hash_t ** work_props,enum svn_wc_status_kind node_status,const char * from_wc_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)822 read_props_from_wc(apr_hash_t **base_props,
823                    apr_hash_t **work_props,
824                    enum svn_wc_status_kind node_status,
825                    const char *from_wc_abspath,
826                    svn_client_ctx_t *ctx,
827                    apr_pool_t *result_pool,
828                    apr_pool_t *scratch_pool)
829 {
830   if (node_status != svn_wc_status_added)
831     SVN_ERR(svn_wc_get_pristine_props(base_props, ctx->wc_ctx, from_wc_abspath,
832                                       result_pool, scratch_pool));
833   else
834     *base_props = NULL;
835   if (node_status != svn_wc_status_deleted)
836     SVN_ERR(svn_wc_prop_list2(work_props, ctx->wc_ctx, from_wc_abspath,
837                               result_pool, scratch_pool));
838   else
839     *work_props = NULL;
840   return SVN_NO_ERROR;
841 }
842 
843 /* Write BASE_PROPS and WORK_PROPS to storage in SHELF_VERSION:WC_RELPATH.
844  */
845 static svn_error_t *
write_props_to_shelf(svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,apr_hash_t * base_props,apr_hash_t * work_props,apr_pool_t * scratch_pool)846 write_props_to_shelf(svn_client__shelf2_version_t *shelf_version,
847                      const char *wc_relpath,
848                      apr_hash_t *base_props,
849                      apr_hash_t *work_props,
850                      apr_pool_t *scratch_pool)
851 {
852   char *stored_props_abspath;
853   svn_stream_t *stream;
854 
855   if (base_props)
856     {
857       SVN_ERR(get_base_props_abspath(&stored_props_abspath,
858                                      shelf_version, wc_relpath,
859                                      scratch_pool, scratch_pool));
860       SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath,
861                                        scratch_pool, scratch_pool));
862       SVN_ERR(svn_hash_write2(base_props, stream, NULL, scratch_pool));
863       SVN_ERR(svn_stream_close(stream));
864     }
865 
866   if (work_props)
867     {
868       SVN_ERR(get_working_props_abspath(&stored_props_abspath,
869                                         shelf_version, wc_relpath,
870                                         scratch_pool, scratch_pool));
871       SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath,
872                                        scratch_pool, scratch_pool));
873       SVN_ERR(svn_hash_write2(work_props, stream, NULL, scratch_pool));
874       SVN_ERR(svn_stream_close(stream));
875     }
876 
877   return SVN_NO_ERROR;
878 }
879 
880 /* Read BASE_PROPS and WORK_PROPS from storage in SHELF_VERSION:WC_RELPATH.
881  */
882 static svn_error_t *
read_props_from_shelf(apr_hash_t ** base_props,apr_hash_t ** work_props,enum svn_wc_status_kind node_status,svn_client__shelf2_version_t * shelf_version,const char * wc_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)883 read_props_from_shelf(apr_hash_t **base_props,
884                       apr_hash_t **work_props,
885                       enum svn_wc_status_kind node_status,
886                       svn_client__shelf2_version_t *shelf_version,
887                       const char *wc_relpath,
888                       apr_pool_t *result_pool,
889                       apr_pool_t *scratch_pool)
890 {
891   char *stored_props_abspath;
892   svn_stream_t *stream;
893 
894   if (node_status != svn_wc_status_added)
895     {
896       *base_props = apr_hash_make(result_pool);
897       SVN_ERR(get_base_props_abspath(&stored_props_abspath,
898                                      shelf_version, wc_relpath,
899                                      scratch_pool, scratch_pool));
900       SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath,
901                                        scratch_pool, scratch_pool));
902       SVN_ERR(svn_hash_read2(*base_props, stream, NULL, scratch_pool));
903       SVN_ERR(svn_stream_close(stream));
904     }
905   else
906     *base_props = NULL;
907 
908   if (node_status != svn_wc_status_deleted)
909     {
910       *work_props = apr_hash_make(result_pool);
911       SVN_ERR(get_working_props_abspath(&stored_props_abspath,
912                                         shelf_version, wc_relpath,
913                                         scratch_pool, scratch_pool));
914       SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath,
915                                        scratch_pool, scratch_pool));
916       SVN_ERR(svn_hash_read2(*work_props, stream, NULL, scratch_pool));
917       SVN_ERR(svn_stream_close(stream));
918     }
919   else
920     *work_props = NULL;
921 
922   return SVN_NO_ERROR;
923 }
924 
925 /* Store metadata for any node, and base and working files if it's a file.
926  *
927  * Copy the WC base and working files at FROM_WC_ABSPATH to the storage
928  * area in SHELF_VERSION.
929  */
930 static svn_error_t *
store_file(const char * from_wc_abspath,const char * wc_relpath,svn_client__shelf2_version_t * shelf_version,const svn_wc_status3_t * status,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)931 store_file(const char *from_wc_abspath,
932            const char *wc_relpath,
933            svn_client__shelf2_version_t *shelf_version,
934            const svn_wc_status3_t *status,
935            svn_client_ctx_t *ctx,
936            apr_pool_t *scratch_pool)
937 {
938   char *stored_abspath;
939   apr_hash_t *base_props, *work_props;
940 
941   SVN_ERR(get_working_file_abspath(&stored_abspath,
942                                    shelf_version, wc_relpath,
943                                    scratch_pool, scratch_pool));
944   SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(stored_abspath,
945                                                          scratch_pool),
946                                       scratch_pool));
947   SVN_ERR(status_write(shelf_version, wc_relpath,
948                        status, scratch_pool));
949 
950   /* properties */
951   SVN_ERR(read_props_from_wc(&base_props, &work_props,
952                              status->node_status,
953                              from_wc_abspath, ctx,
954                              scratch_pool, scratch_pool));
955   SVN_ERR(write_props_to_shelf(shelf_version, wc_relpath,
956                                base_props, work_props,
957                                scratch_pool));
958 
959   /* file text */
960   if (status->kind == svn_node_file)
961     {
962       svn_stream_t *wc_base_stream;
963       svn_node_kind_t work_kind;
964 
965       /* Copy the base file (copy-from base, if copied/moved), if present */
966       SVN_ERR(svn_wc_get_pristine_contents2(&wc_base_stream,
967                                             ctx->wc_ctx, from_wc_abspath,
968                                             scratch_pool, scratch_pool));
969       if (wc_base_stream)
970         {
971           char *stored_base_abspath;
972           svn_stream_t *stored_base_stream;
973 
974           SVN_ERR(get_base_file_abspath(&stored_base_abspath,
975                                         shelf_version, wc_relpath,
976                                         scratch_pool, scratch_pool));
977           SVN_ERR(svn_stream_open_writable(&stored_base_stream,
978                                            stored_base_abspath,
979                                            scratch_pool, scratch_pool));
980           SVN_ERR(svn_stream_copy3(wc_base_stream, stored_base_stream,
981                                    NULL, NULL, scratch_pool));
982         }
983 
984       /* Copy the working file, if present */
985       SVN_ERR(svn_io_check_path(from_wc_abspath, &work_kind, scratch_pool));
986       if (work_kind == svn_node_file)
987         {
988           SVN_ERR(svn_io_copy_file(from_wc_abspath, stored_abspath,
989                                    TRUE /*copy_perms*/, scratch_pool));
990         }
991     }
992   return SVN_NO_ERROR;
993 }
994 
995 /* An implementation of svn_wc_status_func4_t. */
996 static svn_error_t *
write_changes_visitor(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)997 write_changes_visitor(void *baton,
998                       const char *local_abspath,
999                       const svn_wc_status3_t *status,
1000                       apr_pool_t *scratch_pool)
1001 {
1002   write_changes_baton_t *wb = baton;
1003   const char *wc_relpath = svn_dirent_skip_ancestor(wb->wc_root_abspath,
1004                                                     local_abspath);
1005 
1006   /* Catch any conflict, even a tree conflict on a path that has
1007      node-status 'unversioned'. */
1008   if (status->conflicted)
1009     {
1010       SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1011                                  status, scratch_pool));
1012     }
1013   else switch (status->node_status)
1014     {
1015       case svn_wc_status_deleted:
1016       case svn_wc_status_added:
1017       case svn_wc_status_replaced:
1018         if (status->kind != svn_node_file
1019             || status->copied)
1020           {
1021             SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1022                                        status, scratch_pool));
1023             break;
1024           }
1025         /* fall through */
1026       case svn_wc_status_modified:
1027       {
1028         /* Store metadata, and base and working versions if it's a file */
1029         SVN_ERR(store_file(local_abspath, wc_relpath, wb->shelf_version,
1030                            status, wb->ctx, scratch_pool));
1031         SVN_ERR(notify_shelved(wb, wc_relpath, local_abspath,
1032                                status, scratch_pool));
1033         break;
1034       }
1035 
1036       case svn_wc_status_incomplete:
1037         if ((status->text_status != svn_wc_status_normal
1038              && status->text_status != svn_wc_status_none)
1039             || (status->prop_status != svn_wc_status_normal
1040                 && status->prop_status != svn_wc_status_none))
1041           {
1042             /* Incomplete, but local modifications */
1043             SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1044                                        status, scratch_pool));
1045           }
1046         break;
1047 
1048       case svn_wc_status_conflicted:
1049       case svn_wc_status_missing:
1050       case svn_wc_status_obstructed:
1051         SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
1052                                    status, scratch_pool));
1053         break;
1054 
1055       case svn_wc_status_normal:
1056       case svn_wc_status_ignored:
1057       case svn_wc_status_none:
1058       case svn_wc_status_external:
1059       case svn_wc_status_unversioned:
1060       default:
1061         break;
1062     }
1063 
1064   return SVN_NO_ERROR;
1065 }
1066 
1067 /* A baton for use with changelist_filter_func(). */
1068 struct changelist_filter_baton_t {
1069   apr_hash_t *changelist_hash;
1070   svn_wc_status_func4_t status_func;
1071   void *status_baton;
1072 };
1073 
1074 /* Filter out paths that are not in the requested changelist(s).
1075  * Implements svn_wc_status_func4_t. */
1076 static svn_error_t *
changelist_filter_func(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)1077 changelist_filter_func(void *baton,
1078                        const char *local_abspath,
1079                        const svn_wc_status3_t *status,
1080                        apr_pool_t *scratch_pool)
1081 {
1082   struct changelist_filter_baton_t *b = baton;
1083 
1084   if (b->changelist_hash
1085       && (! status->changelist
1086           || ! svn_hash_gets(b->changelist_hash, status->changelist)))
1087     {
1088       return SVN_NO_ERROR;
1089     }
1090 
1091   SVN_ERR(b->status_func(b->status_baton, local_abspath, status,
1092                          scratch_pool));
1093   return SVN_NO_ERROR;
1094 }
1095 
1096 /*
1097  * Walk the WC tree(s) rooted at PATHS, to depth DEPTH, omitting paths that
1098  * are not in one of the CHANGELISTS (if not null).
1099  *
1100  * Call STATUS_FUNC(STATUS_BATON, ...) for each visited path.
1101  *
1102  * PATHS are absolute, or relative to CWD.
1103  */
1104 static svn_error_t *
wc_walk_status_multi(const apr_array_header_t * paths,svn_depth_t depth,const apr_array_header_t * changelists,svn_wc_status_func4_t status_func,void * status_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1105 wc_walk_status_multi(const apr_array_header_t *paths,
1106                      svn_depth_t depth,
1107                      const apr_array_header_t *changelists,
1108                      svn_wc_status_func4_t status_func,
1109                      void *status_baton,
1110                      svn_client_ctx_t *ctx,
1111                      apr_pool_t *scratch_pool)
1112 {
1113   struct changelist_filter_baton_t cb = {0};
1114   int i;
1115 
1116   if (changelists && changelists->nelts)
1117     SVN_ERR(svn_hash_from_cstring_keys(&cb.changelist_hash,
1118                                        changelists, scratch_pool));
1119   cb.status_func = status_func;
1120   cb.status_baton = status_baton;
1121 
1122   for (i = 0; i < paths->nelts; i++)
1123     {
1124       const char *path = APR_ARRAY_IDX(paths, i, const char *);
1125 
1126       if (svn_path_is_url(path))
1127         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1128                                  _("'%s' is not a local path"), path);
1129       SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool));
1130 
1131       SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, path, depth,
1132                                  FALSE /*get_all*/, FALSE /*no_ignore*/,
1133                                  FALSE /*ignore_text_mods*/,
1134                                  NULL /*ignore_patterns*/,
1135                                  changelist_filter_func, &cb,
1136                                  ctx->cancel_func, ctx->cancel_baton,
1137                                  scratch_pool));
1138     }
1139 
1140   return SVN_NO_ERROR;
1141 }
1142 
1143 /** Write local changes to the shelf storage.
1144  *
1145  * @a paths, @a depth, @a changelists: The selection of local paths to diff.
1146  *
1147  * @a paths are relative to CWD (or absolute).
1148  */
1149 static svn_error_t *
shelf_write_changes(svn_boolean_t * any_shelved,svn_client__shelf2_version_t * shelf_version,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,const char * wc_root_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1150 shelf_write_changes(svn_boolean_t *any_shelved,
1151                     svn_client__shelf2_version_t *shelf_version,
1152                     const apr_array_header_t *paths,
1153                     svn_depth_t depth,
1154                     const apr_array_header_t *changelists,
1155                     svn_client_status_func_t shelved_func,
1156                     void *shelved_baton,
1157                     svn_client_status_func_t not_shelved_func,
1158                     void *not_shelved_baton,
1159                     const char *wc_root_abspath,
1160                     svn_client_ctx_t *ctx,
1161                     apr_pool_t *result_pool,
1162                     apr_pool_t *scratch_pool)
1163 {
1164   write_changes_baton_t wb = { 0 };
1165 
1166   wb.wc_root_abspath = wc_root_abspath;
1167   wb.shelf_version = shelf_version;
1168   wb.ctx = ctx;
1169   wb.any_shelved = FALSE;
1170   wb.was_shelved_func = shelved_func;
1171   wb.was_shelved_baton = shelved_baton;
1172   wb.was_not_shelved_func = not_shelved_func;
1173   wb.was_not_shelved_baton = not_shelved_baton;
1174   wb.pool = result_pool;
1175 
1176   /* Walk the WC */
1177   SVN_ERR(wc_walk_status_multi(paths, depth, changelists,
1178                                write_changes_visitor, &wb,
1179                                ctx, scratch_pool));
1180 
1181   *any_shelved = wb.any_shelved;
1182   return SVN_NO_ERROR;
1183 }
1184 
1185 /* Construct a shelf object representing an empty shelf: no versions,
1186  * no revprops, no looking to see if such a shelf exists on disk.
1187  */
1188 static svn_error_t *
shelf_construct(svn_client__shelf2_t ** shelf_p,const char * name,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool)1189 shelf_construct(svn_client__shelf2_t **shelf_p,
1190                 const char *name,
1191                 const char *local_abspath,
1192                 svn_client_ctx_t *ctx,
1193                 apr_pool_t *result_pool)
1194 {
1195   svn_client__shelf2_t *shelf = apr_palloc(result_pool, sizeof(*shelf));
1196   char *shelves_dir;
1197 
1198   SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath,
1199                                  local_abspath, ctx,
1200                                  result_pool, result_pool));
1201   SVN_ERR(get_shelves_dir(&shelves_dir,
1202                           ctx->wc_ctx, local_abspath,
1203                           result_pool, result_pool));
1204   shelf->shelves_dir = shelves_dir;
1205   shelf->ctx = ctx;
1206   shelf->pool = result_pool;
1207 
1208   shelf->name = apr_pstrdup(result_pool, name);
1209   shelf->revprops = apr_hash_make(result_pool);
1210   shelf->max_version = 0;
1211 
1212   *shelf_p = shelf;
1213   return SVN_NO_ERROR;
1214 }
1215 
1216 svn_error_t *
svn_client__shelf2_open_existing(svn_client__shelf2_t ** shelf_p,const char * name,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool)1217 svn_client__shelf2_open_existing(svn_client__shelf2_t **shelf_p,
1218                                  const char *name,
1219                                  const char *local_abspath,
1220                                  svn_client_ctx_t *ctx,
1221                                  apr_pool_t *result_pool)
1222 {
1223   SVN_ERR(shelf_construct(shelf_p, name,
1224                           local_abspath, ctx, result_pool));
1225   SVN_ERR(shelf_read_revprops(*shelf_p, result_pool));
1226   SVN_ERR(shelf_read_current(*shelf_p, result_pool));
1227   if ((*shelf_p)->max_version < 0)
1228     {
1229       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1230                                _("Shelf '%s' not found"),
1231                                name);
1232     }
1233   return SVN_NO_ERROR;
1234 }
1235 
1236 svn_error_t *
svn_client__shelf2_open_or_create(svn_client__shelf2_t ** shelf_p,const char * name,const char * local_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool)1237 svn_client__shelf2_open_or_create(svn_client__shelf2_t **shelf_p,
1238                                   const char *name,
1239                                   const char *local_abspath,
1240                                   svn_client_ctx_t *ctx,
1241                                   apr_pool_t *result_pool)
1242 {
1243   svn_client__shelf2_t *shelf;
1244 
1245   SVN_ERR(shelf_construct(&shelf, name,
1246                           local_abspath, ctx, result_pool));
1247   SVN_ERR(shelf_read_revprops(shelf, result_pool));
1248   SVN_ERR(shelf_read_current(shelf, result_pool));
1249   if (shelf->max_version < 0)
1250     {
1251       shelf->max_version = 0;
1252       SVN_ERR(shelf_write_current(shelf, result_pool));
1253     }
1254   *shelf_p = shelf;
1255   return SVN_NO_ERROR;
1256 }
1257 
1258 svn_error_t *
svn_client__shelf2_close(svn_client__shelf2_t * shelf,apr_pool_t * scratch_pool)1259 svn_client__shelf2_close(svn_client__shelf2_t *shelf,
1260                          apr_pool_t *scratch_pool)
1261 {
1262   return SVN_NO_ERROR;
1263 }
1264 
1265 svn_error_t *
svn_client__shelf2_delete(const char * name,const char * local_abspath,svn_boolean_t dry_run,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1266 svn_client__shelf2_delete(const char *name,
1267                           const char *local_abspath,
1268                           svn_boolean_t dry_run,
1269                           svn_client_ctx_t *ctx,
1270                           apr_pool_t *scratch_pool)
1271 {
1272   svn_client__shelf2_t *shelf;
1273   int i;
1274   char *abspath;
1275 
1276   SVN_ERR(svn_client__shelf2_open_existing(&shelf, name,
1277                                            local_abspath, ctx, scratch_pool));
1278 
1279   /* Remove the versions. */
1280   for (i = shelf->max_version; i > 0; i--)
1281     {
1282       SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
1283     }
1284 
1285   /* Remove the other files */
1286   SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool));
1287   SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
1288   SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool));
1289   SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
1290 
1291   SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool));
1292   return SVN_NO_ERROR;
1293 }
1294 
1295 /* Baton for paths_changed_visitor(). */
1296 struct paths_changed_walk_baton_t
1297 {
1298   apr_hash_t *paths_hash;
1299   svn_boolean_t as_abspath;
1300   const char *wc_root_abspath;
1301   apr_pool_t *pool;
1302 };
1303 
1304 /* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file.
1305  * Implements shelved_files_walk_func_t. */
1306 static svn_error_t *
paths_changed_visitor(void * baton,const char * relpath,svn_wc_status3_t * s,apr_pool_t * scratch_pool)1307 paths_changed_visitor(void *baton,
1308                       const char *relpath,
1309                       svn_wc_status3_t *s,
1310                       apr_pool_t *scratch_pool)
1311 {
1312   struct paths_changed_walk_baton_t *b = baton;
1313 
1314   relpath = (b->as_abspath
1315              ? svn_dirent_join(b->wc_root_abspath, relpath, b->pool)
1316              : apr_pstrdup(b->pool, relpath));
1317   svn_hash_sets(b->paths_hash, relpath, relpath);
1318   return SVN_NO_ERROR;
1319 }
1320 
1321 /* Get the paths changed, relative to WC root or as abspaths, as a hash
1322  * and/or an array (in no particular order).
1323  */
1324 static svn_error_t *
shelf_paths_changed(apr_hash_t ** paths_hash_p,apr_array_header_t ** paths_array_p,svn_client__shelf2_version_t * shelf_version,svn_boolean_t as_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1325 shelf_paths_changed(apr_hash_t **paths_hash_p,
1326                     apr_array_header_t **paths_array_p,
1327                     svn_client__shelf2_version_t *shelf_version,
1328                     svn_boolean_t as_abspath,
1329                     apr_pool_t *result_pool,
1330                     apr_pool_t *scratch_pool)
1331 {
1332   svn_client__shelf2_t *shelf = shelf_version->shelf;
1333   apr_hash_t *paths_hash = apr_hash_make(result_pool);
1334   struct paths_changed_walk_baton_t baton;
1335 
1336   baton.paths_hash = paths_hash;
1337   baton.as_abspath = as_abspath;
1338   baton.wc_root_abspath = shelf->wc_root_abspath;
1339   baton.pool = result_pool;
1340   SVN_ERR(shelf_status_walk(shelf_version, "",
1341                             paths_changed_visitor, &baton,
1342                             scratch_pool));
1343 
1344   if (paths_hash_p)
1345     *paths_hash_p = paths_hash;
1346   if (paths_array_p)
1347     SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool));
1348 
1349   return SVN_NO_ERROR;
1350 }
1351 
1352 svn_error_t *
svn_client__shelf2_paths_changed(apr_hash_t ** affected_paths,svn_client__shelf2_version_t * shelf_version,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1353 svn_client__shelf2_paths_changed(apr_hash_t **affected_paths,
1354                                  svn_client__shelf2_version_t *shelf_version,
1355                                  apr_pool_t *result_pool,
1356                                  apr_pool_t *scratch_pool)
1357 {
1358   SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version,
1359                               FALSE /*as_abspath*/,
1360                               result_pool, scratch_pool));
1361   return SVN_NO_ERROR;
1362 }
1363 
1364 /* Send a notification */
1365 static svn_error_t *
send_notification(const char * local_abspath,svn_wc_notify_action_t action,svn_node_kind_t kind,svn_wc_notify_state_t content_state,svn_wc_notify_state_t prop_state,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)1366 send_notification(const char *local_abspath,
1367                   svn_wc_notify_action_t action,
1368                   svn_node_kind_t kind,
1369                   svn_wc_notify_state_t content_state,
1370                   svn_wc_notify_state_t prop_state,
1371                   svn_wc_notify_func2_t notify_func,
1372                   void *notify_baton,
1373                   apr_pool_t *scratch_pool)
1374 {
1375   if (notify_func)
1376     {
1377       svn_wc_notify_t *notify
1378         = svn_wc_create_notify(local_abspath, action, scratch_pool);
1379 
1380       notify->kind = kind;
1381       notify->content_state = content_state;
1382       notify->prop_state = prop_state;
1383       notify_func(notify_baton, notify, scratch_pool);
1384     }
1385 
1386   return SVN_NO_ERROR;
1387 }
1388 
1389 /* Merge a shelved change into WC_ABSPATH.
1390  */
1391 static svn_error_t *
wc_file_merge(const char * wc_abspath,const char * left_file,const char * right_file,apr_hash_t * left_props,apr_hash_t * right_props,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1392 wc_file_merge(const char *wc_abspath,
1393               const char *left_file,
1394               const char *right_file,
1395               /*const*/ apr_hash_t *left_props,
1396               /*const*/ apr_hash_t *right_props,
1397               svn_client_ctx_t *ctx,
1398               apr_pool_t *scratch_pool)
1399 {
1400   svn_wc_notify_state_t property_state;
1401   svn_boolean_t has_local_mods;
1402   enum svn_wc_merge_outcome_t content_outcome;
1403   const char *target_label, *left_label, *right_label;
1404   apr_array_header_t *prop_changes;
1405 
1406   /* xgettext: the '.working', '.merge-left' and '.merge-right' strings
1407      are used to tag onto a file name in case of a merge conflict */
1408   target_label = apr_psprintf(scratch_pool, _(".working"));
1409   left_label = apr_psprintf(scratch_pool, _(".merge-left"));
1410   right_label = apr_psprintf(scratch_pool, _(".merge-right"));
1411 
1412   SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1413   SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx,
1414                                   wc_abspath, FALSE, scratch_pool));
1415 
1416   /* Do property merge and text merge in one step so that keyword expansion
1417      takes into account the new property values. */
1418   SVN_WC__CALL_WITH_WRITE_LOCK(
1419     svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx,
1420                   left_file, right_file, wc_abspath,
1421                   left_label, right_label, target_label,
1422                   NULL, NULL, /*left, right conflict-versions*/
1423                   FALSE /*dry_run*/, NULL /*diff3_cmd*/,
1424                   NULL /*merge_options*/,
1425                   left_props, prop_changes,
1426                   NULL, NULL,
1427                   ctx->cancel_func, ctx->cancel_baton,
1428                   scratch_pool),
1429     ctx->wc_ctx, wc_abspath,
1430     FALSE /*lock_anchor*/, scratch_pool);
1431 
1432   return SVN_NO_ERROR;
1433 }
1434 
1435 /* Merge a shelved change (of properties) into the dir at WC_ABSPATH.
1436  */
1437 static svn_error_t *
wc_dir_props_merge(const char * wc_abspath,apr_hash_t * left_props,apr_hash_t * right_props,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1438 wc_dir_props_merge(const char *wc_abspath,
1439                    /*const*/ apr_hash_t *left_props,
1440                    /*const*/ apr_hash_t *right_props,
1441                    svn_client_ctx_t *ctx,
1442                    apr_pool_t *result_pool,
1443                    apr_pool_t *scratch_pool)
1444 {
1445   apr_array_header_t *prop_changes;
1446   svn_wc_notify_state_t property_state;
1447 
1448   SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1449   SVN_WC__CALL_WITH_WRITE_LOCK(
1450     svn_wc_merge_props3(&property_state, ctx->wc_ctx,
1451                         wc_abspath,
1452                         NULL, NULL, /*left, right conflict-versions*/
1453                         left_props, prop_changes,
1454                         FALSE /*dry_run*/,
1455                         NULL, NULL,
1456                         ctx->cancel_func, ctx->cancel_baton,
1457                         scratch_pool),
1458     ctx->wc_ctx, wc_abspath,
1459     FALSE /*lock_anchor*/, scratch_pool);
1460 
1461   return SVN_NO_ERROR;
1462 }
1463 
1464 /* Apply a shelved "delete" to TO_WC_ABSPATH.
1465  */
1466 static svn_error_t *
wc_node_delete(const char * to_wc_abspath,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1467 wc_node_delete(const char *to_wc_abspath,
1468                svn_client_ctx_t *ctx,
1469                apr_pool_t *scratch_pool)
1470 {
1471   SVN_WC__CALL_WITH_WRITE_LOCK(
1472     svn_wc_delete4(ctx->wc_ctx,
1473                    to_wc_abspath,
1474                    FALSE /*keep_local*/,
1475                    TRUE /*delete_unversioned_target*/,
1476                    NULL, NULL, NULL, NULL, /*cancel, notify*/
1477                    scratch_pool),
1478     ctx->wc_ctx, to_wc_abspath,
1479     TRUE /*lock_anchor*/, scratch_pool);
1480   return SVN_NO_ERROR;
1481 }
1482 
1483 /* Apply a shelved "add" to TO_WC_ABSPATH.
1484  * The node must already exist on disk, in a versioned parent dir.
1485  */
1486 static svn_error_t *
wc_node_add(const char * to_wc_abspath,apr_hash_t * work_props,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1487 wc_node_add(const char *to_wc_abspath,
1488             apr_hash_t *work_props,
1489             svn_client_ctx_t *ctx,
1490             apr_pool_t *scratch_pool)
1491 {
1492   /* If it was not already versioned, schedule the node for addition.
1493      (Do not apply autoprops, because this isn't a user-facing "add" but
1494      restoring a previously saved state.) */
1495   SVN_WC__CALL_WITH_WRITE_LOCK(
1496     svn_wc_add_from_disk3(ctx->wc_ctx,
1497                           to_wc_abspath, work_props,
1498                           FALSE /* skip checks */,
1499                           NULL, NULL, scratch_pool),
1500     ctx->wc_ctx, to_wc_abspath,
1501     TRUE /*lock_anchor*/, scratch_pool);
1502   return SVN_NO_ERROR;
1503 }
1504 
1505 /* Baton for apply_file_visitor(). */
1506 struct apply_files_baton_t
1507 {
1508   svn_client__shelf2_version_t *shelf_version;
1509   svn_boolean_t test_only;  /* only check whether it would conflict */
1510   svn_boolean_t conflict;  /* would it conflict? */
1511   svn_client_ctx_t *ctx;
1512 };
1513 
1514 /* Copy the file RELPATH from shelf binary file storage to the WC.
1515  *
1516  * If it is not already versioned, schedule the file for addition.
1517  *
1518  * Make any missing parent directories.
1519  *
1520  * In test mode (BATON->test_only): set BATON->conflict if we can't apply
1521  * the change to WC at RELPATH without conflict. But in fact, just check
1522  * if WC at RELPATH is locally modified.
1523  *
1524  * Implements shelved_files_walk_func_t. */
1525 static svn_error_t *
apply_file_visitor(void * baton,const char * relpath,svn_wc_status3_t * s,apr_pool_t * scratch_pool)1526 apply_file_visitor(void *baton,
1527                    const char *relpath,
1528                    svn_wc_status3_t *s,
1529                    apr_pool_t *scratch_pool)
1530 {
1531   struct apply_files_baton_t *b = baton;
1532   const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath;
1533   char *stored_base_abspath, *stored_work_abspath;
1534   apr_hash_t *base_props, *work_props;
1535   const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath,
1536                                               scratch_pool);
1537   const char *to_dir_abspath = svn_dirent_dirname(to_wc_abspath, scratch_pool);
1538 
1539   SVN_ERR(get_base_file_abspath(&stored_base_abspath,
1540                                 b->shelf_version, relpath,
1541                                 scratch_pool, scratch_pool));
1542   SVN_ERR(get_working_file_abspath(&stored_work_abspath,
1543                                    b->shelf_version, relpath,
1544                                    scratch_pool, scratch_pool));
1545   SVN_ERR(read_props_from_shelf(&base_props, &work_props,
1546                                 s->node_status,
1547                                 b->shelf_version, relpath,
1548                                 scratch_pool, scratch_pool));
1549 
1550   if (b->test_only)
1551     {
1552       svn_wc_status3_t *status;
1553 
1554       SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath,
1555                              scratch_pool, scratch_pool));
1556       switch (status->node_status)
1557         {
1558         case svn_wc_status_normal:
1559         case svn_wc_status_none:
1560           break;
1561         default:
1562           b->conflict = TRUE;
1563         }
1564 
1565       return SVN_NO_ERROR;
1566     }
1567 
1568   /* Handle 'delete' and the delete half of 'replace' */
1569   if (s->node_status == svn_wc_status_deleted
1570       || s->node_status == svn_wc_status_replaced)
1571     {
1572       SVN_ERR(wc_node_delete(to_wc_abspath, b->ctx, scratch_pool));
1573       if (s->node_status != svn_wc_status_replaced)
1574         {
1575           SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_delete,
1576                                     s->kind,
1577                                     svn_wc_notify_state_inapplicable,
1578                                     svn_wc_notify_state_inapplicable,
1579                                     b->ctx->notify_func2, b->ctx->notify_baton2,
1580                                     scratch_pool));
1581         }
1582     }
1583 
1584   /* If we can merge a file, do so. */
1585   if (s->node_status == svn_wc_status_modified)
1586     {
1587       if (s->kind == svn_node_dir)
1588         {
1589           SVN_ERR(wc_dir_props_merge(to_wc_abspath,
1590                                      base_props, work_props,
1591                                      b->ctx, scratch_pool, scratch_pool));
1592         }
1593       else if (s->kind == svn_node_file)
1594         {
1595           SVN_ERR(wc_file_merge(to_wc_abspath,
1596                                 stored_base_abspath, stored_work_abspath,
1597                                 base_props, work_props,
1598                                 b->ctx, scratch_pool));
1599         }
1600       SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_update,
1601                                 s->kind,
1602                                 (s->kind == svn_node_dir)
1603                                   ? svn_wc_notify_state_inapplicable
1604                                   : svn_wc_notify_state_merged,
1605                                 (s->kind == svn_node_dir)
1606                                   ? svn_wc_notify_state_merged
1607                                   : svn_wc_notify_state_unknown,
1608                                 b->ctx->notify_func2, b->ctx->notify_baton2,
1609                                 scratch_pool));
1610     }
1611 
1612   /* For an added file, copy it into the WC and ensure it's versioned. */
1613   if (s->node_status == svn_wc_status_added
1614       || s->node_status == svn_wc_status_replaced)
1615     {
1616       if (s->kind == svn_node_dir)
1617         {
1618           SVN_ERR(svn_io_make_dir_recursively(to_wc_abspath, scratch_pool));
1619         }
1620       else if (s->kind == svn_node_file)
1621         {
1622           SVN_ERR(svn_io_make_dir_recursively(to_dir_abspath, scratch_pool));
1623           SVN_ERR(svn_io_copy_file(stored_work_abspath, to_wc_abspath,
1624                                    TRUE /*copy_perms*/, scratch_pool));
1625         }
1626       SVN_ERR(wc_node_add(to_wc_abspath, work_props, b->ctx, scratch_pool));
1627       SVN_ERR(send_notification(to_wc_abspath,
1628                                 (s->node_status == svn_wc_status_replaced)
1629                                   ? svn_wc_notify_update_replace
1630                                   : svn_wc_notify_update_add,
1631                                 s->kind,
1632                                 svn_wc_notify_state_inapplicable,
1633                                 svn_wc_notify_state_inapplicable,
1634                                 b->ctx->notify_func2, b->ctx->notify_baton2,
1635                                 scratch_pool));
1636     }
1637 
1638   return SVN_NO_ERROR;
1639 }
1640 
1641 /*-------------------------------------------------------------------------*/
1642 /* Diff */
1643 
1644 /*  */
1645 static svn_error_t *
file_changed(svn_client__shelf2_version_t * shelf_version,const char * relpath,svn_wc_status3_t * s,const svn_diff_tree_processor_t * diff_processor,svn_diff_source_t * left_source,svn_diff_source_t * right_source,const char * left_stored_abspath,const char * right_stored_abspath,void * dir_baton,apr_pool_t * scratch_pool)1646 file_changed(svn_client__shelf2_version_t *shelf_version,
1647              const char *relpath,
1648              svn_wc_status3_t *s,
1649              const svn_diff_tree_processor_t *diff_processor,
1650              svn_diff_source_t *left_source,
1651              svn_diff_source_t *right_source,
1652              const char *left_stored_abspath,
1653              const char *right_stored_abspath,
1654              void *dir_baton,
1655              apr_pool_t *scratch_pool)
1656 {
1657   void *fb;
1658   svn_boolean_t skip = FALSE;
1659 
1660   SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
1661                                       left_source, right_source,
1662                                       NULL /*copyfrom*/,
1663                                       dir_baton, diff_processor,
1664                                       scratch_pool, scratch_pool));
1665   if (!skip)
1666     {
1667       apr_hash_t *left_props, *right_props;
1668       apr_array_header_t *prop_changes;
1669 
1670       SVN_ERR(read_props_from_shelf(&left_props, &right_props,
1671                                     s->node_status, shelf_version, relpath,
1672                                     scratch_pool, scratch_pool));
1673       SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
1674                              scratch_pool));
1675       SVN_ERR(diff_processor->file_changed(
1676                 relpath,
1677                 left_source, right_source,
1678                 left_stored_abspath, right_stored_abspath,
1679                 left_props, right_props,
1680                 TRUE /*file_modified*/, prop_changes,
1681                 fb, diff_processor, scratch_pool));
1682     }
1683 
1684   return SVN_NO_ERROR;
1685 }
1686 
1687 /*  */
1688 static svn_error_t *
file_deleted(svn_client__shelf2_version_t * shelf_version,const char * relpath,svn_wc_status3_t * s,const svn_diff_tree_processor_t * diff_processor,svn_diff_source_t * left_source,const char * left_stored_abspath,void * dir_baton,apr_pool_t * scratch_pool)1689 file_deleted(svn_client__shelf2_version_t *shelf_version,
1690              const char *relpath,
1691              svn_wc_status3_t *s,
1692              const svn_diff_tree_processor_t *diff_processor,
1693              svn_diff_source_t *left_source,
1694              const char *left_stored_abspath,
1695              void *dir_baton,
1696              apr_pool_t *scratch_pool)
1697 {
1698   void *fb;
1699   svn_boolean_t skip = FALSE;
1700 
1701   SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
1702                                       left_source, NULL, NULL /*copyfrom*/,
1703                                       dir_baton, diff_processor,
1704                                       scratch_pool, scratch_pool));
1705   if (!skip)
1706     {
1707       apr_hash_t *left_props, *right_props;
1708 
1709       SVN_ERR(read_props_from_shelf(&left_props, &right_props,
1710                                     s->node_status, shelf_version, relpath,
1711                                     scratch_pool, scratch_pool));
1712       SVN_ERR(diff_processor->file_deleted(relpath,
1713                                            left_source,
1714                                            left_stored_abspath,
1715                                            left_props,
1716                                            fb, diff_processor,
1717                                            scratch_pool));
1718     }
1719 
1720   return SVN_NO_ERROR;
1721 }
1722 
1723 /*  */
1724 static svn_error_t *
file_added(svn_client__shelf2_version_t * shelf_version,const char * relpath,svn_wc_status3_t * s,const svn_diff_tree_processor_t * diff_processor,svn_diff_source_t * right_source,const char * right_stored_abspath,void * dir_baton,apr_pool_t * scratch_pool)1725 file_added(svn_client__shelf2_version_t *shelf_version,
1726            const char *relpath,
1727            svn_wc_status3_t *s,
1728            const svn_diff_tree_processor_t *diff_processor,
1729            svn_diff_source_t *right_source,
1730            const char *right_stored_abspath,
1731            void *dir_baton,
1732            apr_pool_t *scratch_pool)
1733 {
1734   void *fb;
1735   svn_boolean_t skip = FALSE;
1736 
1737   SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
1738                                       NULL, right_source, NULL /*copyfrom*/,
1739                                       dir_baton, diff_processor,
1740                                       scratch_pool, scratch_pool));
1741   if (!skip)
1742     {
1743       apr_hash_t *left_props, *right_props;
1744 
1745       SVN_ERR(read_props_from_shelf(&left_props, &right_props,
1746                                     s->node_status, shelf_version, relpath,
1747                                     scratch_pool, scratch_pool));
1748       SVN_ERR(diff_processor->file_added(
1749                 relpath,
1750                 NULL /*copyfrom_source*/, right_source,
1751                 NULL /*copyfrom_abspath*/, right_stored_abspath,
1752                 NULL /*copyfrom_props*/, right_props,
1753                 fb, diff_processor, scratch_pool));
1754     }
1755 
1756   return SVN_NO_ERROR;
1757 }
1758 
1759 /* Baton for diff_visitor(). */
1760 struct diff_baton_t
1761 {
1762   svn_client__shelf2_version_t *shelf_version;
1763   const char *top_relpath;  /* top of diff, relative to shelf */
1764   const char *walk_root_abspath;
1765   const svn_diff_tree_processor_t *diff_processor;
1766 };
1767 
1768 /* Drive BATON->diff_processor.
1769  * Implements svn_io_walk_func_t. */
1770 static svn_error_t *
diff_visitor(void * baton,const char * abspath,const apr_finfo_t * finfo,apr_pool_t * scratch_pool)1771 diff_visitor(void *baton,
1772              const char *abspath,
1773              const apr_finfo_t *finfo,
1774              apr_pool_t *scratch_pool)
1775 {
1776   struct diff_baton_t *b = baton;
1777   const char *relpath;
1778 
1779   relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath);
1780   if (finfo->filetype == APR_REG
1781            && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0))
1782     {
1783       svn_wc_status3_t *s;
1784       void *db = NULL;
1785       svn_diff_source_t *left_source;
1786       svn_diff_source_t *right_source;
1787       char *left_stored_abspath, *right_stored_abspath;
1788 
1789       relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5);
1790       if (!svn_relpath_skip_ancestor(b->top_relpath, relpath))
1791         return SVN_NO_ERROR;
1792 
1793       SVN_ERR(status_read(&s, b->shelf_version, relpath,
1794                           scratch_pool, scratch_pool));
1795 
1796       left_source = svn_diff__source_create(s->revision, scratch_pool);
1797       right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
1798       SVN_ERR(get_base_file_abspath(&left_stored_abspath,
1799                                     b->shelf_version, relpath,
1800                                     scratch_pool, scratch_pool));
1801       SVN_ERR(get_working_file_abspath(&right_stored_abspath,
1802                                        b->shelf_version, relpath,
1803                                        scratch_pool, scratch_pool));
1804 
1805       switch (s->node_status)
1806         {
1807         case svn_wc_status_modified:
1808           SVN_ERR(file_changed(b->shelf_version, relpath, s,
1809                                b->diff_processor,
1810                                left_source, right_source,
1811                                left_stored_abspath, right_stored_abspath,
1812                                db, scratch_pool));
1813           break;
1814         case svn_wc_status_added:
1815           SVN_ERR(file_added(b->shelf_version, relpath, s,
1816                              b->diff_processor,
1817                              right_source, right_stored_abspath,
1818                              db, scratch_pool));
1819           break;
1820         case svn_wc_status_deleted:
1821           SVN_ERR(file_deleted(b->shelf_version, relpath, s,
1822                                b->diff_processor,
1823                                left_source, left_stored_abspath,
1824                                db, scratch_pool));
1825           break;
1826         case svn_wc_status_replaced:
1827           SVN_ERR(file_deleted(b->shelf_version, relpath, s,
1828                                b->diff_processor,
1829                                left_source, left_stored_abspath,
1830                                db, scratch_pool));
1831           SVN_ERR(file_added(b->shelf_version, relpath, s,
1832                              b->diff_processor,
1833                              right_source, right_stored_abspath,
1834                              db, scratch_pool));
1835         default:
1836           break;
1837         }
1838     }
1839   return SVN_NO_ERROR;
1840 }
1841 
1842 svn_error_t *
svn_client__shelf2_test_apply_file(svn_boolean_t * conflict_p,svn_client__shelf2_version_t * shelf_version,const char * file_relpath,apr_pool_t * scratch_pool)1843 svn_client__shelf2_test_apply_file(svn_boolean_t *conflict_p,
1844                                    svn_client__shelf2_version_t *shelf_version,
1845                                    const char *file_relpath,
1846                                    apr_pool_t *scratch_pool)
1847 {
1848   struct apply_files_baton_t baton = {0};
1849 
1850   baton.shelf_version = shelf_version;
1851   baton.test_only = TRUE;
1852   baton.conflict = FALSE;
1853   baton.ctx = shelf_version->shelf->ctx;
1854   SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath,
1855                            apply_file_visitor, &baton,
1856                            scratch_pool));
1857   *conflict_p = baton.conflict;
1858 
1859   return SVN_NO_ERROR;
1860 }
1861 
1862 svn_error_t *
svn_client__shelf2_apply(svn_client__shelf2_version_t * shelf_version,svn_boolean_t dry_run,apr_pool_t * scratch_pool)1863 svn_client__shelf2_apply(svn_client__shelf2_version_t *shelf_version,
1864                          svn_boolean_t dry_run,
1865                          apr_pool_t *scratch_pool)
1866 {
1867   struct apply_files_baton_t baton = {0};
1868 
1869   baton.shelf_version = shelf_version;
1870   baton.ctx = shelf_version->shelf->ctx;
1871   SVN_ERR(shelf_status_walk(shelf_version, "",
1872                             apply_file_visitor, &baton,
1873                             scratch_pool));
1874 
1875   svn_io_sleep_for_timestamps(shelf_version->shelf->wc_root_abspath,
1876                               scratch_pool);
1877   return SVN_NO_ERROR;
1878 }
1879 
1880 svn_error_t *
svn_client__shelf2_unapply(svn_client__shelf2_version_t * shelf_version,svn_boolean_t dry_run,apr_pool_t * scratch_pool)1881 svn_client__shelf2_unapply(svn_client__shelf2_version_t *shelf_version,
1882                            svn_boolean_t dry_run,
1883                            apr_pool_t *scratch_pool)
1884 {
1885   apr_array_header_t *targets;
1886 
1887   SVN_ERR(shelf_paths_changed(NULL, &targets, shelf_version,
1888                               TRUE /*as_abspath*/,
1889                               scratch_pool, scratch_pool));
1890   if (!dry_run)
1891     {
1892       SVN_ERR(svn_client_revert4(targets, svn_depth_empty,
1893                                  NULL /*changelists*/,
1894                                  FALSE /*clear_changelists*/,
1895                                  FALSE /*metadata_only*/,
1896                                  FALSE /*added_keep_local*/,
1897                                  shelf_version->shelf->ctx, scratch_pool));
1898     }
1899   return SVN_NO_ERROR;
1900 }
1901 
1902 svn_error_t *
svn_client__shelf2_delete_newer_versions(svn_client__shelf2_t * shelf,svn_client__shelf2_version_t * shelf_version,apr_pool_t * scratch_pool)1903 svn_client__shelf2_delete_newer_versions(svn_client__shelf2_t *shelf,
1904                                          svn_client__shelf2_version_t *shelf_version,
1905                                          apr_pool_t *scratch_pool)
1906 {
1907   int previous_version = shelf_version ? shelf_version->version_number : 0;
1908   int i;
1909 
1910   /* Delete any newer checkpoints */
1911   for (i = shelf->max_version; i > previous_version; i--)
1912     {
1913       SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
1914     }
1915 
1916   shelf->max_version = previous_version;
1917   SVN_ERR(shelf_write_current(shelf, scratch_pool));
1918   return SVN_NO_ERROR;
1919 }
1920 
1921 svn_error_t *
svn_client__shelf2_diff(svn_client__shelf2_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)1922 svn_client__shelf2_diff(svn_client__shelf2_version_t *shelf_version,
1923                         const char *shelf_relpath,
1924                         svn_depth_t depth,
1925                         svn_boolean_t ignore_ancestry,
1926                         const svn_diff_tree_processor_t *diff_processor,
1927                         apr_pool_t *scratch_pool)
1928 {
1929   struct diff_baton_t baton;
1930 
1931   if (shelf_version->version_number == 0)
1932     return SVN_NO_ERROR;
1933 
1934   baton.shelf_version = shelf_version;
1935   baton.top_relpath = shelf_relpath;
1936   baton.walk_root_abspath = shelf_version->files_dir_abspath;
1937   baton.diff_processor = diff_processor;
1938   SVN_ERR(svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/,
1939                            diff_visitor, &baton,
1940                            scratch_pool));
1941 
1942   return SVN_NO_ERROR;
1943 }
1944 
1945 svn_error_t *
svn_client__shelf2_save_new_version3(svn_client__shelf2_version_t ** new_version_p,svn_client__shelf2_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)1946 svn_client__shelf2_save_new_version3(svn_client__shelf2_version_t **new_version_p,
1947                                      svn_client__shelf2_t *shelf,
1948                                      const apr_array_header_t *paths,
1949                                      svn_depth_t depth,
1950                                      const apr_array_header_t *changelists,
1951                                      svn_client_status_func_t shelved_func,
1952                                      void *shelved_baton,
1953                                      svn_client_status_func_t not_shelved_func,
1954                                      void *not_shelved_baton,
1955                                      apr_pool_t *scratch_pool)
1956 {
1957   int next_version = shelf->max_version + 1;
1958   svn_client__shelf2_version_t *new_shelf_version;
1959   svn_boolean_t any_shelved;
1960 
1961   SVN_ERR(shelf_version_create(&new_shelf_version,
1962                                shelf, next_version, scratch_pool));
1963   SVN_ERR(shelf_write_changes(&any_shelved,
1964                               new_shelf_version,
1965                               paths, depth, changelists,
1966                               shelved_func, shelved_baton,
1967                               not_shelved_func, not_shelved_baton,
1968                               shelf->wc_root_abspath,
1969                               shelf->ctx, scratch_pool, scratch_pool));
1970 
1971   if (any_shelved)
1972     {
1973       shelf->max_version = next_version;
1974       SVN_ERR(shelf_write_current(shelf, scratch_pool));
1975 
1976       if (new_version_p)
1977         SVN_ERR(svn_client__shelf2_version_open(new_version_p, shelf, next_version,
1978                                                 scratch_pool, scratch_pool));
1979     }
1980   else
1981     {
1982       if (new_version_p)
1983         *new_version_p = NULL;
1984     }
1985   return SVN_NO_ERROR;
1986 }
1987 
1988 svn_error_t *
svn_client__shelf2_get_log_message(char ** log_message,svn_client__shelf2_t * shelf,apr_pool_t * result_pool)1989 svn_client__shelf2_get_log_message(char **log_message,
1990                                    svn_client__shelf2_t *shelf,
1991                                    apr_pool_t *result_pool)
1992 {
1993   svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG);
1994 
1995   if (propval)
1996     *log_message = apr_pstrdup(result_pool, propval->data);
1997   else
1998     *log_message = NULL;
1999   return SVN_NO_ERROR;
2000 }
2001 
2002 svn_error_t *
svn_client__shelf2_set_log_message(svn_client__shelf2_t * shelf,const char * message,apr_pool_t * scratch_pool)2003 svn_client__shelf2_set_log_message(svn_client__shelf2_t *shelf,
2004                                    const char *message,
2005                                    apr_pool_t *scratch_pool)
2006 {
2007   svn_string_t *propval
2008     = message ? svn_string_create(message, shelf->pool) : NULL;
2009 
2010   SVN_ERR(svn_client__shelf2_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval,
2011                                          scratch_pool));
2012   return SVN_NO_ERROR;
2013 }
2014 
2015 svn_error_t *
svn_client__shelf2_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)2016 svn_client__shelf2_list(apr_hash_t **shelf_infos,
2017                         const char *local_abspath,
2018                         svn_client_ctx_t *ctx,
2019                         apr_pool_t *result_pool,
2020                         apr_pool_t *scratch_pool)
2021 {
2022   const char *wc_root_abspath;
2023   char *shelves_dir;
2024   apr_hash_t *dirents;
2025   apr_hash_index_t *hi;
2026 
2027   SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath,
2028                              scratch_pool, scratch_pool));
2029   SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
2030                           scratch_pool, scratch_pool));
2031   SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
2032                               result_pool, scratch_pool));
2033 
2034   *shelf_infos = apr_hash_make(result_pool);
2035 
2036   /* Remove non-shelves */
2037   for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
2038     {
2039       const char *filename = apr_hash_this_key(hi);
2040       svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
2041       char *name = NULL;
2042 
2043       svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
2044       if (name && dirent->kind == svn_node_file)
2045         {
2046           svn_client__shelf2_info_t *info
2047             = apr_palloc(result_pool, sizeof(*info));
2048 
2049           info->mtime = dirent->mtime;
2050           svn_hash_sets(*shelf_infos, name, info);
2051         }
2052     }
2053 
2054   return SVN_NO_ERROR;
2055 }
2056 
2057 svn_error_t *
svn_client__shelf2_version_open(svn_client__shelf2_version_t ** shelf_version_p,svn_client__shelf2_t * shelf,int version_number,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2058 svn_client__shelf2_version_open(svn_client__shelf2_version_t **shelf_version_p,
2059                                 svn_client__shelf2_t *shelf,
2060                                 int version_number,
2061                                 apr_pool_t *result_pool,
2062                                 apr_pool_t *scratch_pool)
2063 {
2064   svn_client__shelf2_version_t *shelf_version;
2065   const svn_io_dirent2_t *dirent;
2066 
2067   SVN_ERR(shelf_version_create(&shelf_version,
2068                                shelf, version_number, result_pool));
2069   SVN_ERR(svn_io_stat_dirent2(&dirent,
2070                               shelf_version->files_dir_abspath,
2071                               FALSE /*verify_truename*/,
2072                               TRUE /*ignore_enoent*/,
2073                               result_pool, scratch_pool));
2074   if (dirent->kind == svn_node_none)
2075     {
2076       return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
2077                                _("Shelf '%s' version %d not found"),
2078                                shelf->name, version_number);
2079     }
2080   shelf_version->mtime = dirent->mtime;
2081   *shelf_version_p = shelf_version;
2082   return SVN_NO_ERROR;
2083 }
2084 
2085 svn_error_t *
svn_client__shelf2_get_newest_version(svn_client__shelf2_version_t ** shelf_version_p,svn_client__shelf2_t * shelf,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2086 svn_client__shelf2_get_newest_version(svn_client__shelf2_version_t **shelf_version_p,
2087                                       svn_client__shelf2_t *shelf,
2088                                       apr_pool_t *result_pool,
2089                                       apr_pool_t *scratch_pool)
2090 {
2091   if (shelf->max_version == 0)
2092     {
2093       *shelf_version_p = NULL;
2094       return SVN_NO_ERROR;
2095     }
2096 
2097   SVN_ERR(svn_client__shelf2_version_open(shelf_version_p,
2098                                           shelf, shelf->max_version,
2099                                           result_pool, scratch_pool));
2100   return SVN_NO_ERROR;
2101 }
2102 
2103 svn_error_t *
svn_client__shelf2_get_all_versions(apr_array_header_t ** versions_p,svn_client__shelf2_t * shelf,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2104 svn_client__shelf2_get_all_versions(apr_array_header_t **versions_p,
2105                                     svn_client__shelf2_t *shelf,
2106                                     apr_pool_t *result_pool,
2107                                     apr_pool_t *scratch_pool)
2108 {
2109   int i;
2110 
2111   *versions_p = apr_array_make(result_pool, shelf->max_version - 1,
2112                                sizeof(svn_client__shelf2_version_t *));
2113 
2114   for (i = 1; i <= shelf->max_version; i++)
2115     {
2116       svn_client__shelf2_version_t *shelf_version;
2117 
2118       SVN_ERR(svn_client__shelf2_version_open(&shelf_version,
2119                                               shelf, i,
2120                                               result_pool, scratch_pool));
2121       APR_ARRAY_PUSH(*versions_p, svn_client__shelf2_version_t *) = shelf_version;
2122     }
2123   return SVN_NO_ERROR;
2124 }
2125