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