1 /*
2 * revert.c: Handling of the in-wc side of the revert operation
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 #include <string.h>
27 #include <stdlib.h>
28
29 #include <apr_pools.h>
30 #include <apr_tables.h>
31
32 #include "svn_types.h"
33 #include "svn_pools.h"
34 #include "svn_string.h"
35 #include "svn_error.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_path.h"
38 #include "svn_hash.h"
39 #include "svn_wc.h"
40 #include "svn_io.h"
41
42 #include "wc.h"
43 #include "adm_files.h"
44 #include "workqueue.h"
45
46 #include "svn_private_config.h"
47 #include "private/svn_io_private.h"
48 #include "private/svn_wc_private.h"
49 #include "private/svn_sorts_private.h"
50
51 /* Thoughts on Reversion.
52
53 What does is mean to revert a given PATH in a tree? We'll
54 consider things by their modifications.
55
56 Adds
57
58 - For files, svn_wc_remove_from_revision_control(), baby.
59
60 - Added directories may contain nothing but added children, and
61 reverting the addition of a directory necessarily means reverting
62 the addition of all the directory's children. Again,
63 svn_wc_remove_from_revision_control() should do the trick.
64
65 - For a copy, we remove the item from disk as well. The thinking here
66 is that Subversion is responsible for the existence of the item: it
67 must have been created by something like 'svn copy' or 'svn merge'.
68
69 - For a plain add, removing the file or directory from disk is optional.
70 The user's idea of Subversion's involvement could be either that
71 Subversion was just responsible for adding an existing item to version
72 control, as with 'svn add', and so should not be responsible for
73 deleting it from disk; or that Subversion is responsible for the
74 existence of the item, e.g. if created by 'svn patch' or svn mkdir'.
75 It depends on the use case.
76
77 Deletes
78
79 - Restore properties to their unmodified state.
80
81 - For files, restore the pristine contents, and reset the schedule
82 to 'normal'.
83
84 - For directories, reset the schedule to 'normal'. All children
85 of a directory marked for deletion must also be marked for
86 deletion, but it's okay for those children to remain deleted even
87 if their parent directory is restored. That's what the
88 recursive flag is for.
89
90 Replaces
91
92 - Restore properties to their unmodified state.
93
94 - For files, restore the pristine contents, and reset the schedule
95 to 'normal'.
96
97 - For directories, reset the schedule to normal. A replaced
98 directory can have deleted children (left over from the initial
99 deletion), replaced children (children of the initial deletion
100 now re-added), and added children (new entries under the
101 replaced directory). Since this is technically an addition, it
102 necessitates recursion.
103
104 Modifications
105
106 - Restore properties and, for files, contents to their unmodified
107 state.
108
109 */
110
111
112 /* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set
113 * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */
114 static svn_error_t *
remove_conflict_file(svn_boolean_t * notify_required,const char * conflict_abspath,const char * local_abspath,apr_pool_t * scratch_pool)115 remove_conflict_file(svn_boolean_t *notify_required,
116 const char *conflict_abspath,
117 const char *local_abspath,
118 apr_pool_t *scratch_pool)
119 {
120 if (conflict_abspath)
121 {
122 svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE,
123 scratch_pool);
124 if (err)
125 svn_error_clear(err);
126 else
127 *notify_required = TRUE;
128 }
129
130 return SVN_NO_ERROR;
131 }
132
133
134 /* Sort copied children obtained from the revert list based on
135 * their paths in descending order (longest paths first). */
136 static int
compare_revert_list_copied_children(const void * a,const void * b)137 compare_revert_list_copied_children(const void *a, const void *b)
138 {
139 const svn_wc__db_revert_list_copied_child_info_t * const *ca = a;
140 const svn_wc__db_revert_list_copied_child_info_t * const *cb = b;
141 int i;
142
143 i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath);
144
145 /* Reverse the result of svn_path_compare_paths() to achieve
146 * descending order. */
147 return -i;
148 }
149
150
151 /* Remove all reverted copied children from the directory at LOCAL_ABSPATH.
152 * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF
153 * should be set if LOCAL_ABSPATH is itself a reverted copy).
154 *
155 * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether
156 * LOCAL_ABSPATH itself was removed.
157 *
158 * All reverted copied file children are removed from disk. Reverted copied
159 * directories left empty as a result are also removed from disk.
160 */
161 static svn_error_t *
revert_restore_handle_copied_dirs(svn_boolean_t * removed_self,svn_wc__db_t * db,const char * local_abspath,svn_boolean_t remove_self,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)162 revert_restore_handle_copied_dirs(svn_boolean_t *removed_self,
163 svn_wc__db_t *db,
164 const char *local_abspath,
165 svn_boolean_t remove_self,
166 svn_cancel_func_t cancel_func,
167 void *cancel_baton,
168 apr_pool_t *scratch_pool)
169 {
170 apr_array_header_t *copied_children;
171 svn_wc__db_revert_list_copied_child_info_t *child_info;
172 int i;
173 svn_node_kind_t on_disk;
174 apr_pool_t *iterpool;
175 svn_error_t *err;
176
177 if (removed_self)
178 *removed_self = FALSE;
179
180 SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children,
181 db, local_abspath,
182 scratch_pool,
183 scratch_pool));
184 iterpool = svn_pool_create(scratch_pool);
185
186 /* Remove all copied file children. */
187 for (i = 0; i < copied_children->nelts; i++)
188 {
189 child_info = APR_ARRAY_IDX(
190 copied_children, i,
191 svn_wc__db_revert_list_copied_child_info_t *);
192
193 if (cancel_func)
194 SVN_ERR(cancel_func(cancel_baton));
195
196 if (child_info->kind != svn_node_file)
197 continue;
198
199 svn_pool_clear(iterpool);
200
201 /* Make sure what we delete from disk is really a file. */
202 SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool));
203 if (on_disk != svn_node_file)
204 continue;
205
206 SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool));
207 }
208
209 /* Delete every empty child directory.
210 * We cannot delete children recursively since we want to keep any files
211 * that still exist on disk (e.g. unversioned files within the copied tree).
212 * So sort the children list such that longest paths come first and try to
213 * remove each child directory in order. */
214 svn_sort__array(copied_children, compare_revert_list_copied_children);
215 for (i = 0; i < copied_children->nelts; i++)
216 {
217 child_info = APR_ARRAY_IDX(
218 copied_children, i,
219 svn_wc__db_revert_list_copied_child_info_t *);
220
221 if (cancel_func)
222 SVN_ERR(cancel_func(cancel_baton));
223
224 if (child_info->kind != svn_node_dir)
225 continue;
226
227 svn_pool_clear(iterpool);
228
229 err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool);
230 if (err)
231 {
232 if (APR_STATUS_IS_ENOENT(err->apr_err) ||
233 SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) ||
234 APR_STATUS_IS_ENOTEMPTY(err->apr_err))
235 svn_error_clear(err);
236 else
237 return svn_error_trace(err);
238 }
239 }
240
241 if (remove_self)
242 {
243 /* Delete LOCAL_ABSPATH itself if no children are left. */
244 err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool);
245 if (err)
246 {
247 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
248 svn_error_clear(err);
249 else
250 return svn_error_trace(err);
251 }
252 else if (removed_self)
253 *removed_self = TRUE;
254 }
255
256 svn_pool_destroy(iterpool);
257
258 return SVN_NO_ERROR;
259 }
260
261 /* Forward definition */
262 static svn_error_t *
263 revert_wc_data(svn_boolean_t *run_wq,
264 svn_boolean_t *notify_required,
265 svn_wc__db_t *db,
266 const char *local_abspath,
267 svn_wc__db_status_t status,
268 svn_node_kind_t kind,
269 svn_node_kind_t reverted_kind,
270 svn_filesize_t recorded_size,
271 apr_time_t recorded_time,
272 svn_boolean_t copied_here,
273 svn_boolean_t use_commit_times,
274 svn_cancel_func_t cancel_func,
275 void *cancel_baton,
276 apr_pool_t *scratch_pool);
277
278 /* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the
279 versioned tree. This function is called after svn_wc__db_op_revert
280 has done the database revert and created the revert list. Notifies
281 for all paths equal to or below LOCAL_ABSPATH that are reverted.
282
283 REVERT_ROOT is true for explicit revert targets and FALSE for targets
284 reached via recursion.
285
286 Sets *RUN_WQ to TRUE when the caller should (eventually) run the workqueue.
287 (The function sets it to FALSE when it has run the WQ itself)
288
289 If INFO is NULL, LOCAL_ABSPATH doesn't exist in DB. Otherwise INFO
290 specifies the state of LOCAL_ABSPATH in DB.
291 */
292 static svn_error_t *
revert_restore(svn_boolean_t * run_wq,svn_wc__db_t * db,const char * local_abspath,svn_depth_t depth,svn_boolean_t metadata_only,svn_boolean_t use_commit_times,svn_boolean_t revert_root,svn_boolean_t added_keep_local,const struct svn_wc__db_info_t * info,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)293 revert_restore(svn_boolean_t *run_wq,
294 svn_wc__db_t *db,
295 const char *local_abspath,
296 svn_depth_t depth,
297 svn_boolean_t metadata_only,
298 svn_boolean_t use_commit_times,
299 svn_boolean_t revert_root,
300 svn_boolean_t added_keep_local,
301 const struct svn_wc__db_info_t *info,
302 svn_cancel_func_t cancel_func,
303 void *cancel_baton,
304 svn_wc_notify_func2_t notify_func,
305 void *notify_baton,
306 apr_pool_t *scratch_pool)
307 {
308 svn_wc__db_status_t status;
309 svn_node_kind_t kind;
310 svn_boolean_t notify_required;
311 const apr_array_header_t *conflict_files;
312 svn_filesize_t recorded_size;
313 apr_time_t recorded_time;
314 svn_boolean_t copied_here;
315 svn_node_kind_t reverted_kind;
316 if (cancel_func)
317 SVN_ERR(cancel_func(cancel_baton));
318
319 if (!revert_root)
320 {
321 svn_boolean_t is_wcroot;
322
323 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
324 if (is_wcroot)
325 {
326 /* Issue #4162: Obstructing working copy. We can't access the working
327 copy data from the parent working copy for this node by just using
328 local_abspath */
329
330 if (notify_func)
331 {
332 svn_wc_notify_t *notify =
333 svn_wc_create_notify(
334 local_abspath,
335 svn_wc_notify_update_skip_obstruction,
336 scratch_pool);
337
338 notify_func(notify_baton, notify, scratch_pool);
339 }
340
341 return SVN_NO_ERROR; /* We don't revert obstructing working copies */
342 }
343 }
344
345 SVN_ERR(svn_wc__db_revert_list_read(¬ify_required,
346 &conflict_files,
347 &copied_here, &reverted_kind,
348 db, local_abspath,
349 scratch_pool, scratch_pool));
350
351 if (info)
352 {
353 status = info->status;
354 kind = info->kind;
355 recorded_size = info->recorded_size;
356 recorded_time = info->recorded_time;
357 }
358 else
359 {
360 if (added_keep_local && !copied_here)
361 {
362 /* It is a plain add, and we want to keep the local file/dir. */
363 if (notify_func && notify_required)
364 notify_func(notify_baton,
365 svn_wc_create_notify(local_abspath,
366 svn_wc_notify_revert,
367 scratch_pool),
368 scratch_pool);
369
370 if (notify_func)
371 SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
372 db, local_abspath,
373 scratch_pool));
374 return SVN_NO_ERROR;
375 }
376 else if (!copied_here)
377 {
378 /* It is a plain add, and we don't want to keep the local file/dir. */
379 status = svn_wc__db_status_not_present;
380 kind = svn_node_none;
381 recorded_size = SVN_INVALID_FILESIZE;
382 recorded_time = 0;
383 }
384 else
385 {
386 /* It is a copy, so we don't want to keep the local file/dir. */
387 /* ### Initialise to values which prevent the code below from
388 * ### trying to restore anything to disk.
389 * ### 'status' should be status_unknown but that doesn't exist. */
390 status = svn_wc__db_status_normal;
391 kind = svn_node_unknown;
392 recorded_size = SVN_INVALID_FILESIZE;
393 recorded_time = 0;
394 }
395 }
396
397 if (!metadata_only)
398 {
399 SVN_ERR(revert_wc_data(run_wq,
400 ¬ify_required,
401 db, local_abspath, status, kind,
402 reverted_kind, recorded_size, recorded_time,
403 copied_here, use_commit_times,
404 cancel_func, cancel_baton, scratch_pool));
405 }
406
407 /* We delete these marker files even though they are not strictly metadata.
408 But for users that use revert as an API with metadata_only, these are. */
409 if (conflict_files)
410 {
411 int i;
412 for (i = 0; i < conflict_files->nelts; i++)
413 {
414 SVN_ERR(remove_conflict_file(¬ify_required,
415 APR_ARRAY_IDX(conflict_files, i,
416 const char *),
417 local_abspath, scratch_pool));
418 }
419 }
420
421 if (notify_func && notify_required)
422 notify_func(notify_baton,
423 svn_wc_create_notify(local_abspath, svn_wc_notify_revert,
424 scratch_pool),
425 scratch_pool);
426
427 if (depth == svn_depth_infinity && kind == svn_node_dir)
428 {
429 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
430 apr_hash_t *children, *conflicts;
431 apr_hash_index_t *hi;
432
433 SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE,
434 cancel_func, cancel_baton,
435 iterpool));
436
437 SVN_ERR(svn_wc__db_read_children_info(&children, &conflicts,
438 db, local_abspath, FALSE,
439 scratch_pool, iterpool));
440
441 for (hi = apr_hash_first(scratch_pool, children);
442 hi;
443 hi = apr_hash_next(hi))
444 {
445 const char *child_name = apr_hash_this_key(hi);
446 const char *child_abspath;
447
448 svn_pool_clear(iterpool);
449
450 child_abspath = svn_dirent_join(local_abspath, child_name, iterpool);
451
452 SVN_ERR(revert_restore(run_wq,
453 db, child_abspath, depth, metadata_only,
454 use_commit_times, FALSE /* revert root */,
455 added_keep_local,
456 apr_hash_this_val(hi),
457 cancel_func, cancel_baton,
458 notify_func, notify_baton,
459 iterpool));
460 }
461
462 /* Run the queue per directory */
463 if (*run_wq)
464 {
465 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
466 iterpool));
467 *run_wq = FALSE;
468 }
469
470 svn_pool_destroy(iterpool);
471 }
472
473 if (notify_func && (revert_root || kind == svn_node_dir))
474 SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton,
475 db, local_abspath, scratch_pool));
476
477 return SVN_NO_ERROR;
478 }
479
480 /* Perform the in-working copy revert of LOCAL_ABSPATH, to what is stored in DB */
481 static svn_error_t *
revert_wc_data(svn_boolean_t * run_wq,svn_boolean_t * notify_required,svn_wc__db_t * db,const char * local_abspath,svn_wc__db_status_t status,svn_node_kind_t kind,svn_node_kind_t reverted_kind,svn_filesize_t recorded_size,apr_time_t recorded_time,svn_boolean_t copied_here,svn_boolean_t use_commit_times,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)482 revert_wc_data(svn_boolean_t *run_wq,
483 svn_boolean_t *notify_required,
484 svn_wc__db_t *db,
485 const char *local_abspath,
486 svn_wc__db_status_t status,
487 svn_node_kind_t kind,
488 svn_node_kind_t reverted_kind,
489 svn_filesize_t recorded_size,
490 apr_time_t recorded_time,
491 svn_boolean_t copied_here,
492 svn_boolean_t use_commit_times,
493 svn_cancel_func_t cancel_func,
494 void *cancel_baton,
495 apr_pool_t *scratch_pool)
496 {
497 svn_error_t *err;
498 apr_finfo_t finfo;
499 svn_node_kind_t on_disk;
500 #ifdef HAVE_SYMLINK
501 svn_boolean_t special;
502 #endif
503
504 /* Would be nice to use svn_io_dirent2_t here, but the performance
505 improvement that provides doesn't work, because we need the read
506 only and executable bits later on, in the most likely code path */
507 err = svn_io_stat(&finfo, local_abspath,
508 APR_FINFO_TYPE | APR_FINFO_LINK
509 | APR_FINFO_SIZE | APR_FINFO_MTIME
510 | SVN__APR_FINFO_EXECUTABLE
511 | SVN__APR_FINFO_READONLY,
512 scratch_pool);
513
514 if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
515 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)))
516 {
517 svn_error_clear(err);
518 on_disk = svn_node_none;
519 #ifdef HAVE_SYMLINK
520 special = FALSE;
521 #endif
522 }
523 else if (!err)
524 {
525 if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK)
526 on_disk = svn_node_file;
527 else if (finfo.filetype == APR_DIR)
528 on_disk = svn_node_dir;
529 else
530 on_disk = svn_node_unknown;
531
532 #ifdef HAVE_SYMLINK
533 special = (finfo.filetype == APR_LNK);
534 #endif
535 }
536 else
537 return svn_error_trace(err);
538
539 if (copied_here)
540 {
541 /* The revert target itself is the op-root of a copy. */
542 if (reverted_kind == svn_node_file && on_disk == svn_node_file)
543 {
544 SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool));
545 on_disk = svn_node_none;
546 }
547 else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir)
548 {
549 svn_boolean_t removed;
550
551 SVN_ERR(revert_restore_handle_copied_dirs(&removed, db,
552 local_abspath, TRUE,
553 cancel_func, cancel_baton,
554 scratch_pool));
555 if (removed)
556 on_disk = svn_node_none;
557 }
558 }
559
560 /* If we expect a versioned item to be present then check that any
561 item on disk matches the versioned item, if it doesn't match then
562 fix it or delete it. */
563 if (on_disk != svn_node_none)
564 {
565 if (on_disk == svn_node_dir && kind != svn_node_dir)
566 {
567 SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE,
568 cancel_func, cancel_baton, scratch_pool));
569 on_disk = svn_node_none;
570 }
571 else if (on_disk == svn_node_file && kind != svn_node_file)
572 {
573 #ifdef HAVE_SYMLINK
574 /* Preserve symlinks pointing at directories. Changes on the
575 * directory node have been reverted. The symlink should remain. */
576 if (!(special && kind == svn_node_dir))
577 #endif
578 {
579 SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
580 on_disk = svn_node_none;
581 }
582 }
583 else if (on_disk == svn_node_file
584 && status != svn_wc__db_status_server_excluded
585 && status != svn_wc__db_status_deleted
586 && status != svn_wc__db_status_excluded
587 && status != svn_wc__db_status_not_present)
588 {
589 svn_boolean_t modified;
590 apr_hash_t *props;
591 #ifdef HAVE_SYMLINK
592 svn_string_t *special_prop;
593 #endif
594
595 SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath,
596 scratch_pool, scratch_pool));
597
598 #ifdef HAVE_SYMLINK
599 special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL);
600
601 if ((special_prop != NULL) != special)
602 {
603 /* File/symlink mismatch. */
604 SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool));
605 on_disk = svn_node_none;
606 }
607 else
608 #endif
609 {
610 /* Issue #1663 asserts that we should compare a file in its
611 working copy format here, but before r1101473 we would only
612 do that if the file was already unequal to its recorded
613 information.
614
615 r1101473 removes the option of asking for a working format
616 compare but *also* check the recorded information first, as
617 that combination doesn't guarantee a stable behavior.
618 (See the revert_test.py: revert_reexpand_keyword)
619
620 But to have the same issue #1663 behavior for revert as we
621 had in <=1.6 we only have to check the recorded information
622 ourselves. And we already have everything we need, because
623 we called stat ourselves. */
624 if (recorded_size != SVN_INVALID_FILESIZE
625 && recorded_time != 0
626 && recorded_size == finfo.size
627 && recorded_time == finfo.mtime)
628 {
629 modified = FALSE;
630 }
631 else
632 /* Side effect: fixes recorded timestamps */
633 SVN_ERR(svn_wc__internal_file_modified_p(&modified,
634 db, local_abspath,
635 TRUE, scratch_pool));
636
637 if (modified)
638 {
639 /* Install will replace the file */
640 on_disk = svn_node_none;
641 }
642 else
643 {
644 if (status == svn_wc__db_status_normal)
645 {
646 svn_boolean_t read_only;
647 svn_string_t *needs_lock_prop;
648
649 SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo,
650 scratch_pool));
651
652 needs_lock_prop = svn_hash_gets(props,
653 SVN_PROP_NEEDS_LOCK);
654 if (needs_lock_prop && !read_only)
655 {
656 SVN_ERR(svn_io_set_file_read_only(local_abspath,
657 FALSE,
658 scratch_pool));
659 *notify_required = TRUE;
660 }
661 else if (!needs_lock_prop && read_only)
662 {
663 SVN_ERR(svn_io_set_file_read_write(local_abspath,
664 FALSE,
665 scratch_pool));
666 *notify_required = TRUE;
667 }
668 }
669
670 #if !defined(WIN32) && !defined(__OS2__)
671 #ifdef HAVE_SYMLINK
672 if (!special)
673 #endif
674 {
675 svn_boolean_t executable;
676 svn_string_t *executable_prop;
677
678 SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo,
679 scratch_pool));
680 executable_prop = svn_hash_gets(props,
681 SVN_PROP_EXECUTABLE);
682 if (executable_prop && !executable)
683 {
684 SVN_ERR(svn_io_set_file_executable(local_abspath,
685 TRUE, FALSE,
686 scratch_pool));
687 *notify_required = TRUE;
688 }
689 else if (!executable_prop && executable)
690 {
691 SVN_ERR(svn_io_set_file_executable(local_abspath,
692 FALSE, FALSE,
693 scratch_pool));
694 *notify_required = TRUE;
695 }
696 }
697 #endif
698 }
699 }
700 }
701 }
702
703 /* If we expect a versioned item to be present and there is nothing
704 on disk then recreate it. */
705 if (on_disk == svn_node_none
706 && status != svn_wc__db_status_server_excluded
707 && status != svn_wc__db_status_deleted
708 && status != svn_wc__db_status_excluded
709 && status != svn_wc__db_status_not_present)
710 {
711 if (kind == svn_node_dir)
712 SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
713
714 if (kind == svn_node_file)
715 {
716 svn_skel_t *work_item;
717
718 SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath,
719 NULL, use_commit_times, TRUE,
720 scratch_pool, scratch_pool));
721 SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item,
722 scratch_pool));
723 *run_wq = TRUE;
724 }
725 *notify_required = TRUE;
726 }
727
728 return SVN_NO_ERROR;
729 }
730
731 /* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all reverts. */
732 static svn_error_t *
revert(svn_wc__db_t * db,const char * local_abspath,svn_depth_t depth,svn_boolean_t use_commit_times,svn_boolean_t clear_changelists,svn_boolean_t metadata_only,svn_boolean_t added_keep_local,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)733 revert(svn_wc__db_t *db,
734 const char *local_abspath,
735 svn_depth_t depth,
736 svn_boolean_t use_commit_times,
737 svn_boolean_t clear_changelists,
738 svn_boolean_t metadata_only,
739 svn_boolean_t added_keep_local,
740 svn_cancel_func_t cancel_func,
741 void *cancel_baton,
742 svn_wc_notify_func2_t notify_func,
743 void *notify_baton,
744 apr_pool_t *scratch_pool)
745 {
746 svn_error_t *err;
747 const struct svn_wc__db_info_t *info = NULL;
748 svn_boolean_t run_queue = FALSE;
749
750 SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity);
751
752 /* We should have a write lock on the parent of local_abspath, except
753 when local_abspath is the working copy root. */
754 {
755 const char *dir_abspath;
756 svn_boolean_t is_wcroot;
757
758 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool));
759
760 if (! is_wcroot)
761 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
762 else
763 dir_abspath = local_abspath;
764
765 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
766 }
767
768 err = svn_error_trace(
769 svn_wc__db_op_revert(db, local_abspath, depth, clear_changelists,
770 scratch_pool, scratch_pool));
771
772 if (!err)
773 {
774 err = svn_error_trace(
775 svn_wc__db_read_single_info(&info, db, local_abspath, FALSE,
776 scratch_pool, scratch_pool));
777
778 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
779 {
780 svn_error_clear(err);
781 err = NULL;
782 info = NULL;
783 }
784 }
785
786 if (!err)
787 err = svn_error_trace(
788 revert_restore(&run_queue, db, local_abspath, depth, metadata_only,
789 use_commit_times, TRUE /* revert root */,
790 added_keep_local,
791 info, cancel_func, cancel_baton,
792 notify_func, notify_baton,
793 scratch_pool));
794
795 if (run_queue)
796 err = svn_error_compose_create(err,
797 svn_wc__wq_run(db, local_abspath,
798 cancel_func, cancel_baton,
799 scratch_pool));
800
801 err = svn_error_compose_create(err,
802 svn_wc__db_revert_list_done(db,
803 local_abspath,
804 scratch_pool));
805
806 return err;
807 }
808
809
810 /* Revert files in LOCAL_ABSPATH to depth DEPTH that match
811 CHANGELIST_HASH and notify for all reverts. */
812 static svn_error_t *
revert_changelist(svn_wc__db_t * db,const char * local_abspath,svn_depth_t depth,svn_boolean_t use_commit_times,apr_hash_t * changelist_hash,svn_boolean_t clear_changelists,svn_boolean_t metadata_only,svn_boolean_t added_keep_local,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)813 revert_changelist(svn_wc__db_t *db,
814 const char *local_abspath,
815 svn_depth_t depth,
816 svn_boolean_t use_commit_times,
817 apr_hash_t *changelist_hash,
818 svn_boolean_t clear_changelists,
819 svn_boolean_t metadata_only,
820 svn_boolean_t added_keep_local,
821 svn_cancel_func_t cancel_func,
822 void *cancel_baton,
823 svn_wc_notify_func2_t notify_func,
824 void *notify_baton,
825 apr_pool_t *scratch_pool)
826 {
827 apr_pool_t *iterpool;
828 const apr_array_header_t *children;
829 int i;
830
831 if (cancel_func)
832 SVN_ERR(cancel_func(cancel_baton));
833
834 /* Revert this node (depth=empty) if it matches one of the changelists. */
835 if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash,
836 scratch_pool))
837 SVN_ERR(revert(db, local_abspath,
838 svn_depth_empty, use_commit_times, clear_changelists,
839 metadata_only, added_keep_local,
840 cancel_func, cancel_baton,
841 notify_func, notify_baton,
842 scratch_pool));
843
844 if (depth == svn_depth_empty)
845 return SVN_NO_ERROR;
846
847 iterpool = svn_pool_create(scratch_pool);
848
849 /* We can handle both depth=files and depth=immediates by setting
850 depth=empty here. We don't need to distinguish files and
851 directories when making the recursive call because directories
852 can never match a changelist, so making the recursive call for
853 directories when asked for depth=files is a no-op. */
854 if (depth == svn_depth_files || depth == svn_depth_immediates)
855 depth = svn_depth_empty;
856
857 SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
858 local_abspath,
859 scratch_pool,
860 iterpool));
861 for (i = 0; i < children->nelts; ++i)
862 {
863 const char *child_abspath;
864
865 svn_pool_clear(iterpool);
866
867 child_abspath = svn_dirent_join(local_abspath,
868 APR_ARRAY_IDX(children, i,
869 const char *),
870 iterpool);
871
872 SVN_ERR(revert_changelist(db, child_abspath, depth,
873 use_commit_times, changelist_hash,
874 clear_changelists, metadata_only,
875 added_keep_local,
876 cancel_func, cancel_baton,
877 notify_func, notify_baton,
878 iterpool));
879 }
880
881 svn_pool_destroy(iterpool);
882
883 return SVN_NO_ERROR;
884 }
885
886
887 /* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH
888 (which must be either svn_depth_files or svn_depth_immediates) by
889 doing a non-recursive revert on each permissible path. Notifies
890 all reverted paths.
891
892 ### This won't revert a copied dir with one level of children since
893 ### the non-recursive revert on the dir will fail. Not sure how a
894 ### partially recursive revert should handle actual-only nodes. */
895 static svn_error_t *
revert_partial(svn_wc__db_t * db,const char * local_abspath,svn_depth_t depth,svn_boolean_t use_commit_times,svn_boolean_t clear_changelists,svn_boolean_t metadata_only,svn_boolean_t added_keep_local,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)896 revert_partial(svn_wc__db_t *db,
897 const char *local_abspath,
898 svn_depth_t depth,
899 svn_boolean_t use_commit_times,
900 svn_boolean_t clear_changelists,
901 svn_boolean_t metadata_only,
902 svn_boolean_t added_keep_local,
903 svn_cancel_func_t cancel_func,
904 void *cancel_baton,
905 svn_wc_notify_func2_t notify_func,
906 void *notify_baton,
907 apr_pool_t *scratch_pool)
908 {
909 apr_pool_t *iterpool;
910 const apr_array_header_t *children;
911 int i;
912
913 SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates);
914
915 if (cancel_func)
916 SVN_ERR(cancel_func(cancel_baton));
917
918 iterpool = svn_pool_create(scratch_pool);
919
920 /* Revert the root node itself (depth=empty), then move on to the
921 children. */
922 SVN_ERR(revert(db, local_abspath, svn_depth_empty,
923 use_commit_times, clear_changelists, metadata_only,
924 added_keep_local,
925 cancel_func, cancel_baton,
926 notify_func, notify_baton, iterpool));
927
928 SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db,
929 local_abspath,
930 scratch_pool,
931 iterpool));
932 for (i = 0; i < children->nelts; ++i)
933 {
934 const char *child_abspath;
935
936 svn_pool_clear(iterpool);
937
938 child_abspath = svn_dirent_join(local_abspath,
939 APR_ARRAY_IDX(children, i, const char *),
940 iterpool);
941
942 /* For svn_depth_files: don't revert non-files. */
943 if (depth == svn_depth_files)
944 {
945 svn_node_kind_t kind;
946
947 SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath,
948 FALSE /* allow_missing */,
949 TRUE /* show_deleted */,
950 FALSE /* show_hidden */,
951 iterpool));
952 if (kind != svn_node_file)
953 continue;
954 }
955
956 /* Revert just this node (depth=empty). */
957 SVN_ERR(revert(db, child_abspath,
958 svn_depth_empty, use_commit_times, clear_changelists,
959 metadata_only, added_keep_local,
960 cancel_func, cancel_baton,
961 notify_func, notify_baton,
962 iterpool));
963 }
964
965 svn_pool_destroy(iterpool);
966
967 return SVN_NO_ERROR;
968 }
969
970
971 svn_error_t *
svn_wc_revert6(svn_wc_context_t * wc_ctx,const char * local_abspath,svn_depth_t depth,svn_boolean_t use_commit_times,const apr_array_header_t * changelist_filter,svn_boolean_t clear_changelists,svn_boolean_t metadata_only,svn_boolean_t added_keep_local,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)972 svn_wc_revert6(svn_wc_context_t *wc_ctx,
973 const char *local_abspath,
974 svn_depth_t depth,
975 svn_boolean_t use_commit_times,
976 const apr_array_header_t *changelist_filter,
977 svn_boolean_t clear_changelists,
978 svn_boolean_t metadata_only,
979 svn_boolean_t added_keep_local,
980 svn_cancel_func_t cancel_func,
981 void *cancel_baton,
982 svn_wc_notify_func2_t notify_func,
983 void *notify_baton,
984 apr_pool_t *scratch_pool)
985 {
986 if (changelist_filter && changelist_filter->nelts)
987 {
988 apr_hash_t *changelist_hash;
989
990 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
991 scratch_pool));
992 return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath,
993 depth, use_commit_times,
994 changelist_hash,
995 clear_changelists,
996 metadata_only,
997 added_keep_local,
998 cancel_func, cancel_baton,
999 notify_func, notify_baton,
1000 scratch_pool));
1001 }
1002
1003 if (depth == svn_depth_empty || depth == svn_depth_infinity)
1004 return svn_error_trace(revert(wc_ctx->db, local_abspath,
1005 depth, use_commit_times, clear_changelists,
1006 metadata_only,
1007 added_keep_local,
1008 cancel_func, cancel_baton,
1009 notify_func, notify_baton,
1010 scratch_pool));
1011
1012 /* The user may expect svn_depth_files/svn_depth_immediates to work
1013 on copied dirs with one level of children. It doesn't, the user
1014 will get an error and will need to invoke an infinite revert. If
1015 we identified those cases where svn_depth_infinity would not
1016 revert too much we could invoke the recursive call above. */
1017
1018 if (depth == svn_depth_files || depth == svn_depth_immediates)
1019 return svn_error_trace(revert_partial(wc_ctx->db, local_abspath,
1020 depth, use_commit_times,
1021 clear_changelists, metadata_only,
1022 added_keep_local,
1023 cancel_func, cancel_baton,
1024 notify_func, notify_baton,
1025 scratch_pool));
1026
1027 /* Bogus depth. Tell the caller. */
1028 return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL);
1029 }
1030