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 continue; /* Leave local additions alone */
110
111 SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_deletes,
112 db, child_abspath, FALSE,
113 cancel_func, cancel_baton,
114 iterpool));
115
116 if (modified && !all_deletes)
117 continue; /* Something interesting is still there */
118 }
119
120 remove_below = (kind == svn_node_dir)
121 ? svn_depth_immediates
122 : svn_depth_files;
123
124 if ((child_status == svn_wc__db_status_server_excluded ||
125 child_status == svn_wc__db_status_excluded ||
126 child_status == svn_wc__db_status_not_present))
127 {
128 if (new_depth < remove_below)
129 SVN_ERR(svn_wc__db_base_remove(db, child_abspath,
130 FALSE /* keep_as_working */,
131 FALSE, FALSE,
132 SVN_INVALID_REVNUM,
133 NULL, NULL, iterpool));
134
135 continue; /* No recurse */
136 }
137
138 if (new_depth < remove_below)
139 {
140 svn_boolean_t modified, all_deletes;
141
142 SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_deletes,
143 db, child_abspath, FALSE,
144 cancel_func, cancel_baton,
145 iterpool));
146
147 if (!modified || all_deletes)
148 {
149 SVN_ERR(svn_wc__db_base_remove(db, child_abspath,
150 FALSE, FALSE, FALSE,
151 SVN_INVALID_REVNUM,
152 NULL, NULL, iterpool));
153 if (notify_func)
154 {
155 svn_wc_notify_t *notify;
156 notify = svn_wc_create_notify(child_abspath,
157 svn_wc_notify_delete,
158 iterpool);
159 (*notify_func)(notify_baton, notify, iterpool);
160 }
161
162 continue; /* No recurse */
163 }
164
165 /* Fall through: recurse:*/
166 }
167
168 if (kind == svn_node_dir)
169 {
170 SVN_ERR(crop_children(db, child_abspath,
171 child_depth, svn_depth_empty,
172 notify_func, notify_baton,
173 cancel_func, cancel_baton,
174 iterpool));
175 }
176 }
177
178 svn_pool_destroy(iterpool);
179
180 return SVN_NO_ERROR;
181 }
182
183 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)184 svn_wc_exclude(svn_wc_context_t *wc_ctx,
185 const char *local_abspath,
186 svn_cancel_func_t cancel_func,
187 void *cancel_baton,
188 svn_wc_notify_func2_t notify_func,
189 void *notify_baton,
190 apr_pool_t *scratch_pool)
191 {
192 svn_boolean_t is_root, is_switched;
193 svn_wc__db_status_t status;
194 svn_node_kind_t kind;
195 svn_revnum_t revision;
196 svn_depth_t depth;
197 svn_boolean_t modified, all_deletes;
198 const char *repos_relpath, *repos_root, *repos_uuid;
199
200 SVN_ERR(svn_wc__db_is_switched(&is_root, &is_switched, NULL,
201 wc_ctx->db, local_abspath, scratch_pool));
202
203 if (is_root)
204 {
205 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
206 _("Cannot exclude '%s': "
207 "it is a working copy root"),
208 svn_dirent_local_style(local_abspath,
209 scratch_pool));
210 }
211 if (is_switched)
212 {
213 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
214 _("Cannot exclude '%s': "
215 "it is a switched path"),
216 svn_dirent_local_style(local_abspath,
217 scratch_pool));
218 }
219
220 SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, &repos_relpath,
221 &repos_root, &repos_uuid, NULL, NULL, NULL,
222 &depth, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
223 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
224 NULL, NULL, NULL,
225 wc_ctx->db, local_abspath,
226 scratch_pool, scratch_pool));
227
228 switch (status)
229 {
230 case svn_wc__db_status_server_excluded:
231 case svn_wc__db_status_excluded:
232 case svn_wc__db_status_not_present:
233 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
234 _("The node '%s' was not found."),
235 svn_dirent_local_style(local_abspath,
236 scratch_pool));
237
238 case svn_wc__db_status_added:
239 /* Would have to check parents if we want to allow this */
240 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
241 _("Cannot exclude '%s': it is to be added "
242 "to the repository. Try commit instead"),
243 svn_dirent_local_style(local_abspath,
244 scratch_pool));
245 case svn_wc__db_status_deleted:
246 /* Would have to check parents if we want to allow this */
247 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
248 _("Cannot exclude '%s': it is to be deleted "
249 "from the repository. Try commit instead"),
250 svn_dirent_local_style(local_abspath,
251 scratch_pool));
252
253 case svn_wc__db_status_normal:
254 case svn_wc__db_status_incomplete:
255 default:
256 break; /* Ok to exclude */
257 }
258
259 SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_deletes,
260 wc_ctx->db, local_abspath, FALSE,
261 cancel_func, cancel_baton,
262 scratch_pool));
263
264 if (!modified || all_deletes)
265 {
266 /* Remove all working copy data below local_abspath */
267 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
268 FALSE /* keep_working */,
269 FALSE, TRUE,
270 revision,
271 NULL, NULL,
272 scratch_pool));
273
274 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
275 cancel_func, cancel_baton,
276 scratch_pool));
277
278 if (notify_func)
279 {
280 svn_wc_notify_t *notify;
281 notify = svn_wc_create_notify(local_abspath,
282 svn_wc_notify_exclude,
283 scratch_pool);
284 notify_func(notify_baton, notify, scratch_pool);
285 }
286 }
287 else
288 {
289 /* Do the next best thing: retry below this path */
290 SVN_ERR(crop_children(wc_ctx->db, local_abspath, depth, svn_depth_empty,
291 notify_func, notify_baton,
292 cancel_func, cancel_baton,
293 scratch_pool));
294 }
295
296 return SVN_NO_ERROR;
297 }
298
299 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)300 svn_wc_crop_tree2(svn_wc_context_t *wc_ctx,
301 const char *local_abspath,
302 svn_depth_t depth,
303 svn_cancel_func_t cancel_func,
304 void *cancel_baton,
305 svn_wc_notify_func2_t notify_func,
306 void *notify_baton,
307 apr_pool_t *scratch_pool)
308 {
309 svn_wc__db_t *db = wc_ctx->db;
310 svn_wc__db_status_t status;
311 svn_node_kind_t kind;
312 svn_depth_t dir_depth;
313
314 /* Only makes sense when the depth is restrictive. */
315 if (depth == svn_depth_infinity)
316 return SVN_NO_ERROR; /* Nothing to crop */
317 if (!(depth >= svn_depth_empty && depth < svn_depth_infinity))
318 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
319 _("Can only crop a working copy with a restrictive depth"));
320
321 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
322 NULL, NULL, &dir_depth, NULL, NULL, NULL, NULL,
323 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
324 NULL, NULL, NULL, NULL, NULL, NULL,
325 db, local_abspath,
326 scratch_pool, scratch_pool));
327
328 if (kind != svn_node_dir)
329 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
330 _("Can only crop directories"));
331
332 switch (status)
333 {
334 case svn_wc__db_status_not_present:
335 case svn_wc__db_status_server_excluded:
336 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
337 _("The node '%s' was not found."),
338 svn_dirent_local_style(local_abspath,
339 scratch_pool));
340
341 case svn_wc__db_status_deleted:
342 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
343 _("Cannot crop '%s': it is going to be removed "
344 "from repository. Try commit instead"),
345 svn_dirent_local_style(local_abspath,
346 scratch_pool));
347
348 case svn_wc__db_status_added:
349 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
350 _("Cannot crop '%s': it is to be added "
351 "to the repository. Try commit instead"),
352 svn_dirent_local_style(local_abspath,
353 scratch_pool));
354 case svn_wc__db_status_excluded:
355 return SVN_NO_ERROR; /* Nothing to do */
356
357 case svn_wc__db_status_normal:
358 case svn_wc__db_status_incomplete:
359 break;
360
361 default:
362 SVN_ERR_MALFUNCTION();
363 }
364
365 SVN_ERR(crop_children(db, local_abspath, dir_depth, depth,
366 notify_func, notify_baton,
367 cancel_func, cancel_baton, scratch_pool));
368
369 return svn_error_trace(svn_wc__wq_run(db, local_abspath,
370 cancel_func, cancel_baton,
371 scratch_pool));
372 }
373