revert.c revision 299742
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    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. */
102static svn_error_t *
103remove_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). */
124static int
125compare_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 */
149static svn_error_t *
150revert_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 */
250static svn_error_t *
251revert_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 */
280static svn_error_t *
281revert_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 */
457static svn_error_t *
458revert_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. */
708static svn_error_t *
709revert(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. */
786static svn_error_t *
787revert_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. */
867static svn_error_t *
868revert_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
941svn_error_t *
942svn_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}
996