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