crop.c revision 362181
1/*
2 * crop.c: Cropping the WC
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 "svn_wc.h"
27#include "svn_pools.h"
28#include "svn_error.h"
29#include "svn_error_codes.h"
30#include "svn_dirent_uri.h"
31#include "svn_path.h"
32
33#include "wc.h"
34#include "workqueue.h"
35
36#include "svn_private_config.h"
37
38/* Helper function that crops the children of the LOCAL_ABSPATH, under the
39 * constraint of NEW_DEPTH. The DIR_PATH itself will never be cropped. The
40 * whole subtree should have been locked.
41 *
42 * DIR_DEPTH is the current depth of LOCAL_ABSPATH as stored in DB.
43 *
44 * If NOTIFY_FUNC is not null, each file and ROOT of subtree will be reported
45 * upon remove.
46 */
47static svn_error_t *
48crop_children(svn_wc__db_t *db,
49              const char *local_abspath,
50              svn_depth_t dir_depth,
51              svn_depth_t new_depth,
52              svn_wc_notify_func2_t notify_func,
53              void *notify_baton,
54              svn_cancel_func_t cancel_func,
55              void *cancel_baton,
56              apr_pool_t *scratch_pool)
57{
58  const apr_array_header_t *children;
59  apr_pool_t *iterpool;
60  int i;
61
62  SVN_ERR_ASSERT(new_depth >= svn_depth_empty
63                 && new_depth <= svn_depth_infinity);
64
65  if (cancel_func)
66    SVN_ERR(cancel_func(cancel_baton));
67
68  iterpool = svn_pool_create(scratch_pool);
69
70  if (dir_depth == svn_depth_unknown)
71    dir_depth = svn_depth_infinity;
72
73  /* Update the depth of target first, if needed. */
74  if (dir_depth > new_depth)
75    SVN_ERR(svn_wc__db_op_set_base_depth(db, local_abspath, new_depth,
76                                         iterpool));
77
78  /* Looping over current directory's SVN entries: */
79  SVN_ERR(svn_wc__db_base_get_children(&children, db, local_abspath,
80                                       scratch_pool, iterpool));
81
82  for (i = 0; i < children->nelts; i++)
83    {
84      const char *child_name = APR_ARRAY_IDX(children, i, const char *);
85      const char *child_abspath;
86      svn_wc__db_status_t child_status;
87      svn_node_kind_t kind;
88      svn_depth_t child_depth;
89      svn_boolean_t have_work;
90      svn_depth_t remove_below;
91
92      svn_pool_clear(iterpool);
93
94      /* Get the next node */
95      child_abspath = svn_dirent_join(local_abspath, child_name, iterpool);
96
97      SVN_ERR(svn_wc__db_read_info(&child_status, &kind, NULL, NULL, NULL,
98                                   NULL,NULL, NULL, NULL, &child_depth,
99                                   NULL, NULL, NULL, NULL, NULL, NULL,
100                                   NULL, NULL, NULL, NULL, NULL, NULL,
101                                   NULL, NULL, NULL, NULL, &have_work,
102                                   db, child_abspath, iterpool, iterpool));
103
104      if (have_work)
105        {
106          svn_boolean_t modified, all_deletes;
107
108          if (child_status != svn_wc__db_status_deleted)
109            {
110              /* ### TODO: Check for issue #4636 constraints, but not only on
111                     this node, but also at all its descendants: We don't want
112                     to remove moved_from information here! */
113              continue; /* Leave local additions alone */
114            }
115
116          SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_deletes,
117                                              db, child_abspath, FALSE,
118                                              cancel_func, cancel_baton,
119                                              iterpool));
120
121          if (modified && !all_deletes)
122            continue; /* Something interesting is still there */
123        }
124
125      remove_below = (kind == svn_node_dir)
126                       ? svn_depth_immediates
127                       : svn_depth_files;
128
129      if ((child_status == svn_wc__db_status_server_excluded ||
130           child_status == svn_wc__db_status_excluded ||
131           child_status == svn_wc__db_status_not_present))
132        {
133          if (new_depth < remove_below)
134            SVN_ERR(svn_wc__db_base_remove(db, child_abspath,
135                                           FALSE /* keep_as_working */,
136                                           FALSE, FALSE,
137                                           SVN_INVALID_REVNUM,
138                                           NULL, NULL, iterpool));
139
140          continue; /* No recurse */
141        }
142
143      if (new_depth < remove_below)
144        {
145          svn_boolean_t modified, all_deletes;
146
147          SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_deletes,
148                                              db, child_abspath, FALSE,
149                                              cancel_func, cancel_baton,
150                                              iterpool));
151
152          if (!modified || all_deletes)
153            {
154              SVN_ERR(svn_wc__db_base_remove(db, child_abspath,
155                                             FALSE, FALSE, FALSE,
156                                             SVN_INVALID_REVNUM,
157                                             NULL, NULL, iterpool));
158              if (notify_func)
159                {
160                  svn_wc_notify_t *notify;
161                  notify = svn_wc_create_notify(child_abspath,
162                                                svn_wc_notify_delete,
163                                                iterpool);
164                  (*notify_func)(notify_baton, notify, iterpool);
165                }
166
167              continue; /* No recurse */
168            }
169
170          /* Fall through: recurse:*/
171        }
172
173      if (kind == svn_node_dir)
174        {
175          SVN_ERR(crop_children(db, child_abspath,
176                                child_depth, svn_depth_empty,
177                                notify_func, notify_baton,
178                                cancel_func, cancel_baton,
179                                iterpool));
180        }
181    }
182
183  svn_pool_destroy(iterpool);
184
185  return SVN_NO_ERROR;
186}
187
188svn_error_t *
189svn_wc_exclude(svn_wc_context_t *wc_ctx,
190               const char *local_abspath,
191               svn_cancel_func_t cancel_func,
192               void *cancel_baton,
193               svn_wc_notify_func2_t notify_func,
194               void *notify_baton,
195               apr_pool_t *scratch_pool)
196{
197  svn_boolean_t is_root, is_switched;
198  svn_wc__db_status_t status;
199  svn_node_kind_t kind;
200  svn_revnum_t revision;
201  svn_depth_t depth;
202  svn_boolean_t modified, all_deletes;
203  const char *repos_relpath, *repos_root, *repos_uuid;
204
205  SVN_ERR(svn_wc__db_is_switched(&is_root, &is_switched, NULL,
206                                 wc_ctx->db, local_abspath, scratch_pool));
207
208  if (is_root)
209    {
210       return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
211                                _("Cannot exclude '%s': "
212                                  "it is a working copy root"),
213                                svn_dirent_local_style(local_abspath,
214                                                       scratch_pool));
215    }
216  if (is_switched)
217    {
218      return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
219                               _("Cannot exclude '%s': "
220                                 "it is a switched path"),
221                               svn_dirent_local_style(local_abspath,
222                                                      scratch_pool));
223    }
224
225  SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, &repos_relpath,
226                               &repos_root, &repos_uuid, NULL, NULL, NULL,
227                               &depth, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
228                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
229                               NULL, NULL, NULL,
230                               wc_ctx->db, local_abspath,
231                               scratch_pool, scratch_pool));
232
233  switch (status)
234    {
235      case svn_wc__db_status_server_excluded:
236      case svn_wc__db_status_excluded:
237      case svn_wc__db_status_not_present:
238        return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
239                                 _("The node '%s' was not found."),
240                                 svn_dirent_local_style(local_abspath,
241                                                        scratch_pool));
242
243      case svn_wc__db_status_added:
244        /* Would have to check parents if we want to allow this */
245        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
246                                 _("Cannot exclude '%s': it is to be added "
247                                   "to the repository. Try commit instead"),
248                                 svn_dirent_local_style(local_abspath,
249                                                        scratch_pool));
250      case svn_wc__db_status_deleted:
251        /* Would have to check parents if we want to allow this */
252        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
253                                 _("Cannot exclude '%s': it is to be deleted "
254                                   "from the repository. Try commit instead"),
255                                 svn_dirent_local_style(local_abspath,
256                                                        scratch_pool));
257
258      case svn_wc__db_status_normal:
259      case svn_wc__db_status_incomplete:
260      default:
261        break; /* Ok to exclude */
262    }
263
264  SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_deletes,
265                                      wc_ctx->db, local_abspath, FALSE,
266                                      cancel_func, cancel_baton,
267                                      scratch_pool));
268
269  if (!modified || all_deletes)
270    {
271      /* Remove all working copy data below local_abspath */
272      SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
273                                     FALSE /* keep_working */,
274                                     FALSE, TRUE,
275                                     revision,
276                                     NULL, NULL,
277                                     scratch_pool));
278
279      SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
280                             cancel_func, cancel_baton,
281                             scratch_pool));
282
283      if (notify_func)
284        {
285          svn_wc_notify_t *notify;
286          notify = svn_wc_create_notify(local_abspath,
287                                        svn_wc_notify_exclude,
288                                        scratch_pool);
289          notify_func(notify_baton, notify, scratch_pool);
290        }
291    }
292  else
293    {
294      /* Do the next best thing: retry below this path */
295      SVN_ERR(crop_children(wc_ctx->db, local_abspath, depth, svn_depth_empty,
296                            notify_func, notify_baton,
297                            cancel_func, cancel_baton,
298                            scratch_pool));
299    }
300
301  return SVN_NO_ERROR;
302}
303
304svn_error_t *
305svn_wc_crop_tree2(svn_wc_context_t *wc_ctx,
306                  const char *local_abspath,
307                  svn_depth_t depth,
308                  svn_cancel_func_t cancel_func,
309                  void *cancel_baton,
310                  svn_wc_notify_func2_t notify_func,
311                  void *notify_baton,
312                  apr_pool_t *scratch_pool)
313{
314  svn_wc__db_t *db = wc_ctx->db;
315  svn_wc__db_status_t status;
316  svn_node_kind_t kind;
317  svn_depth_t dir_depth;
318
319  /* Only makes sense when the depth is restrictive. */
320  if (depth == svn_depth_infinity)
321    return SVN_NO_ERROR; /* Nothing to crop */
322  if (!(depth >= svn_depth_empty && depth < svn_depth_infinity))
323    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
324      _("Can only crop a working copy with a restrictive depth"));
325
326  SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
327                               NULL, NULL, &dir_depth, NULL, NULL, NULL, NULL,
328                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
329                               NULL, NULL, NULL, NULL, NULL, NULL,
330                               db, local_abspath,
331                               scratch_pool, scratch_pool));
332
333  if (kind != svn_node_dir)
334    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
335      _("Can only crop directories"));
336
337  switch (status)
338    {
339      case svn_wc__db_status_not_present:
340      case svn_wc__db_status_server_excluded:
341        return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
342                                 _("The node '%s' was not found."),
343                                 svn_dirent_local_style(local_abspath,
344                                                        scratch_pool));
345
346      case svn_wc__db_status_deleted:
347        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
348                               _("Cannot crop '%s': it is going to be removed "
349                                 "from repository. Try commit instead"),
350                               svn_dirent_local_style(local_abspath,
351                                                      scratch_pool));
352
353      case svn_wc__db_status_added:
354        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
355                                 _("Cannot crop '%s': it is to be added "
356                                   "to the repository. Try commit instead"),
357                                 svn_dirent_local_style(local_abspath,
358                                                        scratch_pool));
359      case svn_wc__db_status_excluded:
360        return SVN_NO_ERROR; /* Nothing to do */
361
362      case svn_wc__db_status_normal:
363      case svn_wc__db_status_incomplete:
364        break;
365
366      default:
367        SVN_ERR_MALFUNCTION();
368    }
369
370  SVN_ERR(crop_children(db, local_abspath, dir_depth, depth,
371                        notify_func, notify_baton,
372                        cancel_func, cancel_baton, scratch_pool));
373
374  return svn_error_trace(svn_wc__wq_run(db, local_abspath,
375                                        cancel_func, cancel_baton,
376                                        scratch_pool));
377}
378