1251881Speter/*
2251881Speter * commit_util.c:  Driver for the WC commit process.
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#include <string.h>
28251881Speter
29251881Speter#include <apr_pools.h>
30251881Speter#include <apr_hash.h>
31251881Speter#include <apr_md5.h>
32251881Speter
33251881Speter#include "client.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_path.h"
36251881Speter#include "svn_types.h"
37251881Speter#include "svn_pools.h"
38251881Speter#include "svn_props.h"
39251881Speter#include "svn_iter.h"
40251881Speter#include "svn_hash.h"
41251881Speter
42251881Speter#include <assert.h>
43251881Speter
44251881Speter#include "svn_private_config.h"
45251881Speter#include "private/svn_wc_private.h"
46251881Speter#include "private/svn_client_private.h"
47299742Sdim#include "private/svn_sorts_private.h"
48251881Speter
49251881Speter/*** Uncomment this to turn on commit driver debugging. ***/
50251881Speter/*
51251881Speter#define SVN_CLIENT_COMMIT_DEBUG
52251881Speter*/
53251881Speter
54251881Speter/* Wrap an RA error in a nicer error if one is available. */
55251881Speterstatic svn_error_t *
56251881Speterfixup_commit_error(const char *local_abspath,
57251881Speter                   const char *base_url,
58251881Speter                   const char *path,
59251881Speter                   svn_node_kind_t kind,
60251881Speter                   svn_error_t *err,
61251881Speter                   svn_client_ctx_t *ctx,
62251881Speter                   apr_pool_t *scratch_pool)
63251881Speter{
64251881Speter  if (err->apr_err == SVN_ERR_FS_NOT_FOUND
65299742Sdim      || err->apr_err == SVN_ERR_FS_CONFLICT
66251881Speter      || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS
67251881Speter      || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE
68251881Speter      || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND
69251881Speter      || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS
70299742Sdim      || err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED
71251881Speter      || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE))
72251881Speter    {
73251881Speter      if (ctx->notify_func2)
74251881Speter        {
75251881Speter          svn_wc_notify_t *notify;
76251881Speter
77251881Speter          if (local_abspath)
78251881Speter            notify = svn_wc_create_notify(local_abspath,
79251881Speter                                          svn_wc_notify_failed_out_of_date,
80251881Speter                                          scratch_pool);
81251881Speter          else
82251881Speter            notify = svn_wc_create_notify_url(
83251881Speter                                svn_path_url_add_component2(base_url, path,
84251881Speter                                                            scratch_pool),
85251881Speter                                svn_wc_notify_failed_out_of_date,
86251881Speter                                scratch_pool);
87251881Speter
88251881Speter          notify->kind = kind;
89251881Speter          notify->err = err;
90251881Speter
91251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
92251881Speter        }
93251881Speter
94251881Speter      return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err,
95251881Speter                               (kind == svn_node_dir
96251881Speter                                 ? _("Directory '%s' is out of date")
97251881Speter                                 : _("File '%s' is out of date")),
98251881Speter                               local_abspath
99251881Speter                                  ? svn_dirent_local_style(local_abspath,
100251881Speter                                                           scratch_pool)
101251881Speter                                  : svn_path_url_add_component2(base_url,
102251881Speter                                                                path,
103251881Speter                                                                scratch_pool));
104251881Speter    }
105251881Speter  else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN)
106251881Speter           || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH
107299742Sdim           || err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN
108251881Speter           || err->apr_err == SVN_ERR_RA_NOT_LOCKED)
109251881Speter    {
110251881Speter      if (ctx->notify_func2)
111251881Speter        {
112251881Speter          svn_wc_notify_t *notify;
113251881Speter
114251881Speter          if (local_abspath)
115251881Speter            notify = svn_wc_create_notify(local_abspath,
116251881Speter                                          svn_wc_notify_failed_locked,
117251881Speter                                          scratch_pool);
118251881Speter          else
119251881Speter            notify = svn_wc_create_notify_url(
120251881Speter                                svn_path_url_add_component2(base_url, path,
121251881Speter                                                            scratch_pool),
122251881Speter                                svn_wc_notify_failed_locked,
123251881Speter                                scratch_pool);
124251881Speter
125251881Speter          notify->kind = kind;
126251881Speter          notify->err = err;
127251881Speter
128251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
129251881Speter        }
130251881Speter
131251881Speter      return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err,
132251881Speter                   (kind == svn_node_dir
133251881Speter                     ? _("Directory '%s' is locked in another working copy")
134251881Speter                     : _("File '%s' is locked in another working copy")),
135251881Speter                   local_abspath
136251881Speter                      ? svn_dirent_local_style(local_abspath,
137251881Speter                                               scratch_pool)
138251881Speter                      : svn_path_url_add_component2(base_url,
139251881Speter                                                    path,
140251881Speter                                                    scratch_pool));
141251881Speter    }
142251881Speter  else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)
143251881Speter           || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE)
144251881Speter    {
145251881Speter      if (ctx->notify_func2)
146251881Speter        {
147251881Speter          svn_wc_notify_t *notify;
148251881Speter
149251881Speter          if (local_abspath)
150251881Speter            notify = svn_wc_create_notify(
151251881Speter                                    local_abspath,
152251881Speter                                    svn_wc_notify_failed_forbidden_by_server,
153251881Speter                                    scratch_pool);
154251881Speter          else
155251881Speter            notify = svn_wc_create_notify_url(
156251881Speter                                svn_path_url_add_component2(base_url, path,
157251881Speter                                                            scratch_pool),
158251881Speter                                svn_wc_notify_failed_forbidden_by_server,
159251881Speter                                scratch_pool);
160251881Speter
161251881Speter          notify->kind = kind;
162251881Speter          notify->err = err;
163251881Speter
164251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
165251881Speter        }
166251881Speter
167251881Speter      return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err,
168251881Speter                   (kind == svn_node_dir
169251881Speter                     ? _("Changing directory '%s' is forbidden by the server")
170251881Speter                     : _("Changing file '%s' is forbidden by the server")),
171251881Speter                   local_abspath
172251881Speter                      ? svn_dirent_local_style(local_abspath,
173251881Speter                                               scratch_pool)
174251881Speter                      : svn_path_url_add_component2(base_url,
175251881Speter                                                    path,
176251881Speter                                                    scratch_pool));
177251881Speter    }
178251881Speter  else
179251881Speter    return err;
180251881Speter}
181251881Speter
182251881Speter
183251881Speter/*** Harvesting Commit Candidates ***/
184251881Speter
185251881Speter
186251881Speter/* Add a new commit candidate (described by all parameters except
187251881Speter   `COMMITTABLES') to the COMMITTABLES hash.  All of the commit item's
188251881Speter   members are allocated out of RESULT_POOL.
189251881Speter
190251881Speter   If the state flag specifies that a lock must be used, store the token in LOCK
191251881Speter   in lock_tokens.
192251881Speter */
193251881Speterstatic svn_error_t *
194251881Speteradd_committable(svn_client__committables_t *committables,
195251881Speter                const char *local_abspath,
196251881Speter                svn_node_kind_t kind,
197251881Speter                const char *repos_root_url,
198251881Speter                const char *repos_relpath,
199251881Speter                svn_revnum_t revision,
200251881Speter                const char *copyfrom_relpath,
201251881Speter                svn_revnum_t copyfrom_rev,
202251881Speter                const char *moved_from_abspath,
203251881Speter                apr_byte_t state_flags,
204251881Speter                apr_hash_t *lock_tokens,
205251881Speter                const svn_lock_t *lock,
206251881Speter                apr_pool_t *result_pool,
207251881Speter                apr_pool_t *scratch_pool)
208251881Speter{
209251881Speter  apr_array_header_t *array;
210251881Speter  svn_client_commit_item3_t *new_item;
211251881Speter
212251881Speter  /* Sanity checks. */
213251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
214251881Speter  SVN_ERR_ASSERT(repos_root_url && repos_relpath);
215251881Speter
216251881Speter  /* ### todo: Get the canonical repository for this item, which will
217251881Speter     be the real key for the COMMITTABLES hash, instead of the above
218251881Speter     bogosity. */
219251881Speter  array = svn_hash_gets(committables->by_repository, repos_root_url);
220251881Speter
221251881Speter  /* E-gads!  There is no array for this repository yet!  Oh, no
222251881Speter     problem, we'll just create (and add to the hash) one. */
223251881Speter  if (array == NULL)
224251881Speter    {
225251881Speter      array = apr_array_make(result_pool, 1, sizeof(new_item));
226251881Speter      svn_hash_sets(committables->by_repository,
227251881Speter                    apr_pstrdup(result_pool, repos_root_url), array);
228251881Speter    }
229251881Speter
230251881Speter  /* Now update pointer values, ensuring that their allocations live
231251881Speter     in POOL. */
232251881Speter  new_item = svn_client_commit_item3_create(result_pool);
233251881Speter  new_item->path           = apr_pstrdup(result_pool, local_abspath);
234251881Speter  new_item->kind           = kind;
235251881Speter  new_item->url            = svn_path_url_add_component2(repos_root_url,
236251881Speter                                                         repos_relpath,
237251881Speter                                                         result_pool);
238251881Speter  new_item->revision       = revision;
239251881Speter  new_item->copyfrom_url   = copyfrom_relpath
240251881Speter                                ? svn_path_url_add_component2(repos_root_url,
241251881Speter                                                              copyfrom_relpath,
242251881Speter                                                              result_pool)
243251881Speter                                : NULL;
244251881Speter  new_item->copyfrom_rev   = copyfrom_rev;
245251881Speter  new_item->state_flags    = state_flags;
246251881Speter  new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
247251881Speter                                                   sizeof(svn_prop_t *));
248251881Speter
249251881Speter  if (moved_from_abspath)
250251881Speter    new_item->moved_from_abspath = apr_pstrdup(result_pool,
251251881Speter                                               moved_from_abspath);
252251881Speter
253251881Speter  /* Now, add the commit item to the array. */
254251881Speter  APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
255251881Speter
256251881Speter  /* ... and to the hash. */
257251881Speter  svn_hash_sets(committables->by_path, new_item->path, new_item);
258251881Speter
259251881Speter  if (lock
260251881Speter      && lock_tokens
261251881Speter      && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN))
262251881Speter    {
263251881Speter      svn_hash_sets(lock_tokens, new_item->url,
264251881Speter                    apr_pstrdup(result_pool, lock->token));
265251881Speter    }
266251881Speter
267251881Speter  return SVN_NO_ERROR;
268251881Speter}
269251881Speter
270251881Speter/* If there is a commit item for PATH in COMMITTABLES, return it, else
271251881Speter   return NULL.  Use POOL for temporary allocation only. */
272251881Speterstatic svn_client_commit_item3_t *
273251881Speterlook_up_committable(svn_client__committables_t *committables,
274251881Speter                    const char *path,
275251881Speter                    apr_pool_t *pool)
276251881Speter{
277251881Speter  return (svn_client_commit_item3_t *)
278251881Speter      svn_hash_gets(committables->by_path, path);
279251881Speter}
280251881Speter
281251881Speter/* Helper function for svn_client__harvest_committables().
282251881Speter * Determine whether we are within a tree-conflicted subtree of the
283251881Speter * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
284251881Speterstatic svn_error_t *
285251881Speterbail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx,
286251881Speter                                 const char *local_abspath,
287251881Speter                                 svn_wc_notify_func2_t notify_func,
288251881Speter                                 void *notify_baton,
289251881Speter                                 apr_pool_t *scratch_pool)
290251881Speter{
291251881Speter  const char *wcroot_abspath;
292251881Speter
293251881Speter  SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath,
294251881Speter                             scratch_pool, scratch_pool));
295251881Speter
296251881Speter  local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
297251881Speter
298251881Speter  while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath))
299251881Speter    {
300251881Speter      svn_boolean_t tree_conflicted;
301251881Speter
302251881Speter      /* Check if the parent has tree conflicts */
303251881Speter      SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
304251881Speter                                   wc_ctx, local_abspath, scratch_pool));
305251881Speter      if (tree_conflicted)
306251881Speter        {
307251881Speter          if (notify_func != NULL)
308251881Speter            {
309251881Speter              notify_func(notify_baton,
310251881Speter                          svn_wc_create_notify(local_abspath,
311251881Speter                                               svn_wc_notify_failed_conflict,
312251881Speter                                               scratch_pool),
313251881Speter                          scratch_pool);
314251881Speter            }
315251881Speter
316251881Speter          return svn_error_createf(
317251881Speter                   SVN_ERR_WC_FOUND_CONFLICT, NULL,
318251881Speter                   _("Aborting commit: '%s' remains in tree-conflict"),
319251881Speter                   svn_dirent_local_style(local_abspath, scratch_pool));
320251881Speter        }
321251881Speter
322251881Speter      /* Step outwards */
323251881Speter      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
324251881Speter        break;
325251881Speter      else
326251881Speter        local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
327251881Speter    }
328251881Speter
329251881Speter  return SVN_NO_ERROR;
330251881Speter}
331251881Speter
332251881Speter
333251881Speter/* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using
334251881Speter   WC_CTX and add those candidates to COMMITTABLES.  If in ADDS_ONLY modes,
335251881Speter   only new additions are recognized.
336251881Speter
337251881Speter   DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH
338251881Speter   when LOCAL_ABSPATH is itself a directory; see
339251881Speter   svn_client__harvest_committables() for its behavior.
340251881Speter
341251881Speter   Lock tokens of candidates will be added to LOCK_TOKENS, if
342251881Speter   non-NULL.  JUST_LOCKED indicates whether to treat non-modified items with
343251881Speter   lock tokens as commit candidates.
344251881Speter
345251881Speter   If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to
346251881Speter   be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as
347251881Speter   items to delete in the copy destination.  COPY_MODE_ROOT should be set TRUE
348251881Speter   for the first call for which COPY_MODE is TRUE, i.e. not for the
349251881Speter   recursive calls, and FALSE otherwise.
350251881Speter
351251881Speter   If CHANGELISTS is non-NULL, it is a hash whose keys are const char *
352251881Speter   changelist names used as a restrictive filter
353251881Speter   when harvesting committables; that is, don't add a path to
354251881Speter   COMMITTABLES unless it's a member of one of those changelists.
355251881Speter
356251881Speter   IS_EXPLICIT_TARGET should always be passed as TRUE, except when
357251881Speter   harvest_committables() calls itself in recursion. This provides a way to
358251881Speter   tell whether LOCAL_ABSPATH was an original target or whether it was reached
359251881Speter   by recursing deeper into a dir target. (This is used to skip all file
360251881Speter   externals that aren't explicit commit targets.)
361251881Speter
362251881Speter   DANGLERS is a hash table mapping const char* absolute paths of a parent
363251881Speter   to a const char * absolute path of a child. See the comment about
364251881Speter   danglers at the top of svn_client__harvest_committables().
365251881Speter
366251881Speter   If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
367251881Speter   if the user has cancelled the operation.
368251881Speter
369251881Speter   Any items added to COMMITTABLES are allocated from the COMITTABLES
370251881Speter   hash pool, not POOL.  SCRATCH_POOL is used for temporary allocations. */
371251881Speter
372251881Speterstruct harvest_baton
373251881Speter{
374251881Speter  /* Static data */
375251881Speter  const char *root_abspath;
376251881Speter  svn_client__committables_t *committables;
377251881Speter  apr_hash_t *lock_tokens;
378251881Speter  const char *commit_relpath; /* Valid for the harvest root */
379251881Speter  svn_depth_t depth;
380251881Speter  svn_boolean_t just_locked;
381251881Speter  apr_hash_t *changelists;
382251881Speter  apr_hash_t *danglers;
383251881Speter  svn_client__check_url_kind_t check_url_func;
384251881Speter  void *check_url_baton;
385251881Speter  svn_wc_notify_func2_t notify_func;
386251881Speter  void *notify_baton;
387251881Speter  svn_wc_context_t *wc_ctx;
388251881Speter  apr_pool_t *result_pool;
389251881Speter
390251881Speter  /* Harvester state */
391251881Speter  const char *skip_below_abspath; /* If non-NULL, skip everything below */
392251881Speter};
393251881Speter
394251881Speterstatic svn_error_t *
395251881Speterharvest_status_callback(void *status_baton,
396251881Speter                        const char *local_abspath,
397251881Speter                        const svn_wc_status3_t *status,
398251881Speter                        apr_pool_t *scratch_pool);
399251881Speter
400251881Speterstatic svn_error_t *
401251881Speterharvest_committables(const char *local_abspath,
402251881Speter                     svn_client__committables_t *committables,
403251881Speter                     apr_hash_t *lock_tokens,
404251881Speter                     const char *copy_mode_relpath,
405251881Speter                     svn_depth_t depth,
406251881Speter                     svn_boolean_t just_locked,
407251881Speter                     apr_hash_t *changelists,
408251881Speter                     apr_hash_t *danglers,
409251881Speter                     svn_client__check_url_kind_t check_url_func,
410251881Speter                     void *check_url_baton,
411251881Speter                     svn_cancel_func_t cancel_func,
412251881Speter                     void *cancel_baton,
413251881Speter                     svn_wc_notify_func2_t notify_func,
414251881Speter                     void *notify_baton,
415251881Speter                     svn_wc_context_t *wc_ctx,
416251881Speter                     apr_pool_t *result_pool,
417251881Speter                     apr_pool_t *scratch_pool)
418251881Speter{
419251881Speter  struct harvest_baton baton;
420251881Speter
421251881Speter  SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked);
422251881Speter
423251881Speter  baton.root_abspath = local_abspath;
424251881Speter  baton.committables = committables;
425251881Speter  baton.lock_tokens = lock_tokens;
426251881Speter  baton.commit_relpath = copy_mode_relpath;
427251881Speter  baton.depth = depth;
428251881Speter  baton.just_locked = just_locked;
429251881Speter  baton.changelists = changelists;
430251881Speter  baton.danglers = danglers;
431251881Speter  baton.check_url_func = check_url_func;
432251881Speter  baton.check_url_baton = check_url_baton;
433251881Speter  baton.notify_func = notify_func;
434251881Speter  baton.notify_baton = notify_baton;
435251881Speter  baton.wc_ctx = wc_ctx;
436251881Speter  baton.result_pool = result_pool;
437251881Speter
438251881Speter  baton.skip_below_abspath = NULL;
439251881Speter
440251881Speter  SVN_ERR(svn_wc_walk_status(wc_ctx,
441251881Speter                             local_abspath,
442251881Speter                             depth,
443251881Speter                             (copy_mode_relpath != NULL) /* get_all */,
444251881Speter                             FALSE /* no_ignore */,
445251881Speter                             FALSE /* ignore_text_mods */,
446251881Speter                             NULL /* ignore_patterns */,
447251881Speter                             harvest_status_callback,
448251881Speter                             &baton,
449251881Speter                             cancel_func, cancel_baton,
450251881Speter                             scratch_pool));
451251881Speter
452251881Speter  return SVN_NO_ERROR;
453251881Speter}
454251881Speter
455251881Speterstatic svn_error_t *
456251881Speterharvest_not_present_for_copy(svn_wc_context_t *wc_ctx,
457251881Speter                             const char *local_abspath,
458251881Speter                             svn_client__committables_t *committables,
459251881Speter                             const char *repos_root_url,
460251881Speter                             const char *commit_relpath,
461251881Speter                             svn_client__check_url_kind_t check_url_func,
462251881Speter                             void *check_url_baton,
463251881Speter                             apr_pool_t *result_pool,
464251881Speter                             apr_pool_t *scratch_pool)
465251881Speter{
466251881Speter  const apr_array_header_t *children;
467251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
468251881Speter  int i;
469251881Speter
470299742Sdim  SVN_ERR_ASSERT(commit_relpath != NULL);
471299742Sdim
472251881Speter  /* A function to retrieve not present children would be nice to have */
473299742Sdim  SVN_ERR(svn_wc__node_get_not_present_children(&children, wc_ctx,
474299742Sdim                                                local_abspath,
475299742Sdim                                                scratch_pool, iterpool));
476251881Speter
477251881Speter  for (i = 0; i < children->nelts; i++)
478251881Speter    {
479251881Speter      const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
480251881Speter      const char *name = svn_dirent_basename(this_abspath, NULL);
481251881Speter      const char *this_commit_relpath;
482251881Speter      svn_boolean_t not_present;
483251881Speter      svn_node_kind_t kind;
484251881Speter
485251881Speter      svn_pool_clear(iterpool);
486251881Speter
487251881Speter      SVN_ERR(svn_wc__node_is_not_present(&not_present, NULL, NULL, wc_ctx,
488251881Speter                                          this_abspath, FALSE, scratch_pool));
489251881Speter
490251881Speter      if (!not_present)
491299742Sdim        continue; /* Node is replaced */
492251881Speter
493299742Sdim      this_commit_relpath = svn_relpath_join(commit_relpath, name,
494299742Sdim                                             iterpool);
495251881Speter
496251881Speter      /* We should check if we should really add a delete operation */
497251881Speter      if (check_url_func)
498251881Speter        {
499251881Speter          svn_revnum_t parent_rev;
500251881Speter          const char *parent_repos_relpath;
501251881Speter          const char *parent_repos_root_url;
502251881Speter          const char *node_url;
503251881Speter
504251881Speter          /* Determine from what parent we would be the deleted child */
505251881Speter          SVN_ERR(svn_wc__node_get_origin(
506251881Speter                              NULL, &parent_rev, &parent_repos_relpath,
507299742Sdim                              &parent_repos_root_url, NULL, NULL, NULL,
508251881Speter                              wc_ctx,
509251881Speter                              svn_dirent_dirname(this_abspath,
510251881Speter                                                  scratch_pool),
511251881Speter                              FALSE, scratch_pool, scratch_pool));
512251881Speter
513251881Speter          node_url = svn_path_url_add_component2(
514251881Speter                        svn_path_url_add_component2(parent_repos_root_url,
515251881Speter                                                    parent_repos_relpath,
516251881Speter                                                    scratch_pool),
517251881Speter                        svn_dirent_basename(this_abspath, NULL),
518251881Speter                        iterpool);
519251881Speter
520251881Speter          SVN_ERR(check_url_func(check_url_baton, &kind,
521251881Speter                                 node_url, parent_rev, iterpool));
522251881Speter
523251881Speter          if (kind == svn_node_none)
524251881Speter            continue; /* This node can't be deleted */
525251881Speter        }
526251881Speter      else
527251881Speter        SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath,
528251881Speter                                  TRUE, TRUE, scratch_pool));
529251881Speter
530251881Speter      SVN_ERR(add_committable(committables, this_abspath, kind,
531251881Speter                              repos_root_url,
532251881Speter                              this_commit_relpath,
533251881Speter                              SVN_INVALID_REVNUM,
534251881Speter                              NULL /* copyfrom_relpath */,
535251881Speter                              SVN_INVALID_REVNUM /* copyfrom_rev */,
536251881Speter                              NULL /* moved_from_abspath */,
537251881Speter                              SVN_CLIENT_COMMIT_ITEM_DELETE,
538251881Speter                              NULL, NULL,
539251881Speter                              result_pool, scratch_pool));
540251881Speter    }
541251881Speter
542251881Speter  svn_pool_destroy(iterpool);
543251881Speter  return SVN_NO_ERROR;
544251881Speter}
545251881Speter
546251881Speter/* Implements svn_wc_status_func4_t */
547251881Speterstatic svn_error_t *
548251881Speterharvest_status_callback(void *status_baton,
549251881Speter                        const char *local_abspath,
550251881Speter                        const svn_wc_status3_t *status,
551251881Speter                        apr_pool_t *scratch_pool)
552251881Speter{
553251881Speter  apr_byte_t state_flags = 0;
554251881Speter  svn_revnum_t node_rev;
555251881Speter  const char *cf_relpath = NULL;
556251881Speter  svn_revnum_t cf_rev = SVN_INVALID_REVNUM;
557251881Speter  svn_boolean_t matches_changelists;
558251881Speter  svn_boolean_t is_added;
559251881Speter  svn_boolean_t is_deleted;
560251881Speter  svn_boolean_t is_replaced;
561251881Speter  svn_boolean_t is_op_root;
562251881Speter  svn_revnum_t original_rev;
563251881Speter  const char *original_relpath;
564251881Speter  svn_boolean_t copy_mode;
565251881Speter
566251881Speter  struct harvest_baton *baton = status_baton;
567251881Speter  svn_boolean_t is_harvest_root =
568251881Speter                (strcmp(baton->root_abspath, local_abspath) == 0);
569251881Speter  svn_client__committables_t *committables = baton->committables;
570251881Speter  const char *repos_root_url = status->repos_root_url;
571251881Speter  const char *commit_relpath = NULL;
572251881Speter  svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root);
573251881Speter  svn_boolean_t just_locked = baton->just_locked;
574251881Speter  apr_hash_t *changelists = baton->changelists;
575251881Speter  svn_wc_notify_func2_t notify_func = baton->notify_func;
576251881Speter  void *notify_baton = baton->notify_baton;
577251881Speter  svn_wc_context_t *wc_ctx = baton->wc_ctx;
578251881Speter  apr_pool_t *result_pool = baton->result_pool;
579251881Speter  const char *moved_from_abspath = NULL;
580251881Speter
581251881Speter  if (baton->commit_relpath)
582251881Speter    commit_relpath = svn_relpath_join(
583251881Speter                        baton->commit_relpath,
584251881Speter                        svn_dirent_skip_ancestor(baton->root_abspath,
585251881Speter                                                 local_abspath),
586251881Speter                        scratch_pool);
587251881Speter
588251881Speter  copy_mode = (commit_relpath != NULL);
589251881Speter
590251881Speter  if (baton->skip_below_abspath
591251881Speter      && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
592251881Speter    {
593251881Speter      return SVN_NO_ERROR;
594251881Speter    }
595251881Speter  else
596251881Speter    baton->skip_below_abspath = NULL; /* We have left the skip tree */
597251881Speter
598251881Speter  /* Return early for nodes that don't have a committable status */
599251881Speter  switch (status->node_status)
600251881Speter    {
601251881Speter      case svn_wc_status_unversioned:
602251881Speter      case svn_wc_status_ignored:
603251881Speter      case svn_wc_status_external:
604251881Speter      case svn_wc_status_none:
605251881Speter        /* Unversioned nodes aren't committable, but are reported by the status
606251881Speter           walker.
607251881Speter           But if the unversioned node is the root of the walk, we have a user
608251881Speter           error */
609251881Speter        if (is_harvest_root)
610251881Speter          return svn_error_createf(
611251881Speter                       SVN_ERR_ILLEGAL_TARGET, NULL,
612251881Speter                       _("'%s' is not under version control"),
613251881Speter                       svn_dirent_local_style(local_abspath, scratch_pool));
614251881Speter        return SVN_NO_ERROR;
615251881Speter      case svn_wc_status_normal:
616251881Speter        /* Status normal nodes aren't modified, so we don't have to commit them
617251881Speter           when we perform a normal commit. But if a node is conflicted we want
618251881Speter           to stop the commit and if we are collecting lock tokens we want to
619251881Speter           look further anyway.
620251881Speter
621251881Speter           When in copy mode we need to compare the revision of the node against
622251881Speter           the parent node to copy mixed-revision base nodes properly */
623251881Speter        if (!copy_mode && !status->conflicted
624251881Speter            && !(just_locked && status->lock))
625251881Speter          return SVN_NO_ERROR;
626251881Speter        break;
627251881Speter      default:
628251881Speter        /* Fall through */
629251881Speter        break;
630251881Speter    }
631251881Speter
632251881Speter  /* Early out if the item is already marked as committable. */
633251881Speter  if (look_up_committable(committables, local_abspath, scratch_pool))
634251881Speter    return SVN_NO_ERROR;
635251881Speter
636251881Speter  SVN_ERR_ASSERT((copy_mode && commit_relpath)
637251881Speter                 || (! copy_mode && ! commit_relpath));
638251881Speter  SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root);
639251881Speter
640251881Speter  /* Save the result for reuse. */
641251881Speter  matches_changelists = ((changelists == NULL)
642251881Speter                         || (status->changelist != NULL
643251881Speter                             && svn_hash_gets(changelists, status->changelist)
644251881Speter                                != NULL));
645251881Speter
646251881Speter  /* Early exit. */
647251881Speter  if (status->kind != svn_node_dir && ! matches_changelists)
648251881Speter    {
649251881Speter      return SVN_NO_ERROR;
650251881Speter    }
651251881Speter
652251881Speter  /* If NODE is in our changelist, then examine it for conflicts. We
653251881Speter     need to bail out if any conflicts exist.
654251881Speter     The status walker checked for conflict marker removal. */
655251881Speter  if (status->conflicted && matches_changelists)
656251881Speter    {
657251881Speter      if (notify_func != NULL)
658251881Speter        {
659251881Speter          notify_func(notify_baton,
660251881Speter                      svn_wc_create_notify(local_abspath,
661251881Speter                                           svn_wc_notify_failed_conflict,
662251881Speter                                           scratch_pool),
663251881Speter                      scratch_pool);
664251881Speter        }
665251881Speter
666251881Speter      return svn_error_createf(
667251881Speter            SVN_ERR_WC_FOUND_CONFLICT, NULL,
668251881Speter            _("Aborting commit: '%s' remains in conflict"),
669251881Speter            svn_dirent_local_style(local_abspath, scratch_pool));
670251881Speter    }
671251881Speter  else if (status->node_status == svn_wc_status_obstructed)
672251881Speter    {
673251881Speter      /* A node's type has changed before attempting to commit.
674251881Speter         This also catches symlink vs non symlink changes */
675251881Speter
676251881Speter      if (notify_func != NULL)
677251881Speter        {
678251881Speter          notify_func(notify_baton,
679251881Speter                      svn_wc_create_notify(local_abspath,
680251881Speter                                           svn_wc_notify_failed_obstruction,
681251881Speter                                           scratch_pool),
682251881Speter                      scratch_pool);
683251881Speter        }
684251881Speter
685251881Speter      return svn_error_createf(
686251881Speter                    SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
687251881Speter                    _("Node '%s' has unexpectedly changed kind"),
688251881Speter                    svn_dirent_local_style(local_abspath, scratch_pool));
689251881Speter    }
690251881Speter
691251881Speter  if (status->conflicted && status->kind == svn_node_unknown)
692251881Speter    return SVN_NO_ERROR; /* Ignore delete-delete conflict */
693251881Speter
694251881Speter  /* Return error on unknown path kinds.  We check both the entry and
695251881Speter     the node itself, since a path might have changed kind since its
696251881Speter     entry was written. */
697251881Speter  SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted,
698251881Speter                                         &is_replaced,
699251881Speter                                         &is_op_root,
700251881Speter                                         &node_rev,
701251881Speter                                         &original_rev, &original_relpath,
702251881Speter                                         wc_ctx, local_abspath,
703251881Speter                                         scratch_pool, scratch_pool));
704251881Speter
705251881Speter  /* Hande file externals only when passed as explicit target. Note that
706251881Speter   * svn_client_commit6() passes all committable externals in as explicit
707251881Speter   * targets iff they count. */
708251881Speter  if (status->file_external && !is_harvest_root)
709251881Speter    {
710251881Speter      return SVN_NO_ERROR;
711251881Speter    }
712251881Speter
713251881Speter  if (status->node_status == svn_wc_status_missing && matches_changelists)
714251881Speter    {
715251881Speter      /* Added files and directories must exist. See issue #3198. */
716251881Speter      if (is_added && is_op_root)
717251881Speter        {
718251881Speter          if (notify_func != NULL)
719251881Speter            {
720251881Speter              notify_func(notify_baton,
721251881Speter                          svn_wc_create_notify(local_abspath,
722251881Speter                                               svn_wc_notify_failed_missing,
723251881Speter                                               scratch_pool),
724251881Speter                          scratch_pool);
725251881Speter            }
726251881Speter          return svn_error_createf(
727251881Speter             SVN_ERR_WC_PATH_NOT_FOUND, NULL,
728251881Speter             _("'%s' is scheduled for addition, but is missing"),
729251881Speter             svn_dirent_local_style(local_abspath, scratch_pool));
730251881Speter        }
731251881Speter
732251881Speter      return SVN_NO_ERROR;
733251881Speter    }
734251881Speter
735251881Speter  if (is_deleted && !is_op_root /* && !is_added */)
736251881Speter    return SVN_NO_ERROR; /* Not an operational delete and not an add. */
737251881Speter
738251881Speter  /* Check for the deletion case.
739251881Speter     * We delete explicitly deleted nodes (duh!)
740251881Speter     * We delete not-present children of copies
741251881Speter     * We delete nodes that directly replace a node in its ancestor
742251881Speter   */
743251881Speter
744251881Speter  if (is_deleted || is_replaced)
745251881Speter    state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
746251881Speter
747251881Speter  /* Check for adds and copies */
748251881Speter  if (is_added && is_op_root)
749251881Speter    {
750251881Speter      /* Root of local add or copy */
751251881Speter      state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
752251881Speter
753251881Speter      if (original_relpath)
754251881Speter        {
755251881Speter          /* Root of copy */
756251881Speter          state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
757251881Speter          cf_relpath = original_relpath;
758251881Speter          cf_rev = original_rev;
759251881Speter
760251881Speter          if (status->moved_from_abspath && !copy_mode)
761251881Speter            {
762251881Speter              state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE;
763251881Speter              moved_from_abspath = status->moved_from_abspath;
764251881Speter            }
765251881Speter        }
766251881Speter    }
767251881Speter
768251881Speter  /* Further copies may occur in copy mode. */
769251881Speter  else if (copy_mode
770251881Speter           && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
771251881Speter    {
772251881Speter      svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
773299742Sdim      const char *dir_repos_relpath = NULL;
774251881Speter
775299742Sdim      if (!copy_mode_root && !is_added)
776299742Sdim        SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, &dir_repos_relpath, NULL,
777299742Sdim                                      NULL, NULL,
778251881Speter                                      wc_ctx, svn_dirent_dirname(local_abspath,
779251881Speter                                                                 scratch_pool),
780251881Speter                                      FALSE /* ignore_enoent */,
781251881Speter                                      scratch_pool, scratch_pool));
782251881Speter
783251881Speter      if (copy_mode_root || status->switched || node_rev != dir_rev)
784251881Speter        {
785251881Speter          state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD
786251881Speter                          | SVN_CLIENT_COMMIT_ITEM_IS_COPY);
787251881Speter
788251881Speter          if (status->copied)
789251881Speter            {
790251881Speter              /* Copy from original location */
791251881Speter              cf_rev = original_rev;
792251881Speter              cf_relpath = original_relpath;
793251881Speter            }
794251881Speter          else
795251881Speter            {
796251881Speter              /* Copy BASE location, to represent a mixed-rev or switch copy */
797251881Speter              cf_rev = status->revision;
798251881Speter              cf_relpath = status->repos_relpath;
799251881Speter            }
800299742Sdim
801299742Sdim          if (!copy_mode_root && !is_added && baton->check_url_func
802299742Sdim              && dir_repos_relpath)
803299742Sdim            {
804299742Sdim              svn_node_kind_t me_kind;
805299742Sdim              /* Maybe we need to issue an delete (mixed rev/switched) */
806299742Sdim
807299742Sdim              SVN_ERR(baton->check_url_func(
808299742Sdim                            baton->check_url_baton, &me_kind,
809299742Sdim                            svn_path_url_add_component2(repos_root_url,
810299742Sdim                                        svn_relpath_join(dir_repos_relpath,
811299742Sdim                                            svn_dirent_basename(local_abspath,
812299742Sdim                                                                NULL),
813299742Sdim                                            scratch_pool),
814299742Sdim                                        scratch_pool),
815299742Sdim                                        dir_rev, scratch_pool));
816299742Sdim              if (me_kind != svn_node_none)
817299742Sdim                state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
818299742Sdim            }
819251881Speter        }
820251881Speter    }
821251881Speter
822251881Speter  if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
823251881Speter      || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
824251881Speter    {
825251881Speter      svn_boolean_t text_mod = FALSE;
826251881Speter      svn_boolean_t prop_mod = FALSE;
827251881Speter
828251881Speter      if (status->kind == svn_node_file)
829251881Speter        {
830251881Speter          /* Check for text modifications on files */
831251881Speter          if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
832251881Speter              && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
833251881Speter            {
834251881Speter              text_mod = TRUE; /* Local added files are always modified */
835251881Speter            }
836251881Speter          else
837251881Speter            text_mod = (status->text_status != svn_wc_status_normal);
838251881Speter        }
839251881Speter
840251881Speter      prop_mod = (status->prop_status != svn_wc_status_normal
841251881Speter                  && status->prop_status != svn_wc_status_none);
842251881Speter
843251881Speter      /* Set text/prop modification flags accordingly. */
844251881Speter      if (text_mod)
845251881Speter        state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
846251881Speter      if (prop_mod)
847251881Speter        state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
848251881Speter    }
849251881Speter
850251881Speter  /* If the entry has a lock token and it is already a commit candidate,
851251881Speter     or the caller wants unmodified locked items to be treated as
852251881Speter     such, note this fact. */
853251881Speter  if (status->lock && baton->lock_tokens && (state_flags || just_locked))
854251881Speter    {
855251881Speter      state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
856251881Speter    }
857251881Speter
858251881Speter  /* Now, if this is something to commit, add it to our list. */
859251881Speter  if (matches_changelists
860251881Speter      && state_flags)
861251881Speter    {
862251881Speter      /* Finally, add the committable item. */
863251881Speter      SVN_ERR(add_committable(committables, local_abspath,
864251881Speter                              status->kind,
865251881Speter                              repos_root_url,
866251881Speter                              copy_mode
867251881Speter                                      ? commit_relpath
868251881Speter                                      : status->repos_relpath,
869251881Speter                              copy_mode
870251881Speter                                      ? SVN_INVALID_REVNUM
871251881Speter                                      : node_rev,
872251881Speter                              cf_relpath,
873251881Speter                              cf_rev,
874251881Speter                              moved_from_abspath,
875251881Speter                              state_flags,
876251881Speter                              baton->lock_tokens, status->lock,
877251881Speter                              result_pool, scratch_pool));
878251881Speter    }
879251881Speter
880251881Speter    /* Fetch lock tokens for descendants of deleted BASE nodes. */
881251881Speter  if (matches_changelists
882251881Speter      && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
883251881Speter      && !copy_mode
884251881Speter      && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */
885251881Speter      && baton->lock_tokens)
886251881Speter    {
887251881Speter      apr_hash_t *local_relpath_tokens;
888251881Speter      apr_hash_index_t *hi;
889251881Speter
890251881Speter      SVN_ERR(svn_wc__node_get_lock_tokens_recursive(
891251881Speter                  &local_relpath_tokens, wc_ctx, local_abspath,
892251881Speter                  result_pool, scratch_pool));
893251881Speter
894251881Speter      /* Add tokens to existing hash. */
895251881Speter      for (hi = apr_hash_first(scratch_pool, local_relpath_tokens);
896251881Speter           hi;
897251881Speter           hi = apr_hash_next(hi))
898251881Speter        {
899251881Speter          const void *key;
900251881Speter          apr_ssize_t klen;
901251881Speter          void * val;
902251881Speter
903251881Speter          apr_hash_this(hi, &key, &klen, &val);
904251881Speter
905251881Speter          apr_hash_set(baton->lock_tokens, key, klen, val);
906251881Speter        }
907251881Speter    }
908251881Speter
909251881Speter  /* Make sure we check for dangling children on additions
910251881Speter
911251881Speter     We perform this operation on the harvest root, and on roots caused by
912251881Speter     changelist filtering.
913251881Speter  */
914251881Speter  if (matches_changelists
915251881Speter      && (is_harvest_root || baton->changelists)
916251881Speter      && state_flags
917269847Speter      && (is_added || (is_deleted && is_op_root && status->copied))
918251881Speter      && baton->danglers)
919251881Speter    {
920251881Speter      /* If a node is added, its parent must exist in the repository at the
921251881Speter         time of committing */
922251881Speter      apr_hash_t *danglers = baton->danglers;
923251881Speter      svn_boolean_t parent_added;
924251881Speter      const char *parent_abspath = svn_dirent_dirname(local_abspath,
925251881Speter                                                      scratch_pool);
926251881Speter
927251881Speter      /* First check if parent is already in the list of commits
928251881Speter         (Common case for GUI clients that provide a list of commit targets) */
929251881Speter      if (look_up_committable(committables, parent_abspath, scratch_pool))
930251881Speter        parent_added = FALSE; /* Skip all expensive checks */
931251881Speter      else
932251881Speter        SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath,
933251881Speter                                      scratch_pool));
934251881Speter
935251881Speter      if (parent_added)
936251881Speter        {
937251881Speter          const char *copy_root_abspath;
938251881Speter          svn_boolean_t parent_is_copy;
939251881Speter
940251881Speter          /* The parent is added, so either it is a copy, or a locally added
941251881Speter           * directory. In either case, we require the op-root of the parent
942251881Speter           * to be part of the commit. See issue #4059. */
943251881Speter          SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL,
944299742Sdim                                          NULL, NULL, &copy_root_abspath,
945251881Speter                                          wc_ctx, parent_abspath,
946251881Speter                                          FALSE, scratch_pool, scratch_pool));
947251881Speter
948251881Speter          if (parent_is_copy)
949251881Speter            parent_abspath = copy_root_abspath;
950251881Speter
951251881Speter          if (!svn_hash_gets(danglers, parent_abspath))
952251881Speter            {
953251881Speter              svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath),
954251881Speter                            apr_pstrdup(result_pool, local_abspath));
955251881Speter            }
956251881Speter        }
957251881Speter    }
958251881Speter
959251881Speter  if (is_deleted && !is_added)
960251881Speter    {
961251881Speter      /* Skip all descendants */
962251881Speter      if (status->kind == svn_node_dir)
963251881Speter        baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
964251881Speter                                                local_abspath);
965251881Speter      return SVN_NO_ERROR;
966251881Speter    }
967251881Speter
968251881Speter  /* Recursively handle each node according to depth, except when the
969251881Speter     node is only being deleted, or is in an added tree (as added trees
970251881Speter     use the normal commit handling). */
971251881Speter  if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir)
972251881Speter    {
973251881Speter      SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables,
974251881Speter                                           repos_root_url, commit_relpath,
975251881Speter                                           baton->check_url_func,
976251881Speter                                           baton->check_url_baton,
977251881Speter                                           result_pool, scratch_pool));
978251881Speter    }
979251881Speter
980251881Speter  return SVN_NO_ERROR;
981251881Speter}
982251881Speter
983251881Speter/* Baton for handle_descendants */
984251881Speterstruct handle_descendants_baton
985251881Speter{
986251881Speter  svn_wc_context_t *wc_ctx;
987251881Speter  svn_cancel_func_t cancel_func;
988251881Speter  void *cancel_baton;
989251881Speter  svn_client__check_url_kind_t check_url_func;
990251881Speter  void *check_url_baton;
991269847Speter  svn_client__committables_t *committables;
992251881Speter};
993251881Speter
994251881Speter/* Helper for the commit harvesters */
995251881Speterstatic svn_error_t *
996251881Speterhandle_descendants(void *baton,
997269847Speter                   const void *key, apr_ssize_t klen, void *val,
998269847Speter                   apr_pool_t *pool)
999251881Speter{
1000251881Speter  struct handle_descendants_baton *hdb = baton;
1001251881Speter  apr_array_header_t *commit_items = val;
1002251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
1003269847Speter  const char *repos_root_url = key;
1004251881Speter  int i;
1005251881Speter
1006251881Speter  for (i = 0; i < commit_items->nelts; i++)
1007251881Speter    {
1008251881Speter      svn_client_commit_item3_t *item =
1009251881Speter        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1010251881Speter      const apr_array_header_t *absent_descendants;
1011251881Speter      int j;
1012251881Speter
1013251881Speter      /* Is this a copy operation? */
1014251881Speter      if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1015251881Speter          || ! item->copyfrom_url)
1016251881Speter        continue;
1017251881Speter
1018251881Speter      if (hdb->cancel_func)
1019251881Speter        SVN_ERR(hdb->cancel_func(hdb->cancel_baton));
1020251881Speter
1021251881Speter      svn_pool_clear(iterpool);
1022251881Speter
1023251881Speter      SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants,
1024251881Speter                                                  hdb->wc_ctx, item->path,
1025251881Speter                                                  iterpool, iterpool));
1026251881Speter
1027251881Speter      for (j = 0; j < absent_descendants->nelts; j++)
1028251881Speter        {
1029251881Speter          svn_node_kind_t kind;
1030269847Speter          svn_client_commit_item3_t *desc_item;
1031251881Speter          const char *relpath = APR_ARRAY_IDX(absent_descendants, j,
1032251881Speter                                              const char *);
1033251881Speter          const char *local_abspath = svn_dirent_join(item->path, relpath,
1034251881Speter                                                      iterpool);
1035251881Speter
1036269847Speter          /* ### Need a sub-iterpool? */
1037269847Speter
1038269847Speter
1039269847Speter          /* We found a 'not present' descendant during a copy (at op_depth>0),
1040269847Speter             this is most commonly caused by copying some mixed revision tree.
1041269847Speter
1042269847Speter             In this case not present can imply that the node does not exist
1043269847Speter             in the parent revision, or that the node does. But we want to copy
1044269847Speter             the working copy state in which it does not exist, but might be
1045269847Speter             replaced. */
1046269847Speter
1047269847Speter          desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath);
1048269847Speter
1049269847Speter          /* If the path has a commit operation (possibly at an higher
1050269847Speter             op_depth, we might want to turn an add in a replace. */
1051269847Speter          if (desc_item)
1052251881Speter            {
1053269847Speter              const char *dir;
1054269847Speter              svn_boolean_t found_intermediate = FALSE;
1055251881Speter
1056269847Speter              if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1057269847Speter                continue; /* We already have a delete or replace */
1058269847Speter              else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1059269847Speter                continue; /* Not a copy/add, just a modification */
1060269847Speter
1061269847Speter              dir = svn_dirent_dirname(local_abspath, iterpool);
1062269847Speter
1063269847Speter              while (strcmp(dir, item->path))
1064251881Speter                {
1065269847Speter                  svn_client_commit_item3_t *i_item;
1066269847Speter
1067269847Speter                  i_item = svn_hash_gets(hdb->committables->by_path, dir);
1068269847Speter
1069269847Speter                  if (i_item)
1070269847Speter                    {
1071269847Speter                      if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1072269847Speter                          || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1073269847Speter                        {
1074269847Speter                          found_intermediate = TRUE;
1075269847Speter                          break;
1076269847Speter                        }
1077269847Speter                    }
1078269847Speter                  dir = svn_dirent_dirname(dir, iterpool);
1079251881Speter                }
1080251881Speter
1081269847Speter              if (found_intermediate)
1082269847Speter                continue; /* Some intermediate ancestor is an add or delete */
1083251881Speter
1084269847Speter              /* Fall through to detect if we need to turn the add in a
1085269847Speter                 replace. */
1086269847Speter            }
1087251881Speter
1088251881Speter          if (hdb->check_url_func)
1089251881Speter            {
1090251881Speter              const char *from_url = svn_path_url_add_component2(
1091251881Speter                                                item->copyfrom_url, relpath,
1092251881Speter                                                iterpool);
1093251881Speter
1094251881Speter              SVN_ERR(hdb->check_url_func(hdb->check_url_baton,
1095251881Speter                                          &kind, from_url, item->copyfrom_rev,
1096251881Speter                                          iterpool));
1097251881Speter
1098251881Speter              if (kind == svn_node_none)
1099251881Speter                continue; /* This node is already deleted */
1100251881Speter            }
1101251881Speter          else
1102251881Speter            kind = svn_node_unknown; /* 'Ok' for a delete of something */
1103251881Speter
1104269847Speter          if (desc_item)
1105269847Speter            {
1106269847Speter              /* Extend the existing add/copy item to create a replace */
1107269847Speter              desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
1108269847Speter              continue;
1109269847Speter            }
1110251881Speter
1111269847Speter          /* Add a new commit item that describes the delete */
1112251881Speter
1113269847Speter          SVN_ERR(add_committable(hdb->committables,
1114269847Speter                                  svn_dirent_join(item->path, relpath,
1115269847Speter                                                  iterpool),
1116269847Speter                                  kind,
1117269847Speter                                  repos_root_url,
1118269847Speter                                  svn_uri_skip_ancestor(
1119269847Speter                                        repos_root_url,
1120269847Speter                                        svn_path_url_add_component2(item->url,
1121269847Speter                                                                    relpath,
1122269847Speter                                                                    iterpool),
1123269847Speter                                        iterpool),
1124269847Speter                                  SVN_INVALID_REVNUM,
1125269847Speter                                  NULL /* copyfrom_relpath */,
1126269847Speter                                  SVN_INVALID_REVNUM,
1127269847Speter                                  NULL /* moved_from_abspath */,
1128269847Speter                                  SVN_CLIENT_COMMIT_ITEM_DELETE,
1129269847Speter                                  NULL /* lock tokens */,
1130269847Speter                                  NULL /* lock */,
1131269847Speter                                  commit_items->pool,
1132269847Speter                                  iterpool));
1133251881Speter        }
1134251881Speter      }
1135251881Speter
1136251881Speter  svn_pool_destroy(iterpool);
1137251881Speter  return SVN_NO_ERROR;
1138251881Speter}
1139251881Speter
1140251881Speter/* Allocate and initialize the COMMITTABLES structure from POOL.
1141251881Speter */
1142251881Speterstatic void
1143251881Spetercreate_committables(svn_client__committables_t **committables,
1144251881Speter                    apr_pool_t *pool)
1145251881Speter{
1146251881Speter  *committables = apr_palloc(pool, sizeof(**committables));
1147251881Speter
1148251881Speter  (*committables)->by_repository = apr_hash_make(pool);
1149251881Speter  (*committables)->by_path = apr_hash_make(pool);
1150251881Speter}
1151251881Speter
1152251881Spetersvn_error_t *
1153251881Spetersvn_client__harvest_committables(svn_client__committables_t **committables,
1154251881Speter                                 apr_hash_t **lock_tokens,
1155251881Speter                                 const char *base_dir_abspath,
1156251881Speter                                 const apr_array_header_t *targets,
1157251881Speter                                 int depth_empty_start,
1158251881Speter                                 svn_depth_t depth,
1159251881Speter                                 svn_boolean_t just_locked,
1160251881Speter                                 const apr_array_header_t *changelists,
1161251881Speter                                 svn_client__check_url_kind_t check_url_func,
1162251881Speter                                 void *check_url_baton,
1163251881Speter                                 svn_client_ctx_t *ctx,
1164251881Speter                                 apr_pool_t *result_pool,
1165251881Speter                                 apr_pool_t *scratch_pool)
1166251881Speter{
1167251881Speter  int i;
1168251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1169251881Speter  apr_hash_t *changelist_hash = NULL;
1170251881Speter  struct handle_descendants_baton hdb;
1171251881Speter  apr_hash_index_t *hi;
1172251881Speter
1173251881Speter  /* It's possible that one of the named targets has a parent that is
1174251881Speter   * itself scheduled for addition or replacement -- that is, the
1175251881Speter   * parent is not yet versioned in the repository.  This is okay, as
1176251881Speter   * long as the parent itself is part of this same commit, either
1177251881Speter   * directly, or by virtue of a grandparent, great-grandparent, etc,
1178251881Speter   * being part of the commit.
1179251881Speter   *
1180251881Speter   * Since we don't know what's included in the commit until we've
1181251881Speter   * harvested all the targets, we can't reliably check this as we
1182251881Speter   * go.  So in `danglers', we record named targets whose parents
1183251881Speter   * do not yet exist in the repository. Then after harvesting the total
1184251881Speter   * commit group, we check to make sure those parents are included.
1185251881Speter   *
1186251881Speter   * Each key of danglers is a parent which does not exist in the
1187251881Speter   * repository.  The (const char *) value is one of that parent's
1188251881Speter   * children which is named as part of the commit; the child is
1189251881Speter   * included only to make a better error message.
1190251881Speter   *
1191251881Speter   * (The reason we don't bother to check unnamed -- i.e, implicit --
1192251881Speter   * targets is that they can only join the commit if their parents
1193251881Speter   * did too, so this situation can't arise for them.)
1194251881Speter   */
1195251881Speter  apr_hash_t *danglers = apr_hash_make(scratch_pool);
1196251881Speter
1197251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath));
1198251881Speter
1199251881Speter  /* Create the COMMITTABLES structure. */
1200251881Speter  create_committables(committables, result_pool);
1201251881Speter
1202251881Speter  /* And the LOCK_TOKENS dito. */
1203251881Speter  *lock_tokens = apr_hash_make(result_pool);
1204251881Speter
1205251881Speter  /* If we have a list of changelists, convert that into a hash with
1206251881Speter     changelist keys. */
1207251881Speter  if (changelists && changelists->nelts)
1208251881Speter    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists,
1209251881Speter                                       scratch_pool));
1210251881Speter
1211251881Speter  for (i = 0; i < targets->nelts; ++i)
1212251881Speter    {
1213251881Speter      const char *target_abspath;
1214251881Speter
1215251881Speter      svn_pool_clear(iterpool);
1216251881Speter
1217251881Speter      /* Add the relative portion to the base abspath.  */
1218251881Speter      target_abspath = svn_dirent_join(base_dir_abspath,
1219251881Speter                                       APR_ARRAY_IDX(targets, i, const char *),
1220251881Speter                                       iterpool);
1221251881Speter
1222251881Speter      /* Handle our TARGET. */
1223251881Speter      /* Make sure this isn't inside a working copy subtree that is
1224251881Speter       * marked as tree-conflicted. */
1225251881Speter      SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath,
1226251881Speter                                               ctx->notify_func2,
1227251881Speter                                               ctx->notify_baton2,
1228251881Speter                                               iterpool));
1229251881Speter
1230251881Speter      /* Are the remaining items externals with depth empty? */
1231251881Speter      if (i == depth_empty_start)
1232251881Speter        depth = svn_depth_empty;
1233251881Speter
1234251881Speter      SVN_ERR(harvest_committables(target_abspath,
1235251881Speter                                   *committables, *lock_tokens,
1236251881Speter                                   NULL /* COPY_MODE_RELPATH */,
1237251881Speter                                   depth, just_locked, changelist_hash,
1238251881Speter                                   danglers,
1239251881Speter                                   check_url_func, check_url_baton,
1240251881Speter                                   ctx->cancel_func, ctx->cancel_baton,
1241251881Speter                                   ctx->notify_func2, ctx->notify_baton2,
1242251881Speter                                   ctx->wc_ctx, result_pool, iterpool));
1243251881Speter    }
1244251881Speter
1245251881Speter  hdb.wc_ctx = ctx->wc_ctx;
1246251881Speter  hdb.cancel_func = ctx->cancel_func;
1247251881Speter  hdb.cancel_baton = ctx->cancel_baton;
1248251881Speter  hdb.check_url_func = check_url_func;
1249251881Speter  hdb.check_url_baton = check_url_baton;
1250269847Speter  hdb.committables = *committables;
1251251881Speter
1252251881Speter  SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository,
1253251881Speter                            handle_descendants, &hdb, iterpool));
1254251881Speter
1255251881Speter  /* Make sure that every path in danglers is part of the commit. */
1256251881Speter  for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi))
1257251881Speter    {
1258299742Sdim      const char *dangling_parent = apr_hash_this_key(hi);
1259251881Speter
1260251881Speter      svn_pool_clear(iterpool);
1261251881Speter
1262251881Speter      if (! look_up_committable(*committables, dangling_parent, iterpool))
1263251881Speter        {
1264299742Sdim          const char *dangling_child = apr_hash_this_val(hi);
1265251881Speter
1266251881Speter          if (ctx->notify_func2 != NULL)
1267251881Speter            {
1268251881Speter              svn_wc_notify_t *notify;
1269251881Speter
1270251881Speter              notify = svn_wc_create_notify(dangling_child,
1271251881Speter                                            svn_wc_notify_failed_no_parent,
1272251881Speter                                            scratch_pool);
1273251881Speter
1274251881Speter              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1275251881Speter            }
1276251881Speter
1277251881Speter          return svn_error_createf(
1278251881Speter                           SVN_ERR_ILLEGAL_TARGET, NULL,
1279251881Speter                           _("'%s' is not known to exist in the repository "
1280251881Speter                             "and is not part of the commit, "
1281251881Speter                             "yet its child '%s' is part of the commit"),
1282251881Speter                           /* Probably one or both of these is an entry, but
1283251881Speter                              safest to local_stylize just in case. */
1284251881Speter                           svn_dirent_local_style(dangling_parent, iterpool),
1285251881Speter                           svn_dirent_local_style(dangling_child, iterpool));
1286251881Speter        }
1287251881Speter    }
1288251881Speter
1289251881Speter  svn_pool_destroy(iterpool);
1290251881Speter
1291251881Speter  return SVN_NO_ERROR;
1292251881Speter}
1293251881Speter
1294251881Speterstruct copy_committables_baton
1295251881Speter{
1296251881Speter  svn_client_ctx_t *ctx;
1297251881Speter  svn_client__committables_t *committables;
1298251881Speter  apr_pool_t *result_pool;
1299251881Speter  svn_client__check_url_kind_t check_url_func;
1300251881Speter  void *check_url_baton;
1301251881Speter};
1302251881Speter
1303251881Speterstatic svn_error_t *
1304251881Speterharvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
1305251881Speter{
1306251881Speter  struct copy_committables_baton *btn = baton;
1307251881Speter  svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item;
1308251881Speter  const char *repos_root_url;
1309251881Speter  const char *commit_relpath;
1310251881Speter  struct handle_descendants_baton hdb;
1311251881Speter
1312251881Speter  /* Read the entry for this SRC. */
1313251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
1314251881Speter
1315251881Speter  SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL,
1316251881Speter                                      btn->ctx->wc_ctx,
1317251881Speter                                      pair->src_abspath_or_url,
1318251881Speter                                      pool, pool));
1319251881Speter
1320251881Speter  commit_relpath = svn_uri_skip_ancestor(repos_root_url,
1321251881Speter                                         pair->dst_abspath_or_url, pool);
1322251881Speter
1323251881Speter  /* Handle this SRC. */
1324251881Speter  SVN_ERR(harvest_committables(pair->src_abspath_or_url,
1325251881Speter                               btn->committables, NULL,
1326251881Speter                               commit_relpath,
1327251881Speter                               svn_depth_infinity,
1328251881Speter                               FALSE,  /* JUST_LOCKED */
1329251881Speter                               NULL /* changelists */,
1330251881Speter                               NULL,
1331251881Speter                               btn->check_url_func,
1332251881Speter                               btn->check_url_baton,
1333251881Speter                               btn->ctx->cancel_func,
1334251881Speter                               btn->ctx->cancel_baton,
1335251881Speter                               btn->ctx->notify_func2,
1336251881Speter                               btn->ctx->notify_baton2,
1337251881Speter                               btn->ctx->wc_ctx, btn->result_pool, pool));
1338251881Speter
1339251881Speter  hdb.wc_ctx = btn->ctx->wc_ctx;
1340251881Speter  hdb.cancel_func = btn->ctx->cancel_func;
1341251881Speter  hdb.cancel_baton = btn->ctx->cancel_baton;
1342251881Speter  hdb.check_url_func = btn->check_url_func;
1343251881Speter  hdb.check_url_baton = btn->check_url_baton;
1344269847Speter  hdb.committables = btn->committables;
1345251881Speter
1346251881Speter  SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository,
1347251881Speter                            handle_descendants, &hdb, pool));
1348251881Speter
1349251881Speter  return SVN_NO_ERROR;
1350251881Speter}
1351251881Speter
1352251881Speter
1353251881Speter
1354251881Spetersvn_error_t *
1355251881Spetersvn_client__get_copy_committables(svn_client__committables_t **committables,
1356251881Speter                                  const apr_array_header_t *copy_pairs,
1357251881Speter                                  svn_client__check_url_kind_t check_url_func,
1358251881Speter                                  void *check_url_baton,
1359251881Speter                                  svn_client_ctx_t *ctx,
1360251881Speter                                  apr_pool_t *result_pool,
1361251881Speter                                  apr_pool_t *scratch_pool)
1362251881Speter{
1363251881Speter  struct copy_committables_baton btn;
1364251881Speter
1365251881Speter  /* Create the COMMITTABLES structure. */
1366251881Speter  create_committables(committables, result_pool);
1367251881Speter
1368251881Speter  btn.ctx = ctx;
1369251881Speter  btn.committables = *committables;
1370251881Speter  btn.result_pool = result_pool;
1371251881Speter
1372251881Speter  btn.check_url_func = check_url_func;
1373251881Speter  btn.check_url_baton = check_url_baton;
1374251881Speter
1375251881Speter  /* For each copy pair, harvest the committables for that pair into the
1376251881Speter     committables hash. */
1377251881Speter  return svn_iter_apr_array(NULL, copy_pairs,
1378251881Speter                            harvest_copy_committables, &btn, scratch_pool);
1379251881Speter}
1380251881Speter
1381251881Speter
1382299742Sdim/* A svn_sort__array()/qsort()-compatible sort routine for sorting
1383299742Sdim   an array of svn_client_commit_item_t *'s by their URL member. */
1384299742Sdimstatic int
1385299742Sdimsort_commit_item_urls(const void *a, const void *b)
1386251881Speter{
1387251881Speter  const svn_client_commit_item3_t *item1
1388251881Speter    = *((const svn_client_commit_item3_t * const *) a);
1389251881Speter  const svn_client_commit_item3_t *item2
1390251881Speter    = *((const svn_client_commit_item3_t * const *) b);
1391251881Speter  return svn_path_compare_paths(item1->url, item2->url);
1392251881Speter}
1393251881Speter
1394251881Speter
1395251881Speter
1396251881Spetersvn_error_t *
1397251881Spetersvn_client__condense_commit_items(const char **base_url,
1398251881Speter                                  apr_array_header_t *commit_items,
1399251881Speter                                  apr_pool_t *pool)
1400251881Speter{
1401251881Speter  apr_array_header_t *ci = commit_items; /* convenience */
1402251881Speter  const char *url;
1403251881Speter  svn_client_commit_item3_t *item, *last_item = NULL;
1404251881Speter  int i;
1405251881Speter
1406251881Speter  SVN_ERR_ASSERT(ci && ci->nelts);
1407251881Speter
1408251881Speter  /* Sort our commit items by their URLs. */
1409299742Sdim  svn_sort__array(ci, sort_commit_item_urls);
1410251881Speter
1411251881Speter  /* Loop through the URLs, finding the longest usable ancestor common
1412251881Speter     to all of them, and making sure there are no duplicate URLs.  */
1413251881Speter  for (i = 0; i < ci->nelts; i++)
1414251881Speter    {
1415251881Speter      item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1416251881Speter      url = item->url;
1417251881Speter
1418251881Speter      if ((last_item) && (strcmp(last_item->url, url) == 0))
1419251881Speter        return svn_error_createf
1420251881Speter          (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1421251881Speter           _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1422251881Speter           svn_dirent_local_style(item->path, pool),
1423251881Speter           svn_dirent_local_style(last_item->path, pool));
1424251881Speter
1425251881Speter      /* In the first iteration, our BASE_URL is just our only
1426251881Speter         encountered commit URL to date.  After that, we find the
1427251881Speter         longest ancestor between the current BASE_URL and the current
1428251881Speter         commit URL.  */
1429251881Speter      if (i == 0)
1430251881Speter        *base_url = apr_pstrdup(pool, url);
1431251881Speter      else
1432251881Speter        *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1433251881Speter
1434251881Speter      /* If our BASE_URL is itself a to-be-committed item, and it is
1435251881Speter         anything other than an already-versioned directory with
1436251881Speter         property mods, we'll call its parent directory URL the
1437251881Speter         BASE_URL.  Why?  Because we can't have a file URL as our base
1438251881Speter         -- period -- and all other directory operations (removal,
1439251881Speter         addition, etc.) require that we open that directory's parent
1440251881Speter         dir first.  */
1441251881Speter      /* ### I don't understand the strlen()s here, hmmm.  -kff */
1442251881Speter      if ((strlen(*base_url) == strlen(url))
1443251881Speter          && (! ((item->kind == svn_node_dir)
1444251881Speter                 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1445251881Speter        *base_url = svn_uri_dirname(*base_url, pool);
1446251881Speter
1447251881Speter      /* Stash our item here for the next iteration. */
1448251881Speter      last_item = item;
1449251881Speter    }
1450251881Speter
1451251881Speter  /* Now that we've settled on a *BASE_URL, go hack that base off
1452251881Speter     of all of our URLs and store it as session_relpath. */
1453251881Speter  for (i = 0; i < ci->nelts; i++)
1454251881Speter    {
1455251881Speter      svn_client_commit_item3_t *this_item
1456251881Speter        = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1457251881Speter
1458251881Speter      this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1459251881Speter                                                         this_item->url, pool);
1460251881Speter    }
1461251881Speter#ifdef SVN_CLIENT_COMMIT_DEBUG
1462251881Speter  /* ### TEMPORARY CODE ### */
1463251881Speter  SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1464251881Speter  SVN_DBG(("   FLAGS     REV  REL-URL (COPY-URL)\n"));
1465251881Speter  for (i = 0; i < ci->nelts; i++)
1466251881Speter    {
1467251881Speter      svn_client_commit_item3_t *this_item
1468251881Speter        = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1469251881Speter      char flags[6];
1470251881Speter      flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1471251881Speter                   ? 'a' : '-';
1472251881Speter      flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1473251881Speter                   ? 'd' : '-';
1474251881Speter      flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1475251881Speter                   ? 't' : '-';
1476251881Speter      flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1477251881Speter                   ? 'p' : '-';
1478251881Speter      flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1479251881Speter                   ? 'c' : '-';
1480251881Speter      flags[5] = '\0';
1481251881Speter      SVN_DBG(("   %s  %6ld  '%s' (%s)\n",
1482251881Speter               flags,
1483251881Speter               this_item->revision,
1484251881Speter               this_item->url ? this_item->url : "",
1485251881Speter               this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1486251881Speter    }
1487251881Speter#endif /* SVN_CLIENT_COMMIT_DEBUG */
1488251881Speter
1489251881Speter  return SVN_NO_ERROR;
1490251881Speter}
1491251881Speter
1492251881Speter
1493251881Speterstruct file_mod_t
1494251881Speter{
1495251881Speter  const svn_client_commit_item3_t *item;
1496251881Speter  void *file_baton;
1497299742Sdim  apr_pool_t *file_pool;
1498251881Speter};
1499251881Speter
1500251881Speter
1501251881Speter/* A baton for use while driving a path-based editor driver for commit */
1502251881Speterstruct item_commit_baton
1503251881Speter{
1504251881Speter  const svn_delta_editor_t *editor;    /* commit editor */
1505251881Speter  void *edit_baton;                    /* commit editor's baton */
1506251881Speter  apr_hash_t *file_mods;               /* hash: path->file_mod_t */
1507251881Speter  const char *notify_path_prefix;      /* notification path prefix
1508251881Speter                                          (NULL is okay, else abs path) */
1509251881Speter  svn_client_ctx_t *ctx;               /* client context baton */
1510251881Speter  apr_hash_t *commit_items;            /* the committables */
1511251881Speter  const char *base_url;                /* The session url for the commit */
1512251881Speter};
1513251881Speter
1514251881Speter
1515251881Speter/* Drive CALLBACK_BATON->editor with the change described by the item in
1516251881Speter * CALLBACK_BATON->commit_items that is keyed by PATH.  If the change
1517251881Speter * includes a text mod, however, call the editor's file_open() function
1518251881Speter * but do not send the text mod to the editor; instead, add a mapping of
1519251881Speter * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1520251881Speter *
1521251881Speter * Before driving the editor, call the cancellation and notification
1522251881Speter * callbacks in CALLBACK_BATON->ctx, if present.
1523251881Speter *
1524251881Speter * This implements svn_delta_path_driver_cb_func_t. */
1525251881Speterstatic svn_error_t *
1526251881Speterdo_item_commit(void **dir_baton,
1527251881Speter               void *parent_baton,
1528251881Speter               void *callback_baton,
1529251881Speter               const char *path,
1530251881Speter               apr_pool_t *pool)
1531251881Speter{
1532251881Speter  struct item_commit_baton *icb = callback_baton;
1533251881Speter  const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1534251881Speter                                                        path);
1535251881Speter  svn_node_kind_t kind = item->kind;
1536251881Speter  void *file_baton = NULL;
1537251881Speter  apr_pool_t *file_pool = NULL;
1538251881Speter  const svn_delta_editor_t *editor = icb->editor;
1539251881Speter  apr_hash_t *file_mods = icb->file_mods;
1540251881Speter  svn_client_ctx_t *ctx = icb->ctx;
1541251881Speter  svn_error_t *err;
1542251881Speter  const char *local_abspath = NULL;
1543251881Speter
1544251881Speter  /* Do some initializations. */
1545251881Speter  *dir_baton = NULL;
1546251881Speter  if (item->kind != svn_node_none && item->path)
1547251881Speter    {
1548251881Speter      /* We always get an absolute path, see svn_client_commit_item3_t. */
1549251881Speter      SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1550251881Speter      local_abspath = item->path;
1551251881Speter    }
1552251881Speter
1553251881Speter  /* If this is a file with textual mods, we'll be keeping its baton
1554251881Speter     around until the end of the commit.  So just lump its memory into
1555251881Speter     a single, big, all-the-file-batons-in-here pool.  Otherwise, we
1556251881Speter     can just use POOL, and trust our caller to clean that mess up. */
1557251881Speter  if ((kind == svn_node_file)
1558251881Speter      && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1559251881Speter    file_pool = apr_hash_pool_get(file_mods);
1560251881Speter  else
1561251881Speter    file_pool = pool;
1562251881Speter
1563299742Sdim  /* Subpools are cheap, but memory isn't */
1564299742Sdim  file_pool = svn_pool_create(file_pool);
1565299742Sdim
1566251881Speter  /* Call the cancellation function. */
1567251881Speter  if (ctx->cancel_func)
1568251881Speter    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1569251881Speter
1570251881Speter  /* Validation. */
1571251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1572251881Speter    {
1573251881Speter      if (! item->copyfrom_url)
1574251881Speter        return svn_error_createf
1575251881Speter          (SVN_ERR_BAD_URL, NULL,
1576251881Speter           _("Commit item '%s' has copy flag but no copyfrom URL"),
1577251881Speter           svn_dirent_local_style(path, pool));
1578251881Speter      if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1579251881Speter        return svn_error_createf
1580251881Speter          (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1581251881Speter           _("Commit item '%s' has copy flag but an invalid revision"),
1582251881Speter           svn_dirent_local_style(path, pool));
1583251881Speter    }
1584251881Speter
1585251881Speter  /* If a feedback table was supplied by the application layer,
1586251881Speter     describe what we're about to do to this item. */
1587251881Speter  if (ctx->notify_func2 && item->path)
1588251881Speter    {
1589251881Speter      const char *npath = item->path;
1590251881Speter      svn_wc_notify_t *notify;
1591251881Speter
1592251881Speter      if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1593251881Speter          && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1594251881Speter        {
1595251881Speter          /* We don't print the "(bin)" notice for binary files when
1596251881Speter             replacing, only when adding.  So we don't bother to get
1597251881Speter             the mime-type here. */
1598251881Speter          if (item->copyfrom_url)
1599251881Speter            notify = svn_wc_create_notify(npath,
1600251881Speter                                          svn_wc_notify_commit_copied_replaced,
1601251881Speter                                          pool);
1602251881Speter          else
1603251881Speter            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1604251881Speter                                          pool);
1605251881Speter
1606251881Speter        }
1607251881Speter      else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1608251881Speter        {
1609251881Speter          notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1610251881Speter                                        pool);
1611251881Speter        }
1612251881Speter      else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1613251881Speter        {
1614251881Speter          if (item->copyfrom_url)
1615251881Speter            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1616251881Speter                                          pool);
1617251881Speter          else
1618251881Speter            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1619251881Speter                                          pool);
1620251881Speter
1621251881Speter          if (item->kind == svn_node_file)
1622251881Speter            {
1623251881Speter              const svn_string_t *propval;
1624251881Speter
1625251881Speter              SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1626251881Speter                                       SVN_PROP_MIME_TYPE, pool, pool));
1627251881Speter
1628251881Speter              if (propval)
1629251881Speter                notify->mime_type = propval->data;
1630251881Speter            }
1631251881Speter        }
1632251881Speter      else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1633251881Speter               || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1634251881Speter        {
1635251881Speter          notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1636251881Speter                                        pool);
1637251881Speter          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1638251881Speter            notify->content_state = svn_wc_notify_state_changed;
1639251881Speter          else
1640251881Speter            notify->content_state = svn_wc_notify_state_unchanged;
1641251881Speter          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1642251881Speter            notify->prop_state = svn_wc_notify_state_changed;
1643251881Speter          else
1644251881Speter            notify->prop_state = svn_wc_notify_state_unchanged;
1645251881Speter        }
1646251881Speter      else
1647251881Speter        notify = NULL;
1648251881Speter
1649299742Sdim
1650251881Speter      if (notify)
1651251881Speter        {
1652251881Speter          notify->kind = item->kind;
1653251881Speter          notify->path_prefix = icb->notify_path_prefix;
1654299742Sdim          ctx->notify_func2(ctx->notify_baton2, notify, pool);
1655251881Speter        }
1656251881Speter    }
1657251881Speter
1658251881Speter  /* If this item is supposed to be deleted, do so. */
1659251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1660251881Speter    {
1661251881Speter      SVN_ERR_ASSERT(parent_baton);
1662251881Speter      err = editor->delete_entry(path, item->revision,
1663251881Speter                                 parent_baton, pool);
1664251881Speter
1665251881Speter      if (err)
1666251881Speter        goto fixup_error;
1667251881Speter    }
1668251881Speter
1669251881Speter  /* If this item is supposed to be added, do so. */
1670251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1671251881Speter    {
1672251881Speter      if (kind == svn_node_file)
1673251881Speter        {
1674251881Speter          SVN_ERR_ASSERT(parent_baton);
1675251881Speter          err = editor->add_file(
1676251881Speter                   path, parent_baton, item->copyfrom_url,
1677251881Speter                   item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1678251881Speter                   file_pool, &file_baton);
1679251881Speter        }
1680251881Speter      else /* May be svn_node_none when adding parent dirs for a copy. */
1681251881Speter        {
1682251881Speter          SVN_ERR_ASSERT(parent_baton);
1683251881Speter          err = editor->add_directory(
1684251881Speter                   path, parent_baton, item->copyfrom_url,
1685251881Speter                   item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1686251881Speter                   pool, dir_baton);
1687251881Speter        }
1688251881Speter
1689251881Speter      if (err)
1690251881Speter        goto fixup_error;
1691251881Speter
1692251881Speter      /* Set other prop-changes, if available in the baton */
1693251881Speter      if (item->outgoing_prop_changes)
1694251881Speter        {
1695251881Speter          svn_prop_t *prop;
1696251881Speter          apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1697251881Speter          int ctr;
1698251881Speter          for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1699251881Speter            {
1700251881Speter              prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1701251881Speter              if (kind == svn_node_file)
1702251881Speter                {
1703251881Speter                  err = editor->change_file_prop(file_baton, prop->name,
1704251881Speter                                                 prop->value, pool);
1705251881Speter                }
1706251881Speter              else
1707251881Speter                {
1708251881Speter                  err = editor->change_dir_prop(*dir_baton, prop->name,
1709251881Speter                                                prop->value, pool);
1710251881Speter                }
1711251881Speter
1712251881Speter              if (err)
1713251881Speter                goto fixup_error;
1714251881Speter            }
1715251881Speter        }
1716251881Speter    }
1717251881Speter
1718251881Speter  /* Now handle property mods. */
1719251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1720251881Speter    {
1721251881Speter      if (kind == svn_node_file)
1722251881Speter        {
1723251881Speter          if (! file_baton)
1724251881Speter            {
1725251881Speter              SVN_ERR_ASSERT(parent_baton);
1726251881Speter              err = editor->open_file(path, parent_baton,
1727251881Speter                                      item->revision,
1728251881Speter                                      file_pool, &file_baton);
1729251881Speter
1730251881Speter              if (err)
1731251881Speter                goto fixup_error;
1732251881Speter            }
1733251881Speter        }
1734251881Speter      else
1735251881Speter        {
1736251881Speter          if (! *dir_baton)
1737251881Speter            {
1738251881Speter              if (! parent_baton)
1739251881Speter                {
1740251881Speter                  err = editor->open_root(icb->edit_baton, item->revision,
1741251881Speter                                          pool, dir_baton);
1742251881Speter                }
1743251881Speter              else
1744251881Speter                {
1745251881Speter                  err = editor->open_directory(path, parent_baton,
1746251881Speter                                               item->revision,
1747251881Speter                                               pool, dir_baton);
1748251881Speter                }
1749251881Speter
1750251881Speter              if (err)
1751251881Speter                goto fixup_error;
1752251881Speter            }
1753251881Speter        }
1754251881Speter
1755251881Speter      /* When committing a directory that no longer exists in the
1756251881Speter         repository, a "not found" error does not occur immediately
1757251881Speter         upon opening the directory.  It appears here during the delta
1758251881Speter         transmisssion. */
1759251881Speter      err = svn_wc_transmit_prop_deltas2(
1760251881Speter              ctx->wc_ctx, local_abspath, editor,
1761251881Speter              (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1762251881Speter
1763251881Speter      if (err)
1764251881Speter        goto fixup_error;
1765251881Speter
1766251881Speter      /* Make any additional client -> repository prop changes. */
1767251881Speter      if (item->outgoing_prop_changes)
1768251881Speter        {
1769251881Speter          svn_prop_t *prop;
1770251881Speter          int i;
1771251881Speter
1772251881Speter          for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1773251881Speter            {
1774251881Speter              prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1775251881Speter                                   svn_prop_t *);
1776251881Speter              if (kind == svn_node_file)
1777251881Speter                {
1778251881Speter                  err = editor->change_file_prop(file_baton, prop->name,
1779251881Speter                                           prop->value, pool);
1780251881Speter                }
1781251881Speter              else
1782251881Speter                {
1783251881Speter                  err = editor->change_dir_prop(*dir_baton, prop->name,
1784251881Speter                                          prop->value, pool);
1785251881Speter                }
1786251881Speter
1787251881Speter              if (err)
1788251881Speter                goto fixup_error;
1789251881Speter            }
1790251881Speter        }
1791251881Speter    }
1792251881Speter
1793251881Speter  /* Finally, handle text mods (in that we need to open a file if it
1794251881Speter     hasn't already been opened, and we need to put the file baton in
1795251881Speter     our FILES hash). */
1796251881Speter  if ((kind == svn_node_file)
1797251881Speter      && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1798251881Speter    {
1799251881Speter      struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1800251881Speter
1801251881Speter      if (! file_baton)
1802251881Speter        {
1803251881Speter          SVN_ERR_ASSERT(parent_baton);
1804251881Speter          err = editor->open_file(path, parent_baton,
1805251881Speter                                    item->revision,
1806251881Speter                                    file_pool, &file_baton);
1807251881Speter
1808251881Speter          if (err)
1809251881Speter            goto fixup_error;
1810251881Speter        }
1811251881Speter
1812251881Speter      /* Add this file mod to the FILE_MODS hash. */
1813251881Speter      mod->item = item;
1814251881Speter      mod->file_baton = file_baton;
1815299742Sdim      mod->file_pool = file_pool;
1816251881Speter      svn_hash_sets(file_mods, item->session_relpath, mod);
1817251881Speter    }
1818251881Speter  else if (file_baton)
1819251881Speter    {
1820251881Speter      /* Close any outstanding file batons that didn't get caught by
1821251881Speter         the "has local mods" conditional above. */
1822251881Speter      err = editor->close_file(file_baton, NULL, file_pool);
1823299742Sdim      svn_pool_destroy(file_pool);
1824251881Speter      if (err)
1825251881Speter        goto fixup_error;
1826251881Speter    }
1827251881Speter
1828251881Speter  return SVN_NO_ERROR;
1829251881Speter
1830251881Speterfixup_error:
1831251881Speter  return svn_error_trace(fixup_commit_error(local_abspath,
1832251881Speter                                            icb->base_url,
1833251881Speter                                            path, kind,
1834251881Speter                                            err, ctx, pool));
1835251881Speter}
1836251881Speter
1837251881Spetersvn_error_t *
1838251881Spetersvn_client__do_commit(const char *base_url,
1839251881Speter                      const apr_array_header_t *commit_items,
1840251881Speter                      const svn_delta_editor_t *editor,
1841251881Speter                      void *edit_baton,
1842251881Speter                      const char *notify_path_prefix,
1843251881Speter                      apr_hash_t **sha1_checksums,
1844251881Speter                      svn_client_ctx_t *ctx,
1845251881Speter                      apr_pool_t *result_pool,
1846251881Speter                      apr_pool_t *scratch_pool)
1847251881Speter{
1848251881Speter  apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1849251881Speter  apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1850251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1851251881Speter  apr_hash_index_t *hi;
1852251881Speter  int i;
1853251881Speter  struct item_commit_baton cb_baton;
1854251881Speter  apr_array_header_t *paths =
1855251881Speter    apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1856251881Speter
1857251881Speter  /* Ditto for the checksums. */
1858251881Speter  if (sha1_checksums)
1859251881Speter    *sha1_checksums = apr_hash_make(result_pool);
1860251881Speter
1861251881Speter  /* Build a hash from our COMMIT_ITEMS array, keyed on the
1862251881Speter     relative paths (which come from the item URLs).  And
1863251881Speter     keep an array of those decoded paths, too.  */
1864251881Speter  for (i = 0; i < commit_items->nelts; i++)
1865251881Speter    {
1866251881Speter      svn_client_commit_item3_t *item =
1867251881Speter        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1868251881Speter      const char *path = item->session_relpath;
1869251881Speter      svn_hash_sets(items_hash, path, item);
1870251881Speter      APR_ARRAY_PUSH(paths, const char *) = path;
1871251881Speter    }
1872251881Speter
1873251881Speter  /* Setup the callback baton. */
1874251881Speter  cb_baton.editor = editor;
1875251881Speter  cb_baton.edit_baton = edit_baton;
1876251881Speter  cb_baton.file_mods = file_mods;
1877251881Speter  cb_baton.notify_path_prefix = notify_path_prefix;
1878251881Speter  cb_baton.ctx = ctx;
1879251881Speter  cb_baton.commit_items = items_hash;
1880251881Speter  cb_baton.base_url = base_url;
1881251881Speter
1882251881Speter  /* Drive the commit editor! */
1883251881Speter  SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1884251881Speter                                 do_item_commit, &cb_baton, scratch_pool));
1885251881Speter
1886251881Speter  /* Transmit outstanding text deltas. */
1887251881Speter  for (hi = apr_hash_first(scratch_pool, file_mods);
1888251881Speter       hi;
1889251881Speter       hi = apr_hash_next(hi))
1890251881Speter    {
1891299742Sdim      struct file_mod_t *mod = apr_hash_this_val(hi);
1892251881Speter      const svn_client_commit_item3_t *item = mod->item;
1893251881Speter      const svn_checksum_t *new_text_base_md5_checksum;
1894251881Speter      const svn_checksum_t *new_text_base_sha1_checksum;
1895251881Speter      svn_boolean_t fulltext = FALSE;
1896251881Speter      svn_error_t *err;
1897251881Speter
1898251881Speter      svn_pool_clear(iterpool);
1899251881Speter
1900251881Speter      /* Transmit the entry. */
1901251881Speter      if (ctx->cancel_func)
1902251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1903251881Speter
1904251881Speter      if (ctx->notify_func2)
1905251881Speter        {
1906251881Speter          svn_wc_notify_t *notify;
1907251881Speter          notify = svn_wc_create_notify(item->path,
1908251881Speter                                        svn_wc_notify_commit_postfix_txdelta,
1909251881Speter                                        iterpool);
1910251881Speter          notify->kind = svn_node_file;
1911251881Speter          notify->path_prefix = notify_path_prefix;
1912251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1913251881Speter        }
1914251881Speter
1915251881Speter      /* If the node has no history, transmit full text */
1916251881Speter      if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1917251881Speter          && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1918251881Speter        fulltext = TRUE;
1919251881Speter
1920251881Speter      err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1921251881Speter                                         &new_text_base_sha1_checksum,
1922251881Speter                                         ctx->wc_ctx, item->path,
1923251881Speter                                         fulltext, editor, mod->file_baton,
1924251881Speter                                         result_pool, iterpool);
1925251881Speter
1926251881Speter      if (err)
1927251881Speter        {
1928251881Speter          svn_pool_destroy(iterpool); /* Close tempfiles */
1929251881Speter          return svn_error_trace(fixup_commit_error(item->path,
1930251881Speter                                                    base_url,
1931251881Speter                                                    item->session_relpath,
1932251881Speter                                                    svn_node_file,
1933251881Speter                                                    err, ctx, scratch_pool));
1934251881Speter        }
1935251881Speter
1936251881Speter      if (sha1_checksums)
1937251881Speter        svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1938299742Sdim
1939299742Sdim      svn_pool_destroy(mod->file_pool);
1940251881Speter    }
1941251881Speter
1942299742Sdim  if (ctx->notify_func2)
1943299742Sdim    {
1944299742Sdim      svn_wc_notify_t *notify;
1945299742Sdim      notify = svn_wc_create_notify_url(base_url,
1946299742Sdim                                        svn_wc_notify_commit_finalizing,
1947299742Sdim                                        iterpool);
1948299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1949299742Sdim    }
1950299742Sdim
1951251881Speter  svn_pool_destroy(iterpool);
1952251881Speter
1953251881Speter  /* Close the edit. */
1954251881Speter  return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1955251881Speter}
1956251881Speter
1957251881Speter
1958251881Spetersvn_error_t *
1959251881Spetersvn_client__get_log_msg(const char **log_msg,
1960251881Speter                        const char **tmp_file,
1961251881Speter                        const apr_array_header_t *commit_items,
1962251881Speter                        svn_client_ctx_t *ctx,
1963251881Speter                        apr_pool_t *pool)
1964251881Speter{
1965251881Speter  if (ctx->log_msg_func3)
1966251881Speter    {
1967251881Speter      /* The client provided a callback function for the current API.
1968251881Speter         Forward the call to it directly. */
1969251881Speter      return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1970251881Speter                                   ctx->log_msg_baton3, pool);
1971251881Speter    }
1972251881Speter  else if (ctx->log_msg_func2 || ctx->log_msg_func)
1973251881Speter    {
1974251881Speter      /* The client provided a pre-1.5 (or pre-1.3) API callback
1975251881Speter         function.  Convert the commit_items list to the appropriate
1976251881Speter         type, and forward call to it. */
1977251881Speter      svn_error_t *err;
1978251881Speter      apr_pool_t *scratch_pool = svn_pool_create(pool);
1979251881Speter      apr_array_header_t *old_commit_items =
1980251881Speter        apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
1981251881Speter
1982251881Speter      int i;
1983251881Speter      for (i = 0; i < commit_items->nelts; i++)
1984251881Speter        {
1985251881Speter          svn_client_commit_item3_t *item =
1986251881Speter            APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1987251881Speter
1988251881Speter          if (ctx->log_msg_func2)
1989251881Speter            {
1990251881Speter              svn_client_commit_item2_t *old_item =
1991251881Speter                apr_pcalloc(scratch_pool, sizeof(*old_item));
1992251881Speter
1993251881Speter              old_item->path = item->path;
1994251881Speter              old_item->kind = item->kind;
1995251881Speter              old_item->url = item->url;
1996251881Speter              old_item->revision = item->revision;
1997251881Speter              old_item->copyfrom_url = item->copyfrom_url;
1998251881Speter              old_item->copyfrom_rev = item->copyfrom_rev;
1999251881Speter              old_item->state_flags = item->state_flags;
2000251881Speter              old_item->wcprop_changes = item->incoming_prop_changes;
2001251881Speter
2002251881Speter              APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
2003251881Speter                old_item;
2004251881Speter            }
2005251881Speter          else /* ctx->log_msg_func */
2006251881Speter            {
2007251881Speter              svn_client_commit_item_t *old_item =
2008251881Speter                apr_pcalloc(scratch_pool, sizeof(*old_item));
2009251881Speter
2010251881Speter              old_item->path = item->path;
2011251881Speter              old_item->kind = item->kind;
2012251881Speter              old_item->url = item->url;
2013251881Speter              /* The pre-1.3 API used the revision field for copyfrom_rev
2014251881Speter                 and revision depeding of copyfrom_url. */
2015251881Speter              old_item->revision = item->copyfrom_url ?
2016251881Speter                item->copyfrom_rev : item->revision;
2017251881Speter              old_item->copyfrom_url = item->copyfrom_url;
2018251881Speter              old_item->state_flags = item->state_flags;
2019251881Speter              old_item->wcprop_changes = item->incoming_prop_changes;
2020251881Speter
2021251881Speter              APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
2022251881Speter                old_item;
2023251881Speter            }
2024251881Speter        }
2025251881Speter
2026251881Speter      if (ctx->log_msg_func2)
2027251881Speter        err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
2028251881Speter                                    ctx->log_msg_baton2, pool);
2029251881Speter      else
2030251881Speter        err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
2031251881Speter                                   ctx->log_msg_baton, pool);
2032251881Speter      svn_pool_destroy(scratch_pool);
2033251881Speter      return err;
2034251881Speter    }
2035251881Speter  else
2036251881Speter    {
2037251881Speter      /* No log message callback was provided by the client. */
2038251881Speter      *log_msg = "";
2039251881Speter      *tmp_file = NULL;
2040251881Speter      return SVN_NO_ERROR;
2041251881Speter    }
2042251881Speter}
2043251881Speter
2044251881Spetersvn_error_t *
2045251881Spetersvn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
2046251881Speter                                 const apr_hash_t *revprop_table_in,
2047251881Speter                                 const char *log_msg,
2048251881Speter                                 svn_client_ctx_t *ctx,
2049251881Speter                                 apr_pool_t *pool)
2050251881Speter{
2051251881Speter  apr_hash_t *new_revprop_table;
2052251881Speter  if (revprop_table_in)
2053251881Speter    {
2054251881Speter      if (svn_prop_has_svn_prop(revprop_table_in, pool))
2055251881Speter        return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2056251881Speter                                _("Standard properties can't be set "
2057251881Speter                                  "explicitly as revision properties"));
2058251881Speter      new_revprop_table = apr_hash_copy(pool, revprop_table_in);
2059251881Speter    }
2060251881Speter  else
2061251881Speter    {
2062251881Speter      new_revprop_table = apr_hash_make(pool);
2063251881Speter    }
2064251881Speter  svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
2065251881Speter                svn_string_create(log_msg, pool));
2066251881Speter  *revprop_table_out = new_revprop_table;
2067251881Speter  return SVN_NO_ERROR;
2068251881Speter}
2069