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