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(¤t_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(¤t_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