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