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
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. */
101static svn_error_t *
102remove_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). */
123static int
124compare_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 */
148static svn_error_t *
149revert_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 */
259static svn_error_t *
260revert_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
639svn_error_t *
640svn_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. */
691static svn_error_t *
692revert_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. */
768static svn_error_t *
769revert_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
838svn_error_t *
839svn_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}
887