1/*
2 * commit_util.c:  Driver for the WC commit process.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27#include <string.h>
28
29#include <apr_pools.h>
30#include <apr_hash.h>
31#include <apr_md5.h>
32
33#include "client.h"
34#include "svn_dirent_uri.h"
35#include "svn_path.h"
36#include "svn_types.h"
37#include "svn_pools.h"
38#include "svn_props.h"
39#include "svn_iter.h"
40#include "svn_hash.h"
41
42#include <assert.h>
43
44#include "svn_private_config.h"
45#include "private/svn_wc_private.h"
46#include "private/svn_client_private.h"
47#include "private/svn_sorts_private.h"
48
49/*** Uncomment this to turn on commit driver debugging. ***/
50/*
51#define SVN_CLIENT_COMMIT_DEBUG
52*/
53
54/* Wrap an RA error in a nicer error if one is available. */
55static svn_error_t *
56fixup_commit_error(const char *local_abspath,
57                   const char *base_url,
58                   const char *path,
59                   svn_node_kind_t kind,
60                   svn_error_t *err,
61                   svn_client_ctx_t *ctx,
62                   apr_pool_t *scratch_pool)
63{
64  if (err->apr_err == SVN_ERR_FS_NOT_FOUND
65      || err->apr_err == SVN_ERR_FS_CONFLICT
66      || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS
67      || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE
68      || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND
69      || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS
70      || err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED
71      || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE))
72    {
73      if (ctx->notify_func2)
74        {
75          svn_wc_notify_t *notify;
76
77          if (local_abspath)
78            notify = svn_wc_create_notify(local_abspath,
79                                          svn_wc_notify_failed_out_of_date,
80                                          scratch_pool);
81          else
82            notify = svn_wc_create_notify_url(
83                                svn_path_url_add_component2(base_url, path,
84                                                            scratch_pool),
85                                svn_wc_notify_failed_out_of_date,
86                                scratch_pool);
87
88          notify->kind = kind;
89          notify->err = err;
90
91          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
92        }
93
94      return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err,
95                               (kind == svn_node_dir
96                                 ? _("Directory '%s' is out of date")
97                                 : _("File '%s' is out of date")),
98                               local_abspath
99                                  ? svn_dirent_local_style(local_abspath,
100                                                           scratch_pool)
101                                  : svn_path_url_add_component2(base_url,
102                                                                path,
103                                                                scratch_pool));
104    }
105  else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN)
106           || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH
107           || err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN
108           || err->apr_err == SVN_ERR_RA_NOT_LOCKED)
109    {
110      if (ctx->notify_func2)
111        {
112          svn_wc_notify_t *notify;
113
114          if (local_abspath)
115            notify = svn_wc_create_notify(local_abspath,
116                                          svn_wc_notify_failed_locked,
117                                          scratch_pool);
118          else
119            notify = svn_wc_create_notify_url(
120                                svn_path_url_add_component2(base_url, path,
121                                                            scratch_pool),
122                                svn_wc_notify_failed_locked,
123                                scratch_pool);
124
125          notify->kind = kind;
126          notify->err = err;
127
128          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
129        }
130
131      return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err,
132                   (kind == svn_node_dir
133                     ? _("Directory '%s' is locked in another working copy")
134                     : _("File '%s' is locked in another working copy")),
135                   local_abspath
136                      ? svn_dirent_local_style(local_abspath,
137                                               scratch_pool)
138                      : svn_path_url_add_component2(base_url,
139                                                    path,
140                                                    scratch_pool));
141    }
142  else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)
143           || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE)
144    {
145      if (ctx->notify_func2)
146        {
147          svn_wc_notify_t *notify;
148
149          if (local_abspath)
150            notify = svn_wc_create_notify(
151                                    local_abspath,
152                                    svn_wc_notify_failed_forbidden_by_server,
153                                    scratch_pool);
154          else
155            notify = svn_wc_create_notify_url(
156                                svn_path_url_add_component2(base_url, path,
157                                                            scratch_pool),
158                                svn_wc_notify_failed_forbidden_by_server,
159                                scratch_pool);
160
161          notify->kind = kind;
162          notify->err = err;
163
164          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
165        }
166
167      return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err,
168                   (kind == svn_node_dir
169                     ? _("Changing directory '%s' is forbidden by the server")
170                     : _("Changing file '%s' is forbidden by the server")),
171                   local_abspath
172                      ? svn_dirent_local_style(local_abspath,
173                                               scratch_pool)
174                      : svn_path_url_add_component2(base_url,
175                                                    path,
176                                                    scratch_pool));
177    }
178  else
179    return err;
180}
181
182
183/*** Harvesting Commit Candidates ***/
184
185
186/* Add a new commit candidate (described by all parameters except
187   `COMMITTABLES') to the COMMITTABLES hash.  All of the commit item's
188   members are allocated out of RESULT_POOL.
189
190   If the state flag specifies that a lock must be used, store the token in LOCK
191   in lock_tokens.
192 */
193static svn_error_t *
194add_committable(svn_client__committables_t *committables,
195                const char *local_abspath,
196                svn_node_kind_t kind,
197                const char *repos_root_url,
198                const char *repos_relpath,
199                svn_revnum_t revision,
200                const char *copyfrom_relpath,
201                svn_revnum_t copyfrom_rev,
202                const char *moved_from_abspath,
203                apr_byte_t state_flags,
204                apr_hash_t *lock_tokens,
205                const svn_lock_t *lock,
206                apr_pool_t *result_pool,
207                apr_pool_t *scratch_pool)
208{
209  apr_array_header_t *array;
210  svn_client_commit_item3_t *new_item;
211
212  /* Sanity checks. */
213  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
214  SVN_ERR_ASSERT(repos_root_url && repos_relpath);
215
216  /* ### todo: Get the canonical repository for this item, which will
217     be the real key for the COMMITTABLES hash, instead of the above
218     bogosity. */
219  array = svn_hash_gets(committables->by_repository, repos_root_url);
220
221  /* E-gads!  There is no array for this repository yet!  Oh, no
222     problem, we'll just create (and add to the hash) one. */
223  if (array == NULL)
224    {
225      array = apr_array_make(result_pool, 1, sizeof(new_item));
226      svn_hash_sets(committables->by_repository,
227                    apr_pstrdup(result_pool, repos_root_url), array);
228    }
229
230  /* Now update pointer values, ensuring that their allocations live
231     in POOL. */
232  new_item = svn_client_commit_item3_create(result_pool);
233  new_item->path           = apr_pstrdup(result_pool, local_abspath);
234  new_item->kind           = kind;
235  new_item->url            = svn_path_url_add_component2(repos_root_url,
236                                                         repos_relpath,
237                                                         result_pool);
238  new_item->revision       = revision;
239  new_item->copyfrom_url   = copyfrom_relpath
240                                ? svn_path_url_add_component2(repos_root_url,
241                                                              copyfrom_relpath,
242                                                              result_pool)
243                                : NULL;
244  new_item->copyfrom_rev   = copyfrom_rev;
245  new_item->state_flags    = state_flags;
246  new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
247                                                   sizeof(svn_prop_t *));
248
249  if (moved_from_abspath)
250    new_item->moved_from_abspath = apr_pstrdup(result_pool,
251                                               moved_from_abspath);
252
253  /* Now, add the commit item to the array. */
254  APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
255
256  /* ... and to the hash. */
257  svn_hash_sets(committables->by_path, new_item->path, new_item);
258
259  if (lock
260      && lock_tokens
261      && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN))
262    {
263      svn_hash_sets(lock_tokens, new_item->url,
264                    apr_pstrdup(result_pool, lock->token));
265    }
266
267  return SVN_NO_ERROR;
268}
269
270/* If there is a commit item for PATH in COMMITTABLES, return it, else
271   return NULL.  Use POOL for temporary allocation only. */
272static svn_client_commit_item3_t *
273look_up_committable(svn_client__committables_t *committables,
274                    const char *path,
275                    apr_pool_t *pool)
276{
277  return (svn_client_commit_item3_t *)
278      svn_hash_gets(committables->by_path, path);
279}
280
281/* Helper function for svn_client__harvest_committables().
282 * Determine whether we are within a tree-conflicted subtree of the
283 * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
284static svn_error_t *
285bail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx,
286                                 const char *local_abspath,
287                                 svn_wc_notify_func2_t notify_func,
288                                 void *notify_baton,
289                                 apr_pool_t *scratch_pool)
290{
291  const char *wcroot_abspath;
292
293  SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath,
294                             scratch_pool, scratch_pool));
295
296  local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
297
298  while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath))
299    {
300      svn_boolean_t tree_conflicted;
301
302      /* Check if the parent has tree conflicts */
303      SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
304                                   wc_ctx, local_abspath, scratch_pool));
305      if (tree_conflicted)
306        {
307          if (notify_func != NULL)
308            {
309              notify_func(notify_baton,
310                          svn_wc_create_notify(local_abspath,
311                                               svn_wc_notify_failed_conflict,
312                                               scratch_pool),
313                          scratch_pool);
314            }
315
316          return svn_error_createf(
317                   SVN_ERR_WC_FOUND_CONFLICT, NULL,
318                   _("Aborting commit: '%s' remains in tree-conflict"),
319                   svn_dirent_local_style(local_abspath, scratch_pool));
320        }
321
322      /* Step outwards */
323      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
324        break;
325      else
326        local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
327    }
328
329  return SVN_NO_ERROR;
330}
331
332
333/* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using
334   WC_CTX and add those candidates to COMMITTABLES.  If in ADDS_ONLY modes,
335   only new additions are recognized.
336
337   DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH
338   when LOCAL_ABSPATH is itself a directory; see
339   svn_client__harvest_committables() for its behavior.
340
341   Lock tokens of candidates will be added to LOCK_TOKENS, if
342   non-NULL.  JUST_LOCKED indicates whether to treat non-modified items with
343   lock tokens as commit candidates.
344
345   If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to
346   be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as
347   items to delete in the copy destination.  COPY_MODE_ROOT should be set TRUE
348   for the first call for which COPY_MODE is TRUE, i.e. not for the
349   recursive calls, and FALSE otherwise.
350
351   If CHANGELISTS is non-NULL, it is a hash whose keys are const char *
352   changelist names used as a restrictive filter
353   when harvesting committables; that is, don't add a path to
354   COMMITTABLES unless it's a member of one of those changelists.
355
356   IS_EXPLICIT_TARGET should always be passed as TRUE, except when
357   harvest_committables() calls itself in recursion. This provides a way to
358   tell whether LOCAL_ABSPATH was an original target or whether it was reached
359   by recursing deeper into a dir target. (This is used to skip all file
360   externals that aren't explicit commit targets.)
361
362   DANGLERS is a hash table mapping const char* absolute paths of a parent
363   to a const char * absolute path of a child. See the comment about
364   danglers at the top of svn_client__harvest_committables().
365
366   If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
367   if the user has cancelled the operation.
368
369   Any items added to COMMITTABLES are allocated from the COMITTABLES
370   hash pool, not POOL.  SCRATCH_POOL is used for temporary allocations. */
371
372struct harvest_baton
373{
374  /* Static data */
375  const char *root_abspath;
376  svn_client__committables_t *committables;
377  apr_hash_t *lock_tokens;
378  const char *commit_relpath; /* Valid for the harvest root */
379  svn_depth_t depth;
380  svn_boolean_t just_locked;
381  apr_hash_t *changelists;
382  apr_hash_t *danglers;
383  svn_client__check_url_kind_t check_url_func;
384  void *check_url_baton;
385  svn_wc_notify_func2_t notify_func;
386  void *notify_baton;
387  svn_wc_context_t *wc_ctx;
388  apr_pool_t *result_pool;
389
390  /* Harvester state */
391  const char *skip_below_abspath; /* If non-NULL, skip everything below */
392};
393
394static svn_error_t *
395harvest_status_callback(void *status_baton,
396                        const char *local_abspath,
397                        const svn_wc_status3_t *status,
398                        apr_pool_t *scratch_pool);
399
400static svn_error_t *
401harvest_committables(const char *local_abspath,
402                     svn_client__committables_t *committables,
403                     apr_hash_t *lock_tokens,
404                     const char *copy_mode_relpath,
405                     svn_depth_t depth,
406                     svn_boolean_t just_locked,
407                     apr_hash_t *changelists,
408                     apr_hash_t *danglers,
409                     svn_client__check_url_kind_t check_url_func,
410                     void *check_url_baton,
411                     svn_cancel_func_t cancel_func,
412                     void *cancel_baton,
413                     svn_wc_notify_func2_t notify_func,
414                     void *notify_baton,
415                     svn_wc_context_t *wc_ctx,
416                     apr_pool_t *result_pool,
417                     apr_pool_t *scratch_pool)
418{
419  struct harvest_baton baton;
420
421  SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked);
422
423  baton.root_abspath = local_abspath;
424  baton.committables = committables;
425  baton.lock_tokens = lock_tokens;
426  baton.commit_relpath = copy_mode_relpath;
427  baton.depth = depth;
428  baton.just_locked = just_locked;
429  baton.changelists = changelists;
430  baton.danglers = danglers;
431  baton.check_url_func = check_url_func;
432  baton.check_url_baton = check_url_baton;
433  baton.notify_func = notify_func;
434  baton.notify_baton = notify_baton;
435  baton.wc_ctx = wc_ctx;
436  baton.result_pool = result_pool;
437
438  baton.skip_below_abspath = NULL;
439
440  SVN_ERR(svn_wc_walk_status(wc_ctx,
441                             local_abspath,
442                             depth,
443                             (copy_mode_relpath != NULL) /* get_all */,
444                             FALSE /* no_ignore */,
445                             FALSE /* ignore_text_mods */,
446                             NULL /* ignore_patterns */,
447                             harvest_status_callback,
448                             &baton,
449                             cancel_func, cancel_baton,
450                             scratch_pool));
451
452  return SVN_NO_ERROR;
453}
454
455static svn_error_t *
456harvest_not_present_for_copy(svn_wc_context_t *wc_ctx,
457                             const char *local_abspath,
458                             svn_client__committables_t *committables,
459                             const char *repos_root_url,
460                             const char *commit_relpath,
461                             svn_client__check_url_kind_t check_url_func,
462                             void *check_url_baton,
463                             apr_pool_t *result_pool,
464                             apr_pool_t *scratch_pool)
465{
466  const apr_array_header_t *children;
467  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
468  int i;
469
470  SVN_ERR_ASSERT(commit_relpath != NULL);
471
472  /* A function to retrieve not present children would be nice to have */
473  SVN_ERR(svn_wc__node_get_not_present_children(&children, wc_ctx,
474                                                local_abspath,
475                                                scratch_pool, iterpool));
476
477  for (i = 0; i < children->nelts; i++)
478    {
479      const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
480      const char *name = svn_dirent_basename(this_abspath, NULL);
481      const char *this_commit_relpath;
482      svn_boolean_t not_present;
483      svn_node_kind_t kind;
484
485      svn_pool_clear(iterpool);
486
487      SVN_ERR(svn_wc__node_is_not_present(&not_present, NULL, NULL, wc_ctx,
488                                          this_abspath, FALSE, scratch_pool));
489
490      if (!not_present)
491        continue; /* Node is replaced */
492
493      this_commit_relpath = svn_relpath_join(commit_relpath, name,
494                                             iterpool);
495
496      /* We should check if we should really add a delete operation */
497      if (check_url_func)
498        {
499          svn_revnum_t parent_rev;
500          const char *parent_repos_relpath;
501          const char *parent_repos_root_url;
502          const char *node_url;
503
504          /* Determine from what parent we would be the deleted child */
505          SVN_ERR(svn_wc__node_get_origin(
506                              NULL, &parent_rev, &parent_repos_relpath,
507                              &parent_repos_root_url, NULL, NULL, NULL,
508                              wc_ctx,
509                              svn_dirent_dirname(this_abspath,
510                                                  scratch_pool),
511                              FALSE, scratch_pool, scratch_pool));
512
513          node_url = svn_path_url_add_component2(
514                        svn_path_url_add_component2(parent_repos_root_url,
515                                                    parent_repos_relpath,
516                                                    scratch_pool),
517                        svn_dirent_basename(this_abspath, NULL),
518                        iterpool);
519
520          SVN_ERR(check_url_func(check_url_baton, &kind,
521                                 node_url, parent_rev, iterpool));
522
523          if (kind == svn_node_none)
524            continue; /* This node can't be deleted */
525        }
526      else
527        SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath,
528                                  TRUE, TRUE, scratch_pool));
529
530      SVN_ERR(add_committable(committables, this_abspath, kind,
531                              repos_root_url,
532                              this_commit_relpath,
533                              SVN_INVALID_REVNUM,
534                              NULL /* copyfrom_relpath */,
535                              SVN_INVALID_REVNUM /* copyfrom_rev */,
536                              NULL /* moved_from_abspath */,
537                              SVN_CLIENT_COMMIT_ITEM_DELETE,
538                              NULL, NULL,
539                              result_pool, scratch_pool));
540    }
541
542  svn_pool_destroy(iterpool);
543  return SVN_NO_ERROR;
544}
545
546/* Implements svn_wc_status_func4_t */
547static svn_error_t *
548harvest_status_callback(void *status_baton,
549                        const char *local_abspath,
550                        const svn_wc_status3_t *status,
551                        apr_pool_t *scratch_pool)
552{
553  apr_byte_t state_flags = 0;
554  svn_revnum_t node_rev;
555  const char *cf_relpath = NULL;
556  svn_revnum_t cf_rev = SVN_INVALID_REVNUM;
557  svn_boolean_t matches_changelists;
558  svn_boolean_t is_added;
559  svn_boolean_t is_deleted;
560  svn_boolean_t is_replaced;
561  svn_boolean_t is_op_root;
562  svn_revnum_t original_rev;
563  const char *original_relpath;
564  svn_boolean_t copy_mode;
565
566  struct harvest_baton *baton = status_baton;
567  svn_boolean_t is_harvest_root =
568                (strcmp(baton->root_abspath, local_abspath) == 0);
569  svn_client__committables_t *committables = baton->committables;
570  const char *repos_root_url = status->repos_root_url;
571  const char *commit_relpath = NULL;
572  svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root);
573  svn_boolean_t just_locked = baton->just_locked;
574  apr_hash_t *changelists = baton->changelists;
575  svn_wc_notify_func2_t notify_func = baton->notify_func;
576  void *notify_baton = baton->notify_baton;
577  svn_wc_context_t *wc_ctx = baton->wc_ctx;
578  apr_pool_t *result_pool = baton->result_pool;
579  const char *moved_from_abspath = NULL;
580
581  if (baton->commit_relpath)
582    commit_relpath = svn_relpath_join(
583                        baton->commit_relpath,
584                        svn_dirent_skip_ancestor(baton->root_abspath,
585                                                 local_abspath),
586                        scratch_pool);
587
588  copy_mode = (commit_relpath != NULL);
589
590  if (baton->skip_below_abspath
591      && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
592    {
593      return SVN_NO_ERROR;
594    }
595  else
596    baton->skip_below_abspath = NULL; /* We have left the skip tree */
597
598  /* Return early for nodes that don't have a committable status */
599  switch (status->node_status)
600    {
601      case svn_wc_status_unversioned:
602      case svn_wc_status_ignored:
603      case svn_wc_status_external:
604      case svn_wc_status_none:
605        /* Unversioned nodes aren't committable, but are reported by the status
606           walker.
607           But if the unversioned node is the root of the walk, we have a user
608           error */
609        if (is_harvest_root)
610          return svn_error_createf(
611                       SVN_ERR_ILLEGAL_TARGET, NULL,
612                       _("'%s' is not under version control"),
613                       svn_dirent_local_style(local_abspath, scratch_pool));
614        return SVN_NO_ERROR;
615      case svn_wc_status_normal:
616        /* Status normal nodes aren't modified, so we don't have to commit them
617           when we perform a normal commit. But if a node is conflicted we want
618           to stop the commit and if we are collecting lock tokens we want to
619           look further anyway.
620
621           When in copy mode we need to compare the revision of the node against
622           the parent node to copy mixed-revision base nodes properly */
623        if (!copy_mode && !status->conflicted
624            && !(just_locked && status->lock))
625          return SVN_NO_ERROR;
626        break;
627      default:
628        /* Fall through */
629        break;
630    }
631
632  /* Early out if the item is already marked as committable. */
633  if (look_up_committable(committables, local_abspath, scratch_pool))
634    return SVN_NO_ERROR;
635
636  SVN_ERR_ASSERT((copy_mode && commit_relpath)
637                 || (! copy_mode && ! commit_relpath));
638  SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root);
639
640  /* Save the result for reuse. */
641  matches_changelists = ((changelists == NULL)
642                         || (status->changelist != NULL
643                             && svn_hash_gets(changelists, status->changelist)
644                                != NULL));
645
646  /* Early exit. */
647  if (status->kind != svn_node_dir && ! matches_changelists)
648    {
649      return SVN_NO_ERROR;
650    }
651
652  /* If NODE is in our changelist, then examine it for conflicts. We
653     need to bail out if any conflicts exist.
654     The status walker checked for conflict marker removal. */
655  if (status->conflicted && matches_changelists)
656    {
657      if (notify_func != NULL)
658        {
659          notify_func(notify_baton,
660                      svn_wc_create_notify(local_abspath,
661                                           svn_wc_notify_failed_conflict,
662                                           scratch_pool),
663                      scratch_pool);
664        }
665
666      return svn_error_createf(
667            SVN_ERR_WC_FOUND_CONFLICT, NULL,
668            _("Aborting commit: '%s' remains in conflict"),
669            svn_dirent_local_style(local_abspath, scratch_pool));
670    }
671  else if (status->node_status == svn_wc_status_obstructed)
672    {
673      /* A node's type has changed before attempting to commit.
674         This also catches symlink vs non symlink changes */
675
676      if (notify_func != NULL)
677        {
678          notify_func(notify_baton,
679                      svn_wc_create_notify(local_abspath,
680                                           svn_wc_notify_failed_obstruction,
681                                           scratch_pool),
682                      scratch_pool);
683        }
684
685      return svn_error_createf(
686                    SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
687                    _("Node '%s' has unexpectedly changed kind"),
688                    svn_dirent_local_style(local_abspath, scratch_pool));
689    }
690
691  if (status->conflicted && status->kind == svn_node_unknown)
692    return SVN_NO_ERROR; /* Ignore delete-delete conflict */
693
694  /* Return error on unknown path kinds.  We check both the entry and
695     the node itself, since a path might have changed kind since its
696     entry was written. */
697  SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted,
698                                         &is_replaced,
699                                         &is_op_root,
700                                         &node_rev,
701                                         &original_rev, &original_relpath,
702                                         wc_ctx, local_abspath,
703                                         scratch_pool, scratch_pool));
704
705  /* Hande file externals only when passed as explicit target. Note that
706   * svn_client_commit6() passes all committable externals in as explicit
707   * targets iff they count. */
708  if (status->file_external && !is_harvest_root)
709    {
710      return SVN_NO_ERROR;
711    }
712
713  if (status->node_status == svn_wc_status_missing && matches_changelists)
714    {
715      /* Added files and directories must exist. See issue #3198. */
716      if (is_added && is_op_root)
717        {
718          if (notify_func != NULL)
719            {
720              notify_func(notify_baton,
721                          svn_wc_create_notify(local_abspath,
722                                               svn_wc_notify_failed_missing,
723                                               scratch_pool),
724                          scratch_pool);
725            }
726          return svn_error_createf(
727             SVN_ERR_WC_PATH_NOT_FOUND, NULL,
728             _("'%s' is scheduled for addition, but is missing"),
729             svn_dirent_local_style(local_abspath, scratch_pool));
730        }
731
732      return SVN_NO_ERROR;
733    }
734
735  if (is_deleted && !is_op_root /* && !is_added */)
736    return SVN_NO_ERROR; /* Not an operational delete and not an add. */
737
738  /* Check for the deletion case.
739     * We delete explicitly deleted nodes (duh!)
740     * We delete not-present children of copies
741     * We delete nodes that directly replace a node in its ancestor
742   */
743
744  if (is_deleted || is_replaced)
745    state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
746
747  /* Check for adds and copies */
748  if (is_added && is_op_root)
749    {
750      /* Root of local add or copy */
751      state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
752
753      if (original_relpath)
754        {
755          /* Root of copy */
756          state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
757          cf_relpath = original_relpath;
758          cf_rev = original_rev;
759
760          if (status->moved_from_abspath && !copy_mode)
761            {
762              state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE;
763              moved_from_abspath = status->moved_from_abspath;
764            }
765        }
766    }
767
768  /* Further copies may occur in copy mode. */
769  else if (copy_mode
770           && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
771    {
772      svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
773      const char *dir_repos_relpath = NULL;
774
775      if (!copy_mode_root && !is_added)
776        SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, &dir_repos_relpath, NULL,
777                                      NULL, NULL,
778                                      wc_ctx, svn_dirent_dirname(local_abspath,
779                                                                 scratch_pool),
780                                      FALSE /* ignore_enoent */,
781                                      scratch_pool, scratch_pool));
782
783      if (copy_mode_root || status->switched || node_rev != dir_rev)
784        {
785          state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD
786                          | SVN_CLIENT_COMMIT_ITEM_IS_COPY);
787
788          if (status->copied)
789            {
790              /* Copy from original location */
791              cf_rev = original_rev;
792              cf_relpath = original_relpath;
793            }
794          else
795            {
796              /* Copy BASE location, to represent a mixed-rev or switch copy */
797              cf_rev = status->revision;
798              cf_relpath = status->repos_relpath;
799            }
800
801          if (!copy_mode_root && !is_added && baton->check_url_func
802              && dir_repos_relpath)
803            {
804              svn_node_kind_t me_kind;
805              /* Maybe we need to issue an delete (mixed rev/switched) */
806
807              SVN_ERR(baton->check_url_func(
808                            baton->check_url_baton, &me_kind,
809                            svn_path_url_add_component2(repos_root_url,
810                                        svn_relpath_join(dir_repos_relpath,
811                                            svn_dirent_basename(local_abspath,
812                                                                NULL),
813                                            scratch_pool),
814                                        scratch_pool),
815                                        dir_rev, scratch_pool));
816              if (me_kind != svn_node_none)
817                state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
818            }
819        }
820    }
821
822  if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
823      || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
824    {
825      svn_boolean_t text_mod = FALSE;
826      svn_boolean_t prop_mod = FALSE;
827
828      if (status->kind == svn_node_file)
829        {
830          /* Check for text modifications on files */
831          if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
832              && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
833            {
834              text_mod = TRUE; /* Local added files are always modified */
835            }
836          else
837            text_mod = (status->text_status != svn_wc_status_normal);
838        }
839
840      prop_mod = (status->prop_status != svn_wc_status_normal
841                  && status->prop_status != svn_wc_status_none);
842
843      /* Set text/prop modification flags accordingly. */
844      if (text_mod)
845        state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
846      if (prop_mod)
847        state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
848    }
849
850  /* If the entry has a lock token and it is already a commit candidate,
851     or the caller wants unmodified locked items to be treated as
852     such, note this fact. */
853  if (status->lock && baton->lock_tokens && (state_flags || just_locked))
854    {
855      state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
856    }
857
858  /* Now, if this is something to commit, add it to our list. */
859  if (matches_changelists
860      && state_flags)
861    {
862      /* Finally, add the committable item. */
863      SVN_ERR(add_committable(committables, local_abspath,
864                              status->kind,
865                              repos_root_url,
866                              copy_mode
867                                      ? commit_relpath
868                                      : status->repos_relpath,
869                              copy_mode
870                                      ? SVN_INVALID_REVNUM
871                                      : node_rev,
872                              cf_relpath,
873                              cf_rev,
874                              moved_from_abspath,
875                              state_flags,
876                              baton->lock_tokens, status->lock,
877                              result_pool, scratch_pool));
878    }
879
880    /* Fetch lock tokens for descendants of deleted BASE nodes. */
881  if (matches_changelists
882      && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
883      && !copy_mode
884      && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */
885      && baton->lock_tokens)
886    {
887      apr_hash_t *local_relpath_tokens;
888      apr_hash_index_t *hi;
889
890      SVN_ERR(svn_wc__node_get_lock_tokens_recursive(
891                  &local_relpath_tokens, wc_ctx, local_abspath,
892                  result_pool, scratch_pool));
893
894      /* Add tokens to existing hash. */
895      for (hi = apr_hash_first(scratch_pool, local_relpath_tokens);
896           hi;
897           hi = apr_hash_next(hi))
898        {
899          const void *key;
900          apr_ssize_t klen;
901          void * val;
902
903          apr_hash_this(hi, &key, &klen, &val);
904
905          apr_hash_set(baton->lock_tokens, key, klen, val);
906        }
907    }
908
909  /* Make sure we check for dangling children on additions
910
911     We perform this operation on the harvest root, and on roots caused by
912     changelist filtering.
913  */
914  if (matches_changelists
915      && (is_harvest_root || baton->changelists)
916      && state_flags
917      && (is_added || (is_deleted && is_op_root && status->copied))
918      && baton->danglers)
919    {
920      /* If a node is added, its parent must exist in the repository at the
921         time of committing */
922      apr_hash_t *danglers = baton->danglers;
923      svn_boolean_t parent_added;
924      const char *parent_abspath = svn_dirent_dirname(local_abspath,
925                                                      scratch_pool);
926
927      /* First check if parent is already in the list of commits
928         (Common case for GUI clients that provide a list of commit targets) */
929      if (look_up_committable(committables, parent_abspath, scratch_pool))
930        parent_added = FALSE; /* Skip all expensive checks */
931      else
932        SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath,
933                                      scratch_pool));
934
935      if (parent_added)
936        {
937          const char *copy_root_abspath;
938          svn_boolean_t parent_is_copy;
939
940          /* The parent is added, so either it is a copy, or a locally added
941           * directory. In either case, we require the op-root of the parent
942           * to be part of the commit. See issue #4059. */
943          SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL,
944                                          NULL, NULL, &copy_root_abspath,
945                                          wc_ctx, parent_abspath,
946                                          FALSE, scratch_pool, scratch_pool));
947
948          if (parent_is_copy)
949            parent_abspath = copy_root_abspath;
950
951          if (!svn_hash_gets(danglers, parent_abspath))
952            {
953              svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath),
954                            apr_pstrdup(result_pool, local_abspath));
955            }
956        }
957    }
958
959  if (is_deleted && !is_added)
960    {
961      /* Skip all descendants */
962      if (status->kind == svn_node_dir)
963        baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
964                                                local_abspath);
965      return SVN_NO_ERROR;
966    }
967
968  /* Recursively handle each node according to depth, except when the
969     node is only being deleted, or is in an added tree (as added trees
970     use the normal commit handling). */
971  if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir)
972    {
973      SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables,
974                                           repos_root_url, commit_relpath,
975                                           baton->check_url_func,
976                                           baton->check_url_baton,
977                                           result_pool, scratch_pool));
978    }
979
980  return SVN_NO_ERROR;
981}
982
983/* Baton for handle_descendants */
984struct handle_descendants_baton
985{
986  svn_wc_context_t *wc_ctx;
987  svn_cancel_func_t cancel_func;
988  void *cancel_baton;
989  svn_client__check_url_kind_t check_url_func;
990  void *check_url_baton;
991  svn_client__committables_t *committables;
992};
993
994/* Helper for the commit harvesters */
995static svn_error_t *
996handle_descendants(void *baton,
997                   const void *key, apr_ssize_t klen, void *val,
998                   apr_pool_t *pool)
999{
1000  struct handle_descendants_baton *hdb = baton;
1001  apr_array_header_t *commit_items = val;
1002  apr_pool_t *iterpool = svn_pool_create(pool);
1003  const char *repos_root_url = key;
1004  int i;
1005
1006  for (i = 0; i < commit_items->nelts; i++)
1007    {
1008      svn_client_commit_item3_t *item =
1009        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1010      const apr_array_header_t *absent_descendants;
1011      int j;
1012
1013      /* Is this a copy operation? */
1014      if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1015          || ! item->copyfrom_url)
1016        continue;
1017
1018      if (hdb->cancel_func)
1019        SVN_ERR(hdb->cancel_func(hdb->cancel_baton));
1020
1021      svn_pool_clear(iterpool);
1022
1023      SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants,
1024                                                  hdb->wc_ctx, item->path,
1025                                                  iterpool, iterpool));
1026
1027      for (j = 0; j < absent_descendants->nelts; j++)
1028        {
1029          svn_node_kind_t kind;
1030          svn_client_commit_item3_t *desc_item;
1031          const char *relpath = APR_ARRAY_IDX(absent_descendants, j,
1032                                              const char *);
1033          const char *local_abspath = svn_dirent_join(item->path, relpath,
1034                                                      iterpool);
1035
1036          /* ### Need a sub-iterpool? */
1037
1038
1039          /* We found a 'not present' descendant during a copy (at op_depth>0),
1040             this is most commonly caused by copying some mixed revision tree.
1041
1042             In this case not present can imply that the node does not exist
1043             in the parent revision, or that the node does. But we want to copy
1044             the working copy state in which it does not exist, but might be
1045             replaced. */
1046
1047          desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath);
1048
1049          /* If the path has a commit operation (possibly at an higher
1050             op_depth, we might want to turn an add in a replace. */
1051          if (desc_item)
1052            {
1053              const char *dir;
1054              svn_boolean_t found_intermediate = FALSE;
1055
1056              if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1057                continue; /* We already have a delete or replace */
1058              else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1059                continue; /* Not a copy/add, just a modification */
1060
1061              dir = svn_dirent_dirname(local_abspath, iterpool);
1062
1063              while (strcmp(dir, item->path))
1064                {
1065                  svn_client_commit_item3_t *i_item;
1066
1067                  i_item = svn_hash_gets(hdb->committables->by_path, dir);
1068
1069                  if (i_item)
1070                    {
1071                      if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1072                          || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1073                        {
1074                          found_intermediate = TRUE;
1075                          break;
1076                        }
1077                    }
1078                  dir = svn_dirent_dirname(dir, iterpool);
1079                }
1080
1081              if (found_intermediate)
1082                continue; /* Some intermediate ancestor is an add or delete */
1083
1084              /* Fall through to detect if we need to turn the add in a
1085                 replace. */
1086            }
1087
1088          if (hdb->check_url_func)
1089            {
1090              const char *from_url = svn_path_url_add_component2(
1091                                                item->copyfrom_url, relpath,
1092                                                iterpool);
1093
1094              SVN_ERR(hdb->check_url_func(hdb->check_url_baton,
1095                                          &kind, from_url, item->copyfrom_rev,
1096                                          iterpool));
1097
1098              if (kind == svn_node_none)
1099                continue; /* This node is already deleted */
1100            }
1101          else
1102            kind = svn_node_unknown; /* 'Ok' for a delete of something */
1103
1104          if (desc_item)
1105            {
1106              /* Extend the existing add/copy item to create a replace */
1107              desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
1108              continue;
1109            }
1110
1111          /* Add a new commit item that describes the delete */
1112
1113          SVN_ERR(add_committable(hdb->committables,
1114                                  svn_dirent_join(item->path, relpath,
1115                                                  iterpool),
1116                                  kind,
1117                                  repos_root_url,
1118                                  svn_uri_skip_ancestor(
1119                                        repos_root_url,
1120                                        svn_path_url_add_component2(item->url,
1121                                                                    relpath,
1122                                                                    iterpool),
1123                                        iterpool),
1124                                  SVN_INVALID_REVNUM,
1125                                  NULL /* copyfrom_relpath */,
1126                                  SVN_INVALID_REVNUM,
1127                                  NULL /* moved_from_abspath */,
1128                                  SVN_CLIENT_COMMIT_ITEM_DELETE,
1129                                  NULL /* lock tokens */,
1130                                  NULL /* lock */,
1131                                  commit_items->pool,
1132                                  iterpool));
1133        }
1134      }
1135
1136  svn_pool_destroy(iterpool);
1137  return SVN_NO_ERROR;
1138}
1139
1140/* Allocate and initialize the COMMITTABLES structure from POOL.
1141 */
1142static void
1143create_committables(svn_client__committables_t **committables,
1144                    apr_pool_t *pool)
1145{
1146  *committables = apr_palloc(pool, sizeof(**committables));
1147
1148  (*committables)->by_repository = apr_hash_make(pool);
1149  (*committables)->by_path = apr_hash_make(pool);
1150}
1151
1152svn_error_t *
1153svn_client__harvest_committables(svn_client__committables_t **committables,
1154                                 apr_hash_t **lock_tokens,
1155                                 const char *base_dir_abspath,
1156                                 const apr_array_header_t *targets,
1157                                 int depth_empty_start,
1158                                 svn_depth_t depth,
1159                                 svn_boolean_t just_locked,
1160                                 const apr_array_header_t *changelists,
1161                                 svn_client__check_url_kind_t check_url_func,
1162                                 void *check_url_baton,
1163                                 svn_client_ctx_t *ctx,
1164                                 apr_pool_t *result_pool,
1165                                 apr_pool_t *scratch_pool)
1166{
1167  int i;
1168  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1169  apr_hash_t *changelist_hash = NULL;
1170  struct handle_descendants_baton hdb;
1171  apr_hash_index_t *hi;
1172
1173  /* It's possible that one of the named targets has a parent that is
1174   * itself scheduled for addition or replacement -- that is, the
1175   * parent is not yet versioned in the repository.  This is okay, as
1176   * long as the parent itself is part of this same commit, either
1177   * directly, or by virtue of a grandparent, great-grandparent, etc,
1178   * being part of the commit.
1179   *
1180   * Since we don't know what's included in the commit until we've
1181   * harvested all the targets, we can't reliably check this as we
1182   * go.  So in `danglers', we record named targets whose parents
1183   * do not yet exist in the repository. Then after harvesting the total
1184   * commit group, we check to make sure those parents are included.
1185   *
1186   * Each key of danglers is a parent which does not exist in the
1187   * repository.  The (const char *) value is one of that parent's
1188   * children which is named as part of the commit; the child is
1189   * included only to make a better error message.
1190   *
1191   * (The reason we don't bother to check unnamed -- i.e, implicit --
1192   * targets is that they can only join the commit if their parents
1193   * did too, so this situation can't arise for them.)
1194   */
1195  apr_hash_t *danglers = apr_hash_make(scratch_pool);
1196
1197  SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath));
1198
1199  /* Create the COMMITTABLES structure. */
1200  create_committables(committables, result_pool);
1201
1202  /* And the LOCK_TOKENS dito. */
1203  *lock_tokens = apr_hash_make(result_pool);
1204
1205  /* If we have a list of changelists, convert that into a hash with
1206     changelist keys. */
1207  if (changelists && changelists->nelts)
1208    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists,
1209                                       scratch_pool));
1210
1211  for (i = 0; i < targets->nelts; ++i)
1212    {
1213      const char *target_abspath;
1214
1215      svn_pool_clear(iterpool);
1216
1217      /* Add the relative portion to the base abspath.  */
1218      target_abspath = svn_dirent_join(base_dir_abspath,
1219                                       APR_ARRAY_IDX(targets, i, const char *),
1220                                       iterpool);
1221
1222      /* Handle our TARGET. */
1223      /* Make sure this isn't inside a working copy subtree that is
1224       * marked as tree-conflicted. */
1225      SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath,
1226                                               ctx->notify_func2,
1227                                               ctx->notify_baton2,
1228                                               iterpool));
1229
1230      /* Are the remaining items externals with depth empty? */
1231      if (i == depth_empty_start)
1232        depth = svn_depth_empty;
1233
1234      SVN_ERR(harvest_committables(target_abspath,
1235                                   *committables, *lock_tokens,
1236                                   NULL /* COPY_MODE_RELPATH */,
1237                                   depth, just_locked, changelist_hash,
1238                                   danglers,
1239                                   check_url_func, check_url_baton,
1240                                   ctx->cancel_func, ctx->cancel_baton,
1241                                   ctx->notify_func2, ctx->notify_baton2,
1242                                   ctx->wc_ctx, result_pool, iterpool));
1243    }
1244
1245  hdb.wc_ctx = ctx->wc_ctx;
1246  hdb.cancel_func = ctx->cancel_func;
1247  hdb.cancel_baton = ctx->cancel_baton;
1248  hdb.check_url_func = check_url_func;
1249  hdb.check_url_baton = check_url_baton;
1250  hdb.committables = *committables;
1251
1252  SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository,
1253                            handle_descendants, &hdb, iterpool));
1254
1255  /* Make sure that every path in danglers is part of the commit. */
1256  for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi))
1257    {
1258      const char *dangling_parent = apr_hash_this_key(hi);
1259
1260      svn_pool_clear(iterpool);
1261
1262      if (! look_up_committable(*committables, dangling_parent, iterpool))
1263        {
1264          const char *dangling_child = apr_hash_this_val(hi);
1265
1266          if (ctx->notify_func2 != NULL)
1267            {
1268              svn_wc_notify_t *notify;
1269
1270              notify = svn_wc_create_notify(dangling_child,
1271                                            svn_wc_notify_failed_no_parent,
1272                                            scratch_pool);
1273
1274              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1275            }
1276
1277          return svn_error_createf(
1278                           SVN_ERR_ILLEGAL_TARGET, NULL,
1279                           _("'%s' is not known to exist in the repository "
1280                             "and is not part of the commit, "
1281                             "yet its child '%s' is part of the commit"),
1282                           /* Probably one or both of these is an entry, but
1283                              safest to local_stylize just in case. */
1284                           svn_dirent_local_style(dangling_parent, iterpool),
1285                           svn_dirent_local_style(dangling_child, iterpool));
1286        }
1287    }
1288
1289  svn_pool_destroy(iterpool);
1290
1291  return SVN_NO_ERROR;
1292}
1293
1294struct copy_committables_baton
1295{
1296  svn_client_ctx_t *ctx;
1297  svn_client__committables_t *committables;
1298  apr_pool_t *result_pool;
1299  svn_client__check_url_kind_t check_url_func;
1300  void *check_url_baton;
1301};
1302
1303static svn_error_t *
1304harvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
1305{
1306  struct copy_committables_baton *btn = baton;
1307  svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item;
1308  const char *repos_root_url;
1309  const char *commit_relpath;
1310  struct handle_descendants_baton hdb;
1311
1312  /* Read the entry for this SRC. */
1313  SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
1314
1315  SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL,
1316                                      btn->ctx->wc_ctx,
1317                                      pair->src_abspath_or_url,
1318                                      pool, pool));
1319
1320  commit_relpath = svn_uri_skip_ancestor(repos_root_url,
1321                                         pair->dst_abspath_or_url, pool);
1322
1323  /* Handle this SRC. */
1324  SVN_ERR(harvest_committables(pair->src_abspath_or_url,
1325                               btn->committables, NULL,
1326                               commit_relpath,
1327                               svn_depth_infinity,
1328                               FALSE,  /* JUST_LOCKED */
1329                               NULL /* changelists */,
1330                               NULL,
1331                               btn->check_url_func,
1332                               btn->check_url_baton,
1333                               btn->ctx->cancel_func,
1334                               btn->ctx->cancel_baton,
1335                               btn->ctx->notify_func2,
1336                               btn->ctx->notify_baton2,
1337                               btn->ctx->wc_ctx, btn->result_pool, pool));
1338
1339  hdb.wc_ctx = btn->ctx->wc_ctx;
1340  hdb.cancel_func = btn->ctx->cancel_func;
1341  hdb.cancel_baton = btn->ctx->cancel_baton;
1342  hdb.check_url_func = btn->check_url_func;
1343  hdb.check_url_baton = btn->check_url_baton;
1344  hdb.committables = btn->committables;
1345
1346  SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository,
1347                            handle_descendants, &hdb, pool));
1348
1349  return SVN_NO_ERROR;
1350}
1351
1352
1353
1354svn_error_t *
1355svn_client__get_copy_committables(svn_client__committables_t **committables,
1356                                  const apr_array_header_t *copy_pairs,
1357                                  svn_client__check_url_kind_t check_url_func,
1358                                  void *check_url_baton,
1359                                  svn_client_ctx_t *ctx,
1360                                  apr_pool_t *result_pool,
1361                                  apr_pool_t *scratch_pool)
1362{
1363  struct copy_committables_baton btn;
1364
1365  /* Create the COMMITTABLES structure. */
1366  create_committables(committables, result_pool);
1367
1368  btn.ctx = ctx;
1369  btn.committables = *committables;
1370  btn.result_pool = result_pool;
1371
1372  btn.check_url_func = check_url_func;
1373  btn.check_url_baton = check_url_baton;
1374
1375  /* For each copy pair, harvest the committables for that pair into the
1376     committables hash. */
1377  return svn_iter_apr_array(NULL, copy_pairs,
1378                            harvest_copy_committables, &btn, scratch_pool);
1379}
1380
1381
1382/* A svn_sort__array()/qsort()-compatible sort routine for sorting
1383   an array of svn_client_commit_item_t *'s by their URL member. */
1384static int
1385sort_commit_item_urls(const void *a, const void *b)
1386{
1387  const svn_client_commit_item3_t *item1
1388    = *((const svn_client_commit_item3_t * const *) a);
1389  const svn_client_commit_item3_t *item2
1390    = *((const svn_client_commit_item3_t * const *) b);
1391  return svn_path_compare_paths(item1->url, item2->url);
1392}
1393
1394
1395
1396svn_error_t *
1397svn_client__condense_commit_items(const char **base_url,
1398                                  apr_array_header_t *commit_items,
1399                                  apr_pool_t *pool)
1400{
1401  apr_array_header_t *ci = commit_items; /* convenience */
1402  const char *url;
1403  svn_client_commit_item3_t *item, *last_item = NULL;
1404  int i;
1405
1406  SVN_ERR_ASSERT(ci && ci->nelts);
1407
1408  /* Sort our commit items by their URLs. */
1409  svn_sort__array(ci, sort_commit_item_urls);
1410
1411  /* Loop through the URLs, finding the longest usable ancestor common
1412     to all of them, and making sure there are no duplicate URLs.  */
1413  for (i = 0; i < ci->nelts; i++)
1414    {
1415      item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1416      url = item->url;
1417
1418      if ((last_item) && (strcmp(last_item->url, url) == 0))
1419        return svn_error_createf
1420          (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1421           _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1422           svn_dirent_local_style(item->path, pool),
1423           svn_dirent_local_style(last_item->path, pool));
1424
1425      /* In the first iteration, our BASE_URL is just our only
1426         encountered commit URL to date.  After that, we find the
1427         longest ancestor between the current BASE_URL and the current
1428         commit URL.  */
1429      if (i == 0)
1430        *base_url = apr_pstrdup(pool, url);
1431      else
1432        *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1433
1434      /* If our BASE_URL is itself a to-be-committed item, and it is
1435         anything other than an already-versioned directory with
1436         property mods, we'll call its parent directory URL the
1437         BASE_URL.  Why?  Because we can't have a file URL as our base
1438         -- period -- and all other directory operations (removal,
1439         addition, etc.) require that we open that directory's parent
1440         dir first.  */
1441      /* ### I don't understand the strlen()s here, hmmm.  -kff */
1442      if ((strlen(*base_url) == strlen(url))
1443          && (! ((item->kind == svn_node_dir)
1444                 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1445        *base_url = svn_uri_dirname(*base_url, pool);
1446
1447      /* Stash our item here for the next iteration. */
1448      last_item = item;
1449    }
1450
1451  /* Now that we've settled on a *BASE_URL, go hack that base off
1452     of all of our URLs and store it as session_relpath. */
1453  for (i = 0; i < ci->nelts; i++)
1454    {
1455      svn_client_commit_item3_t *this_item
1456        = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1457
1458      this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1459                                                         this_item->url, pool);
1460    }
1461#ifdef SVN_CLIENT_COMMIT_DEBUG
1462  /* ### TEMPORARY CODE ### */
1463  SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1464  SVN_DBG(("   FLAGS     REV  REL-URL (COPY-URL)\n"));
1465  for (i = 0; i < ci->nelts; i++)
1466    {
1467      svn_client_commit_item3_t *this_item
1468        = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1469      char flags[6];
1470      flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1471                   ? 'a' : '-';
1472      flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1473                   ? 'd' : '-';
1474      flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1475                   ? 't' : '-';
1476      flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1477                   ? 'p' : '-';
1478      flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1479                   ? 'c' : '-';
1480      flags[5] = '\0';
1481      SVN_DBG(("   %s  %6ld  '%s' (%s)\n",
1482               flags,
1483               this_item->revision,
1484               this_item->url ? this_item->url : "",
1485               this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1486    }
1487#endif /* SVN_CLIENT_COMMIT_DEBUG */
1488
1489  return SVN_NO_ERROR;
1490}
1491
1492
1493struct file_mod_t
1494{
1495  const svn_client_commit_item3_t *item;
1496  void *file_baton;
1497  apr_pool_t *file_pool;
1498};
1499
1500
1501/* A baton for use while driving a path-based editor driver for commit */
1502struct item_commit_baton
1503{
1504  const svn_delta_editor_t *editor;    /* commit editor */
1505  void *edit_baton;                    /* commit editor's baton */
1506  apr_hash_t *file_mods;               /* hash: path->file_mod_t */
1507  const char *notify_path_prefix;      /* notification path prefix
1508                                          (NULL is okay, else abs path) */
1509  svn_client_ctx_t *ctx;               /* client context baton */
1510  apr_hash_t *commit_items;            /* the committables */
1511  const char *base_url;                /* The session url for the commit */
1512};
1513
1514
1515/* Drive CALLBACK_BATON->editor with the change described by the item in
1516 * CALLBACK_BATON->commit_items that is keyed by PATH.  If the change
1517 * includes a text mod, however, call the editor's file_open() function
1518 * but do not send the text mod to the editor; instead, add a mapping of
1519 * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1520 *
1521 * Before driving the editor, call the cancellation and notification
1522 * callbacks in CALLBACK_BATON->ctx, if present.
1523 *
1524 * This implements svn_delta_path_driver_cb_func_t. */
1525static svn_error_t *
1526do_item_commit(void **dir_baton,
1527               void *parent_baton,
1528               void *callback_baton,
1529               const char *path,
1530               apr_pool_t *pool)
1531{
1532  struct item_commit_baton *icb = callback_baton;
1533  const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1534                                                        path);
1535  svn_node_kind_t kind = item->kind;
1536  void *file_baton = NULL;
1537  apr_pool_t *file_pool = NULL;
1538  const svn_delta_editor_t *editor = icb->editor;
1539  apr_hash_t *file_mods = icb->file_mods;
1540  svn_client_ctx_t *ctx = icb->ctx;
1541  svn_error_t *err;
1542  const char *local_abspath = NULL;
1543
1544  /* Do some initializations. */
1545  *dir_baton = NULL;
1546  if (item->kind != svn_node_none && item->path)
1547    {
1548      /* We always get an absolute path, see svn_client_commit_item3_t. */
1549      SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1550      local_abspath = item->path;
1551    }
1552
1553  /* If this is a file with textual mods, we'll be keeping its baton
1554     around until the end of the commit.  So just lump its memory into
1555     a single, big, all-the-file-batons-in-here pool.  Otherwise, we
1556     can just use POOL, and trust our caller to clean that mess up. */
1557  if ((kind == svn_node_file)
1558      && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1559    file_pool = apr_hash_pool_get(file_mods);
1560  else
1561    file_pool = pool;
1562
1563  /* Subpools are cheap, but memory isn't */
1564  file_pool = svn_pool_create(file_pool);
1565
1566  /* Call the cancellation function. */
1567  if (ctx->cancel_func)
1568    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1569
1570  /* Validation. */
1571  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1572    {
1573      if (! item->copyfrom_url)
1574        return svn_error_createf
1575          (SVN_ERR_BAD_URL, NULL,
1576           _("Commit item '%s' has copy flag but no copyfrom URL"),
1577           svn_dirent_local_style(path, pool));
1578      if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1579        return svn_error_createf
1580          (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1581           _("Commit item '%s' has copy flag but an invalid revision"),
1582           svn_dirent_local_style(path, pool));
1583    }
1584
1585  /* If a feedback table was supplied by the application layer,
1586     describe what we're about to do to this item. */
1587  if (ctx->notify_func2 && item->path)
1588    {
1589      const char *npath = item->path;
1590      svn_wc_notify_t *notify;
1591
1592      if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1593          && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1594        {
1595          /* We don't print the "(bin)" notice for binary files when
1596             replacing, only when adding.  So we don't bother to get
1597             the mime-type here. */
1598          if (item->copyfrom_url)
1599            notify = svn_wc_create_notify(npath,
1600                                          svn_wc_notify_commit_copied_replaced,
1601                                          pool);
1602          else
1603            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1604                                          pool);
1605
1606        }
1607      else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1608        {
1609          notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1610                                        pool);
1611        }
1612      else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1613        {
1614          if (item->copyfrom_url)
1615            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1616                                          pool);
1617          else
1618            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1619                                          pool);
1620
1621          if (item->kind == svn_node_file)
1622            {
1623              const svn_string_t *propval;
1624
1625              SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1626                                       SVN_PROP_MIME_TYPE, pool, pool));
1627
1628              if (propval)
1629                notify->mime_type = propval->data;
1630            }
1631        }
1632      else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1633               || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1634        {
1635          notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1636                                        pool);
1637          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1638            notify->content_state = svn_wc_notify_state_changed;
1639          else
1640            notify->content_state = svn_wc_notify_state_unchanged;
1641          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1642            notify->prop_state = svn_wc_notify_state_changed;
1643          else
1644            notify->prop_state = svn_wc_notify_state_unchanged;
1645        }
1646      else
1647        notify = NULL;
1648
1649
1650      if (notify)
1651        {
1652          notify->kind = item->kind;
1653          notify->path_prefix = icb->notify_path_prefix;
1654          ctx->notify_func2(ctx->notify_baton2, notify, pool);
1655        }
1656    }
1657
1658  /* If this item is supposed to be deleted, do so. */
1659  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1660    {
1661      SVN_ERR_ASSERT(parent_baton);
1662      err = editor->delete_entry(path, item->revision,
1663                                 parent_baton, pool);
1664
1665      if (err)
1666        goto fixup_error;
1667    }
1668
1669  /* If this item is supposed to be added, do so. */
1670  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1671    {
1672      if (kind == svn_node_file)
1673        {
1674          SVN_ERR_ASSERT(parent_baton);
1675          err = editor->add_file(
1676                   path, parent_baton, item->copyfrom_url,
1677                   item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1678                   file_pool, &file_baton);
1679        }
1680      else /* May be svn_node_none when adding parent dirs for a copy. */
1681        {
1682          SVN_ERR_ASSERT(parent_baton);
1683          err = editor->add_directory(
1684                   path, parent_baton, item->copyfrom_url,
1685                   item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1686                   pool, dir_baton);
1687        }
1688
1689      if (err)
1690        goto fixup_error;
1691
1692      /* Set other prop-changes, if available in the baton */
1693      if (item->outgoing_prop_changes)
1694        {
1695          svn_prop_t *prop;
1696          apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1697          int ctr;
1698          for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1699            {
1700              prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1701              if (kind == svn_node_file)
1702                {
1703                  err = editor->change_file_prop(file_baton, prop->name,
1704                                                 prop->value, pool);
1705                }
1706              else
1707                {
1708                  err = editor->change_dir_prop(*dir_baton, prop->name,
1709                                                prop->value, pool);
1710                }
1711
1712              if (err)
1713                goto fixup_error;
1714            }
1715        }
1716    }
1717
1718  /* Now handle property mods. */
1719  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1720    {
1721      if (kind == svn_node_file)
1722        {
1723          if (! file_baton)
1724            {
1725              SVN_ERR_ASSERT(parent_baton);
1726              err = editor->open_file(path, parent_baton,
1727                                      item->revision,
1728                                      file_pool, &file_baton);
1729
1730              if (err)
1731                goto fixup_error;
1732            }
1733        }
1734      else
1735        {
1736          if (! *dir_baton)
1737            {
1738              if (! parent_baton)
1739                {
1740                  err = editor->open_root(icb->edit_baton, item->revision,
1741                                          pool, dir_baton);
1742                }
1743              else
1744                {
1745                  err = editor->open_directory(path, parent_baton,
1746                                               item->revision,
1747                                               pool, dir_baton);
1748                }
1749
1750              if (err)
1751                goto fixup_error;
1752            }
1753        }
1754
1755      /* When committing a directory that no longer exists in the
1756         repository, a "not found" error does not occur immediately
1757         upon opening the directory.  It appears here during the delta
1758         transmisssion. */
1759      err = svn_wc_transmit_prop_deltas2(
1760              ctx->wc_ctx, local_abspath, editor,
1761              (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1762
1763      if (err)
1764        goto fixup_error;
1765
1766      /* Make any additional client -> repository prop changes. */
1767      if (item->outgoing_prop_changes)
1768        {
1769          svn_prop_t *prop;
1770          int i;
1771
1772          for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1773            {
1774              prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1775                                   svn_prop_t *);
1776              if (kind == svn_node_file)
1777                {
1778                  err = editor->change_file_prop(file_baton, prop->name,
1779                                           prop->value, pool);
1780                }
1781              else
1782                {
1783                  err = editor->change_dir_prop(*dir_baton, prop->name,
1784                                          prop->value, pool);
1785                }
1786
1787              if (err)
1788                goto fixup_error;
1789            }
1790        }
1791    }
1792
1793  /* Finally, handle text mods (in that we need to open a file if it
1794     hasn't already been opened, and we need to put the file baton in
1795     our FILES hash). */
1796  if ((kind == svn_node_file)
1797      && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1798    {
1799      struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1800
1801      if (! file_baton)
1802        {
1803          SVN_ERR_ASSERT(parent_baton);
1804          err = editor->open_file(path, parent_baton,
1805                                    item->revision,
1806                                    file_pool, &file_baton);
1807
1808          if (err)
1809            goto fixup_error;
1810        }
1811
1812      /* Add this file mod to the FILE_MODS hash. */
1813      mod->item = item;
1814      mod->file_baton = file_baton;
1815      mod->file_pool = file_pool;
1816      svn_hash_sets(file_mods, item->session_relpath, mod);
1817    }
1818  else if (file_baton)
1819    {
1820      /* Close any outstanding file batons that didn't get caught by
1821         the "has local mods" conditional above. */
1822      err = editor->close_file(file_baton, NULL, file_pool);
1823      svn_pool_destroy(file_pool);
1824      if (err)
1825        goto fixup_error;
1826    }
1827
1828  return SVN_NO_ERROR;
1829
1830fixup_error:
1831  return svn_error_trace(fixup_commit_error(local_abspath,
1832                                            icb->base_url,
1833                                            path, kind,
1834                                            err, ctx, pool));
1835}
1836
1837svn_error_t *
1838svn_client__do_commit(const char *base_url,
1839                      const apr_array_header_t *commit_items,
1840                      const svn_delta_editor_t *editor,
1841                      void *edit_baton,
1842                      const char *notify_path_prefix,
1843                      apr_hash_t **sha1_checksums,
1844                      svn_client_ctx_t *ctx,
1845                      apr_pool_t *result_pool,
1846                      apr_pool_t *scratch_pool)
1847{
1848  apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1849  apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1850  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1851  apr_hash_index_t *hi;
1852  int i;
1853  struct item_commit_baton cb_baton;
1854  apr_array_header_t *paths =
1855    apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1856
1857  /* Ditto for the checksums. */
1858  if (sha1_checksums)
1859    *sha1_checksums = apr_hash_make(result_pool);
1860
1861  /* Build a hash from our COMMIT_ITEMS array, keyed on the
1862     relative paths (which come from the item URLs).  And
1863     keep an array of those decoded paths, too.  */
1864  for (i = 0; i < commit_items->nelts; i++)
1865    {
1866      svn_client_commit_item3_t *item =
1867        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1868      const char *path = item->session_relpath;
1869      svn_hash_sets(items_hash, path, item);
1870      APR_ARRAY_PUSH(paths, const char *) = path;
1871    }
1872
1873  /* Setup the callback baton. */
1874  cb_baton.editor = editor;
1875  cb_baton.edit_baton = edit_baton;
1876  cb_baton.file_mods = file_mods;
1877  cb_baton.notify_path_prefix = notify_path_prefix;
1878  cb_baton.ctx = ctx;
1879  cb_baton.commit_items = items_hash;
1880  cb_baton.base_url = base_url;
1881
1882  /* Drive the commit editor! */
1883  SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1884                                 do_item_commit, &cb_baton, scratch_pool));
1885
1886  /* Transmit outstanding text deltas. */
1887  for (hi = apr_hash_first(scratch_pool, file_mods);
1888       hi;
1889       hi = apr_hash_next(hi))
1890    {
1891      struct file_mod_t *mod = apr_hash_this_val(hi);
1892      const svn_client_commit_item3_t *item = mod->item;
1893      const svn_checksum_t *new_text_base_md5_checksum;
1894      const svn_checksum_t *new_text_base_sha1_checksum;
1895      svn_boolean_t fulltext = FALSE;
1896      svn_error_t *err;
1897
1898      svn_pool_clear(iterpool);
1899
1900      /* Transmit the entry. */
1901      if (ctx->cancel_func)
1902        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1903
1904      if (ctx->notify_func2)
1905        {
1906          svn_wc_notify_t *notify;
1907          notify = svn_wc_create_notify(item->path,
1908                                        svn_wc_notify_commit_postfix_txdelta,
1909                                        iterpool);
1910          notify->kind = svn_node_file;
1911          notify->path_prefix = notify_path_prefix;
1912          ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1913        }
1914
1915      /* If the node has no history, transmit full text */
1916      if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1917          && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1918        fulltext = TRUE;
1919
1920      err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1921                                         &new_text_base_sha1_checksum,
1922                                         ctx->wc_ctx, item->path,
1923                                         fulltext, editor, mod->file_baton,
1924                                         result_pool, iterpool);
1925
1926      if (err)
1927        {
1928          svn_pool_destroy(iterpool); /* Close tempfiles */
1929          return svn_error_trace(fixup_commit_error(item->path,
1930                                                    base_url,
1931                                                    item->session_relpath,
1932                                                    svn_node_file,
1933                                                    err, ctx, scratch_pool));
1934        }
1935
1936      if (sha1_checksums)
1937        svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1938
1939      svn_pool_destroy(mod->file_pool);
1940    }
1941
1942  if (ctx->notify_func2)
1943    {
1944      svn_wc_notify_t *notify;
1945      notify = svn_wc_create_notify_url(base_url,
1946                                        svn_wc_notify_commit_finalizing,
1947                                        iterpool);
1948      ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1949    }
1950
1951  svn_pool_destroy(iterpool);
1952
1953  /* Close the edit. */
1954  return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1955}
1956
1957
1958svn_error_t *
1959svn_client__get_log_msg(const char **log_msg,
1960                        const char **tmp_file,
1961                        const apr_array_header_t *commit_items,
1962                        svn_client_ctx_t *ctx,
1963                        apr_pool_t *pool)
1964{
1965  if (ctx->log_msg_func3)
1966    {
1967      /* The client provided a callback function for the current API.
1968         Forward the call to it directly. */
1969      return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1970                                   ctx->log_msg_baton3, pool);
1971    }
1972  else if (ctx->log_msg_func2 || ctx->log_msg_func)
1973    {
1974      /* The client provided a pre-1.5 (or pre-1.3) API callback
1975         function.  Convert the commit_items list to the appropriate
1976         type, and forward call to it. */
1977      svn_error_t *err;
1978      apr_pool_t *scratch_pool = svn_pool_create(pool);
1979      apr_array_header_t *old_commit_items =
1980        apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
1981
1982      int i;
1983      for (i = 0; i < commit_items->nelts; i++)
1984        {
1985          svn_client_commit_item3_t *item =
1986            APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1987
1988          if (ctx->log_msg_func2)
1989            {
1990              svn_client_commit_item2_t *old_item =
1991                apr_pcalloc(scratch_pool, sizeof(*old_item));
1992
1993              old_item->path = item->path;
1994              old_item->kind = item->kind;
1995              old_item->url = item->url;
1996              old_item->revision = item->revision;
1997              old_item->copyfrom_url = item->copyfrom_url;
1998              old_item->copyfrom_rev = item->copyfrom_rev;
1999              old_item->state_flags = item->state_flags;
2000              old_item->wcprop_changes = item->incoming_prop_changes;
2001
2002              APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
2003                old_item;
2004            }
2005          else /* ctx->log_msg_func */
2006            {
2007              svn_client_commit_item_t *old_item =
2008                apr_pcalloc(scratch_pool, sizeof(*old_item));
2009
2010              old_item->path = item->path;
2011              old_item->kind = item->kind;
2012              old_item->url = item->url;
2013              /* The pre-1.3 API used the revision field for copyfrom_rev
2014                 and revision depeding of copyfrom_url. */
2015              old_item->revision = item->copyfrom_url ?
2016                item->copyfrom_rev : item->revision;
2017              old_item->copyfrom_url = item->copyfrom_url;
2018              old_item->state_flags = item->state_flags;
2019              old_item->wcprop_changes = item->incoming_prop_changes;
2020
2021              APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
2022                old_item;
2023            }
2024        }
2025
2026      if (ctx->log_msg_func2)
2027        err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
2028                                    ctx->log_msg_baton2, pool);
2029      else
2030        err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
2031                                   ctx->log_msg_baton, pool);
2032      svn_pool_destroy(scratch_pool);
2033      return err;
2034    }
2035  else
2036    {
2037      /* No log message callback was provided by the client. */
2038      *log_msg = "";
2039      *tmp_file = NULL;
2040      return SVN_NO_ERROR;
2041    }
2042}
2043
2044svn_error_t *
2045svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
2046                                 const apr_hash_t *revprop_table_in,
2047                                 const char *log_msg,
2048                                 svn_client_ctx_t *ctx,
2049                                 apr_pool_t *pool)
2050{
2051  apr_hash_t *new_revprop_table;
2052  if (revprop_table_in)
2053    {
2054      if (svn_prop_has_svn_prop(revprop_table_in, pool))
2055        return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2056                                _("Standard properties can't be set "
2057                                  "explicitly as revision properties"));
2058      new_revprop_table = apr_hash_copy(pool, revprop_table_in);
2059    }
2060  else
2061    {
2062      new_revprop_table = apr_hash_make(pool);
2063    }
2064  svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
2065                svn_string_create(log_msg, pool));
2066  *revprop_table_out = new_revprop_table;
2067  return SVN_NO_ERROR;
2068}
2069