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