]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_client/commit_util.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_client / commit_util.c
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. */
55 static svn_error_t *
56 fixup_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 \f
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  */
190 static svn_error_t *
191 add_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. */
269 static svn_client_commit_item3_t *
270 look_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. */
281 static svn_error_t *
282 bail_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
369 struct 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
391 static svn_error_t *
392 harvest_status_callback(void *status_baton,
393                         const char *local_abspath,
394                         const svn_wc_status3_t *status,
395                         apr_pool_t *scratch_pool);
396
397 static svn_error_t *
398 harvest_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
452 static svn_error_t *
453 harvest_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 */
545 static svn_error_t *
546 harvest_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 */
962 struct 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 */
973 static svn_error_t *
974 handle_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  */
1120 static void
1121 create_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
1130 svn_error_t *
1131 svn_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
1272 struct 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
1281 static svn_error_t *
1282 harvest_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
1332 svn_error_t *
1333 svn_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
1360 int 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
1371 svn_error_t *
1372 svn_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
1469 struct 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 */
1477 struct 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. */
1500 static svn_error_t *
1501 do_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
1800 fixup_error:
1801   return svn_error_trace(fixup_commit_error(local_abspath,
1802                                             icb->base_url,
1803                                             path, kind,
1804                                             err, ctx, pool));
1805 }
1806
1807 svn_error_t *
1808 svn_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
1917 svn_error_t *
1918 svn_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
2003 svn_error_t *
2004 svn_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 }