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  */
47 static svn_error_t *
crop_children(svn_wc__db_t * db,const char * local_abspath,svn_depth_t dir_depth,svn_depth_t new_depth,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)48 crop_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 
188 svn_error_t *
svn_wc_exclude(svn_wc_context_t * wc_ctx,const char * local_abspath,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)189 svn_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 
304 svn_error_t *
svn_wc_crop_tree2(svn_wc_context_t * wc_ctx,const char * local_abspath,svn_depth_t depth,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)305 svn_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