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(&notify_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                              &notify_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(&notify_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