1251881Speter/*
2251881Speter * delete.c:  wrappers around wc delete functionality.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <apr_file_io.h>
31251881Speter#include "svn_hash.h"
32251881Speter#include "svn_types.h"
33251881Speter#include "svn_pools.h"
34251881Speter#include "svn_wc.h"
35251881Speter#include "svn_client.h"
36251881Speter#include "svn_error.h"
37251881Speter#include "svn_dirent_uri.h"
38251881Speter#include "svn_path.h"
39251881Speter#include "client.h"
40251881Speter
41251881Speter#include "private/svn_client_private.h"
42251881Speter#include "private/svn_wc_private.h"
43251881Speter#include "private/svn_ra_private.h"
44251881Speter
45251881Speter#include "svn_private_config.h"
46251881Speter
47251881Speter
48251881Speter/*** Code. ***/
49251881Speter
50251881Speter/* Baton for find_undeletables */
51251881Speterstruct can_delete_baton_t
52251881Speter{
53251881Speter  const char *root_abspath;
54251881Speter  svn_boolean_t target_missing;
55251881Speter};
56251881Speter
57251881Speter/* An svn_wc_status_func4_t callback function for finding
58251881Speter   status structures which are not safely deletable. */
59251881Speterstatic svn_error_t *
60251881Speterfind_undeletables(void *baton,
61251881Speter                  const char *local_abspath,
62251881Speter                  const svn_wc_status3_t *status,
63251881Speter                  apr_pool_t *pool)
64251881Speter{
65251881Speter  if (status->node_status == svn_wc_status_missing)
66251881Speter    {
67251881Speter      struct can_delete_baton_t *cdt = baton;
68251881Speter
69251881Speter      if (strcmp(cdt->root_abspath, local_abspath) == 0)
70251881Speter        cdt->target_missing = TRUE;
71251881Speter    }
72251881Speter
73251881Speter  /* Check for error-ful states. */
74251881Speter  if (status->node_status == svn_wc_status_obstructed)
75251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
76251881Speter                             _("'%s' is in the way of the resource "
77251881Speter                               "actually under version control"),
78251881Speter                             svn_dirent_local_style(local_abspath, pool));
79251881Speter  else if (! status->versioned)
80251881Speter    return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
81251881Speter                             _("'%s' is not under version control"),
82251881Speter                             svn_dirent_local_style(local_abspath, pool));
83251881Speter  else if ((status->node_status == svn_wc_status_added
84251881Speter            || status->node_status == svn_wc_status_replaced)
85251881Speter           && status->text_status == svn_wc_status_normal
86251881Speter           && (status->prop_status == svn_wc_status_normal
87251881Speter               || status->prop_status == svn_wc_status_none))
88251881Speter    {
89251881Speter      /* Unmodified copy. Go ahead, remove it */
90251881Speter    }
91251881Speter  else if (status->node_status != svn_wc_status_normal
92251881Speter           && status->node_status != svn_wc_status_deleted
93251881Speter           && status->node_status != svn_wc_status_missing)
94251881Speter    return svn_error_createf(SVN_ERR_CLIENT_MODIFIED, NULL,
95251881Speter                             _("'%s' has local modifications -- commit or "
96251881Speter                               "revert them first"),
97251881Speter                             svn_dirent_local_style(local_abspath, pool));
98251881Speter
99251881Speter  return SVN_NO_ERROR;
100251881Speter}
101251881Speter
102251881Speter/* Check whether LOCAL_ABSPATH is an external and raise an error if it is.
103251881Speter
104251881Speter   A file external should not be deleted since the file external is
105251881Speter   implemented as a switched file and it would delete the file the
106251881Speter   file external is switched to, which is not the behavior the user
107251881Speter   would probably want.
108251881Speter
109251881Speter   A directory external should not be deleted since it is the root
110251881Speter   of a different working copy. */
111251881Speterstatic svn_error_t *
112251881Spetercheck_external(const char *local_abspath,
113251881Speter               svn_client_ctx_t *ctx,
114251881Speter               apr_pool_t *scratch_pool)
115251881Speter{
116251881Speter  svn_node_kind_t external_kind;
117251881Speter  const char *defining_abspath;
118251881Speter
119251881Speter  SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL,
120251881Speter                                     NULL, NULL,
121251881Speter                                     ctx->wc_ctx, local_abspath,
122251881Speter                                     local_abspath, TRUE,
123251881Speter                                     scratch_pool, scratch_pool));
124251881Speter
125251881Speter  if (external_kind != svn_node_none)
126251881Speter    return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL,
127251881Speter                             _("Cannot remove the external at '%s'; "
128251881Speter                               "please edit or delete the svn:externals "
129251881Speter                               "property on '%s'"),
130251881Speter                             svn_dirent_local_style(local_abspath,
131251881Speter                                                    scratch_pool),
132251881Speter                             svn_dirent_local_style(defining_abspath,
133251881Speter                                                    scratch_pool));
134251881Speter
135251881Speter  return SVN_NO_ERROR;
136251881Speter}
137251881Speter
138251881Speter/* Verify that the path can be deleted without losing stuff,
139251881Speter   i.e. ensure that there are no modified or unversioned resources
140251881Speter   under PATH.  This is similar to checking the output of the status
141251881Speter   command.  CTX is used for the client's config options.  POOL is
142251881Speter   used for all temporary allocations. */
143251881Speterstatic svn_error_t *
144251881Spetercan_delete_node(svn_boolean_t *target_missing,
145251881Speter                const char *local_abspath,
146251881Speter                svn_client_ctx_t *ctx,
147251881Speter                apr_pool_t *scratch_pool)
148251881Speter{
149251881Speter  apr_array_header_t *ignores;
150251881Speter  struct can_delete_baton_t cdt;
151251881Speter
152251881Speter  /* Use an infinite-depth status check to see if there's anything in
153251881Speter     or under PATH which would make it unsafe for deletion.  The
154251881Speter     status callback function find_undeletables() makes the
155251881Speter     determination, returning an error if it finds anything that shouldn't
156251881Speter     be deleted. */
157251881Speter
158251881Speter  SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
159251881Speter
160251881Speter  cdt.root_abspath = local_abspath;
161251881Speter  cdt.target_missing = FALSE;
162251881Speter
163251881Speter  SVN_ERR(svn_wc_walk_status(ctx->wc_ctx,
164251881Speter                             local_abspath,
165251881Speter                             svn_depth_infinity,
166251881Speter                             FALSE /* get_all */,
167251881Speter                             FALSE /* no_ignore */,
168251881Speter                             FALSE /* ignore_text_mod */,
169251881Speter                             ignores,
170251881Speter                             find_undeletables, &cdt,
171251881Speter                             ctx->cancel_func,
172251881Speter                             ctx->cancel_baton,
173251881Speter                             scratch_pool));
174251881Speter
175251881Speter  if (target_missing)
176251881Speter    *target_missing = cdt.target_missing;
177251881Speter
178251881Speter  return SVN_NO_ERROR;
179251881Speter}
180251881Speter
181251881Speter
182251881Speterstatic svn_error_t *
183251881Speterpath_driver_cb_func(void **dir_baton,
184362181Sdim                    const svn_delta_editor_t *editor,
185362181Sdim                    void *edit_baton,
186251881Speter                    void *parent_baton,
187251881Speter                    void *callback_baton,
188251881Speter                    const char *path,
189251881Speter                    apr_pool_t *pool)
190251881Speter{
191251881Speter  *dir_baton = NULL;
192251881Speter  return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool);
193251881Speter}
194251881Speter
195251881Speterstatic svn_error_t *
196251881Spetersingle_repos_delete(svn_ra_session_t *ra_session,
197269833Speter                    const char *base_uri,
198251881Speter                    const apr_array_header_t *relpaths,
199251881Speter                    const apr_hash_t *revprop_table,
200251881Speter                    svn_commit_callback2_t commit_callback,
201251881Speter                    void *commit_baton,
202251881Speter                    svn_client_ctx_t *ctx,
203251881Speter                    apr_pool_t *pool)
204251881Speter{
205251881Speter  const svn_delta_editor_t *editor;
206251881Speter  apr_hash_t *commit_revprops;
207251881Speter  void *edit_baton;
208251881Speter  const char *log_msg;
209251881Speter  int i;
210251881Speter  svn_error_t *err;
211251881Speter
212251881Speter  /* Create new commit items and add them to the array. */
213251881Speter  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
214251881Speter    {
215251881Speter      svn_client_commit_item3_t *item;
216251881Speter      const char *tmp_file;
217251881Speter      apr_array_header_t *commit_items
218251881Speter        = apr_array_make(pool, relpaths->nelts, sizeof(item));
219251881Speter
220251881Speter      for (i = 0; i < relpaths->nelts; i++)
221251881Speter        {
222251881Speter          const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
223251881Speter
224251881Speter          item = svn_client_commit_item3_create(pool);
225269833Speter          item->url = svn_path_url_add_component2(base_uri, relpath, pool);
226251881Speter          item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
227251881Speter          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
228251881Speter        }
229251881Speter      SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
230251881Speter                                      ctx, pool));
231251881Speter      if (! log_msg)
232251881Speter        return SVN_NO_ERROR;
233251881Speter    }
234251881Speter  else
235251881Speter    log_msg = "";
236251881Speter
237251881Speter  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
238251881Speter                                           log_msg, ctx, pool));
239251881Speter
240251881Speter  /* Fetch RA commit editor */
241251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
242251881Speter                        svn_client__get_shim_callbacks(ctx->wc_ctx,
243251881Speter                                                       NULL, pool)));
244251881Speter  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
245251881Speter                                    commit_revprops,
246251881Speter                                    commit_callback,
247251881Speter                                    commit_baton,
248251881Speter                                    NULL, TRUE, /* No lock tokens */
249251881Speter                                    pool));
250251881Speter
251251881Speter  /* Call the path-based editor driver. */
252362181Sdim  err = svn_delta_path_driver3(editor, edit_baton, relpaths, TRUE,
253362181Sdim                               path_driver_cb_func, NULL, pool);
254251881Speter
255251881Speter  if (err)
256251881Speter    {
257251881Speter      return svn_error_trace(
258251881Speter               svn_error_compose_create(err,
259251881Speter                                        editor->abort_edit(edit_baton, pool)));
260251881Speter    }
261251881Speter
262289180Speter  if (ctx->notify_func2)
263289180Speter    {
264289180Speter      svn_wc_notify_t *notify;
265289180Speter      notify = svn_wc_create_notify_url(base_uri,
266289180Speter                                        svn_wc_notify_commit_finalizing,
267289180Speter                                        pool);
268289180Speter      ctx->notify_func2(ctx->notify_baton2, notify, pool);
269289180Speter    }
270289180Speter
271251881Speter  /* Close the edit. */
272251881Speter  return svn_error_trace(editor->close_edit(edit_baton, pool));
273251881Speter}
274251881Speter
275251881Speter
276251881Speter/* Structure for tracking remote delete targets associated with a
277251881Speter   specific repository. */
278251881Speterstruct repos_deletables_t
279251881Speter{
280251881Speter  svn_ra_session_t *ra_session;
281251881Speter  apr_array_header_t *target_uris;
282251881Speter};
283251881Speter
284251881Speter
285251881Speterstatic svn_error_t *
286251881Speterdelete_urls_multi_repos(const apr_array_header_t *uris,
287251881Speter                        const apr_hash_t *revprop_table,
288251881Speter                        svn_commit_callback2_t commit_callback,
289251881Speter                        void *commit_baton,
290251881Speter                        svn_client_ctx_t *ctx,
291251881Speter                        apr_pool_t *pool)
292251881Speter{
293251881Speter  apr_hash_t *deletables = apr_hash_make(pool);
294251881Speter  apr_pool_t *iterpool;
295251881Speter  apr_hash_index_t *hi;
296251881Speter  int i;
297251881Speter
298251881Speter  /* Create a hash mapping repository root URLs -> repos_deletables_t *
299251881Speter     structures.  */
300251881Speter  for (i = 0; i < uris->nelts; i++)
301251881Speter    {
302251881Speter      const char *uri = APR_ARRAY_IDX(uris, i, const char *);
303251881Speter      struct repos_deletables_t *repos_deletables = NULL;
304251881Speter      const char *repos_relpath;
305251881Speter      svn_node_kind_t kind;
306251881Speter
307251881Speter      for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
308251881Speter        {
309289180Speter          const char *repos_root = apr_hash_this_key(hi);
310251881Speter
311251881Speter          repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
312251881Speter          if (repos_relpath)
313251881Speter            {
314251881Speter              /* Great!  We've found another URI underneath this
315251881Speter                 session.  We'll pick out the related RA session for
316251881Speter                 use later, store the new target, and move on.  */
317289180Speter              repos_deletables = apr_hash_this_val(hi);
318251881Speter              APR_ARRAY_PUSH(repos_deletables->target_uris, const char *) =
319251881Speter                apr_pstrdup(pool, uri);
320251881Speter              break;
321251881Speter            }
322251881Speter        }
323251881Speter
324251881Speter      /* If we haven't created a repos_deletable structure for this
325251881Speter         delete target, we need to do.  That means opening up an RA
326251881Speter         session and initializing its targets list.  */
327251881Speter      if (!repos_deletables)
328251881Speter        {
329251881Speter          svn_ra_session_t *ra_session = NULL;
330251881Speter          const char *repos_root;
331251881Speter          apr_array_header_t *target_uris;
332251881Speter
333251881Speter          /* Open an RA session to (ultimately) the root of the
334251881Speter             repository in which URI is found.  */
335251881Speter          SVN_ERR(svn_client_open_ra_session2(&ra_session, uri, NULL,
336251881Speter                                              ctx, pool, pool));
337251881Speter          SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
338251881Speter          SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool));
339251881Speter          repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
340251881Speter
341251881Speter          /* Make a new relpaths list for this repository, and add
342251881Speter             this URI's relpath to it. */
343251881Speter          target_uris = apr_array_make(pool, 1, sizeof(const char *));
344251881Speter          APR_ARRAY_PUSH(target_uris, const char *) = apr_pstrdup(pool, uri);
345251881Speter
346251881Speter          /* Build our repos_deletables_t item and stash it in the
347251881Speter             hash. */
348251881Speter          repos_deletables = apr_pcalloc(pool, sizeof(*repos_deletables));
349251881Speter          repos_deletables->ra_session = ra_session;
350251881Speter          repos_deletables->target_uris = target_uris;
351251881Speter          svn_hash_sets(deletables, repos_root, repos_deletables);
352251881Speter        }
353251881Speter
354251881Speter      /* If we get here, we should have been able to calculate a
355251881Speter         repos_relpath for this URI.  Let's make sure.  (We return an
356251881Speter         RA error code otherwise for 1.6 compatibility.)  */
357251881Speter      if (!repos_relpath || !*repos_relpath)
358251881Speter        return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
359289180Speter                                 _("URL '%s' not within a repository"), uri);
360251881Speter
361251881Speter      /* Now, test to see if the thing actually exists in HEAD. */
362251881Speter      SVN_ERR(svn_ra_check_path(repos_deletables->ra_session, repos_relpath,
363251881Speter                                SVN_INVALID_REVNUM, &kind, pool));
364251881Speter      if (kind == svn_node_none)
365251881Speter        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
366289180Speter                                 _("URL '%s' does not exist"), uri);
367251881Speter    }
368251881Speter
369251881Speter  /* Now we iterate over the DELETABLES hash, issuing a commit for
370251881Speter     each repository with its associated collected targets. */
371251881Speter  iterpool = svn_pool_create(pool);
372251881Speter  for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
373251881Speter    {
374289180Speter      struct repos_deletables_t *repos_deletables = apr_hash_this_val(hi);
375251881Speter      const char *base_uri;
376251881Speter      apr_array_header_t *target_relpaths;
377251881Speter
378251881Speter      svn_pool_clear(iterpool);
379251881Speter
380251881Speter      /* We want to anchor the commit on the longest common path
381251881Speter         across the targets for this one repository.  If, however, one
382251881Speter         of our targets is that longest common path, we need instead
383251881Speter         anchor the commit on that path's immediate parent.  Because
384251881Speter         we're asking svn_uri_condense_targets() to remove
385251881Speter         redundancies, this situation should be detectable by their
386251881Speter         being returned either a) only a single, empty-path, target
387251881Speter         relpath, or b) no target relpaths at all.  */
388251881Speter      SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths,
389251881Speter                                       repos_deletables->target_uris,
390251881Speter                                       TRUE, iterpool, iterpool));
391251881Speter      SVN_ERR_ASSERT(!svn_path_is_empty(base_uri));
392251881Speter      if (target_relpaths->nelts == 0)
393251881Speter        {
394251881Speter          const char *target_relpath;
395251881Speter
396251881Speter          svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
397251881Speter          APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
398251881Speter        }
399251881Speter      else if ((target_relpaths->nelts == 1)
400251881Speter               && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
401251881Speter                                                   const char *))))
402251881Speter        {
403251881Speter          const char *target_relpath;
404251881Speter
405251881Speter          svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
406251881Speter          APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
407251881Speter        }
408251881Speter
409251881Speter      SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool));
410269833Speter      SVN_ERR(single_repos_delete(repos_deletables->ra_session, base_uri,
411251881Speter                                  target_relpaths,
412251881Speter                                  revprop_table, commit_callback,
413251881Speter                                  commit_baton, ctx, iterpool));
414251881Speter    }
415251881Speter  svn_pool_destroy(iterpool);
416251881Speter
417251881Speter  return SVN_NO_ERROR;
418251881Speter}
419251881Speter
420251881Spetersvn_error_t *
421251881Spetersvn_client__wc_delete(const char *local_abspath,
422251881Speter                      svn_boolean_t force,
423251881Speter                      svn_boolean_t dry_run,
424251881Speter                      svn_boolean_t keep_local,
425251881Speter                      svn_wc_notify_func2_t notify_func,
426251881Speter                      void *notify_baton,
427251881Speter                      svn_client_ctx_t *ctx,
428251881Speter                      apr_pool_t *pool)
429251881Speter{
430251881Speter  svn_boolean_t target_missing = FALSE;
431251881Speter
432251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
433251881Speter
434251881Speter  SVN_ERR(check_external(local_abspath, ctx, pool));
435251881Speter
436251881Speter  if (!force && !keep_local)
437251881Speter    /* Verify that there are no "awkward" files */
438251881Speter    SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool));
439251881Speter
440251881Speter  if (!dry_run)
441251881Speter    /* Mark the entry for commit deletion and perform wc deletion */
442251881Speter    return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath,
443251881Speter                                          keep_local || target_missing
444251881Speter                                                            /*keep_local */,
445251881Speter                                          TRUE /* delete_unversioned */,
446251881Speter                                          ctx->cancel_func, ctx->cancel_baton,
447251881Speter                                          notify_func, notify_baton, pool));
448251881Speter
449251881Speter  return SVN_NO_ERROR;
450251881Speter}
451251881Speter
452251881Spetersvn_error_t *
453251881Spetersvn_client__wc_delete_many(const apr_array_header_t *targets,
454251881Speter                           svn_boolean_t force,
455251881Speter                           svn_boolean_t dry_run,
456251881Speter                           svn_boolean_t keep_local,
457251881Speter                           svn_wc_notify_func2_t notify_func,
458251881Speter                           void *notify_baton,
459251881Speter                           svn_client_ctx_t *ctx,
460251881Speter                           apr_pool_t *pool)
461251881Speter{
462251881Speter  int i;
463251881Speter  svn_boolean_t has_non_missing = FALSE;
464251881Speter
465251881Speter  for (i = 0; i < targets->nelts; i++)
466251881Speter    {
467251881Speter      const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
468251881Speter
469251881Speter      SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
470251881Speter
471251881Speter      SVN_ERR(check_external(local_abspath, ctx, pool));
472251881Speter
473251881Speter      if (!force && !keep_local)
474251881Speter        {
475251881Speter          svn_boolean_t missing;
476251881Speter          /* Verify that there are no "awkward" files */
477251881Speter
478251881Speter          SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
479251881Speter
480251881Speter          if (! missing)
481251881Speter            has_non_missing = TRUE;
482251881Speter        }
483251881Speter      else
484251881Speter        has_non_missing = TRUE;
485251881Speter    }
486251881Speter
487251881Speter  if (!dry_run)
488251881Speter    {
489251881Speter      /* Mark the entry for commit deletion and perform wc deletion */
490251881Speter
491251881Speter      /* If none of the targets exists, pass keep local TRUE, to avoid
492251881Speter         deleting case-different files. Detecting this in the generic case
493251881Speter         from the delete code is expensive */
494251881Speter      return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets,
495251881Speter                                                 keep_local || !has_non_missing,
496251881Speter                                                 TRUE /* delete_unversioned_target */,
497251881Speter                                                 ctx->cancel_func,
498251881Speter                                                 ctx->cancel_baton,
499251881Speter                                                 notify_func, notify_baton,
500251881Speter                                                 pool));
501251881Speter    }
502251881Speter
503251881Speter  return SVN_NO_ERROR;
504251881Speter}
505251881Speter
506251881Spetersvn_error_t *
507251881Spetersvn_client_delete4(const apr_array_header_t *paths,
508251881Speter                   svn_boolean_t force,
509251881Speter                   svn_boolean_t keep_local,
510251881Speter                   const apr_hash_t *revprop_table,
511251881Speter                   svn_commit_callback2_t commit_callback,
512251881Speter                   void *commit_baton,
513251881Speter                   svn_client_ctx_t *ctx,
514251881Speter                   apr_pool_t *pool)
515251881Speter{
516251881Speter  svn_boolean_t is_url;
517251881Speter
518251881Speter  if (! paths->nelts)
519251881Speter    return SVN_NO_ERROR;
520251881Speter
521251881Speter  SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
522251881Speter  is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
523251881Speter
524251881Speter  if (is_url)
525251881Speter    {
526251881Speter      SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
527251881Speter                                      commit_baton, ctx, pool));
528251881Speter    }
529251881Speter  else
530251881Speter    {
531251881Speter      const char *local_abspath;
532251881Speter      apr_hash_t *wcroots;
533251881Speter      apr_hash_index_t *hi;
534251881Speter      int i;
535251881Speter      int j;
536251881Speter      apr_pool_t *iterpool;
537251881Speter      svn_boolean_t is_new_target;
538251881Speter
539251881Speter      /* Build a map of wcroots and targets within them. */
540251881Speter      wcroots = apr_hash_make(pool);
541251881Speter      iterpool = svn_pool_create(pool);
542251881Speter      for (i = 0; i < paths->nelts; i++)
543251881Speter        {
544251881Speter          const char *wcroot_abspath;
545251881Speter          apr_array_header_t *targets;
546251881Speter
547251881Speter          svn_pool_clear(iterpool);
548251881Speter
549251881Speter          /* See if the user wants us to stop. */
550251881Speter          if (ctx->cancel_func)
551251881Speter            SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
552251881Speter
553251881Speter          SVN_ERR(svn_dirent_get_absolute(&local_abspath,
554251881Speter                                          APR_ARRAY_IDX(paths, i,
555251881Speter                                                        const char *),
556251881Speter                                          pool));
557251881Speter          SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
558251881Speter                                     local_abspath, pool, iterpool));
559251881Speter          targets = svn_hash_gets(wcroots, wcroot_abspath);
560251881Speter          if (targets == NULL)
561251881Speter            {
562251881Speter              targets = apr_array_make(pool, 1, sizeof(const char *));
563251881Speter              svn_hash_sets(wcroots, wcroot_abspath, targets);
564251881Speter             }
565251881Speter
566251881Speter          /* Make sure targets are unique. */
567251881Speter          is_new_target = TRUE;
568251881Speter          for (j = 0; j < targets->nelts; j++)
569251881Speter            {
570251881Speter              if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
571251881Speter                         local_abspath) == 0)
572251881Speter                {
573251881Speter                  is_new_target = FALSE;
574251881Speter                  break;
575251881Speter                }
576251881Speter            }
577251881Speter
578251881Speter          if (is_new_target)
579251881Speter            APR_ARRAY_PUSH(targets, const char *) = local_abspath;
580251881Speter        }
581251881Speter
582251881Speter      /* Delete the targets from each working copy in turn. */
583251881Speter      for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
584251881Speter        {
585251881Speter          const char *root_abspath;
586289180Speter          const apr_array_header_t *targets = apr_hash_this_val(hi);
587251881Speter
588251881Speter          svn_pool_clear(iterpool);
589251881Speter
590251881Speter          SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
591251881Speter                                              FALSE, iterpool, iterpool));
592251881Speter
593251881Speter          SVN_WC__CALL_WITH_WRITE_LOCK(
594251881Speter            svn_client__wc_delete_many(targets, force, FALSE, keep_local,
595251881Speter                                       ctx->notify_func2, ctx->notify_baton2,
596251881Speter                                       ctx, iterpool),
597251881Speter            ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
598251881Speter            iterpool);
599251881Speter        }
600251881Speter      svn_pool_destroy(iterpool);
601251881Speter    }
602251881Speter
603251881Speter  return SVN_NO_ERROR;
604251881Speter}
605