]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/commit_util.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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
1396 svn_error_t *
1397 svn_client__condense_commit_items(const char **base_url,
1398                                   apr_array_header_t *commit_items,
1399                                   apr_pool_t *pool)
1400 {
1401   apr_array_header_t *ci = commit_items; /* convenience */
1402   const char *url;
1403   svn_client_commit_item3_t *item, *last_item = NULL;
1404   int i;
1405
1406   SVN_ERR_ASSERT(ci && ci->nelts);
1407
1408   /* Sort our commit items by their URLs. */
1409   svn_sort__array(ci, sort_commit_item_urls);
1410
1411   /* Loop through the URLs, finding the longest usable ancestor common
1412      to all of them, and making sure there are no duplicate URLs.  */
1413   for (i = 0; i < ci->nelts; i++)
1414     {
1415       item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1416       url = item->url;
1417
1418       if ((last_item) && (strcmp(last_item->url, url) == 0))
1419         return svn_error_createf
1420           (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1421            _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1422            svn_dirent_local_style(item->path, pool),
1423            svn_dirent_local_style(last_item->path, pool));
1424
1425       /* In the first iteration, our BASE_URL is just our only
1426          encountered commit URL to date.  After that, we find the
1427          longest ancestor between the current BASE_URL and the current
1428          commit URL.  */
1429       if (i == 0)
1430         *base_url = apr_pstrdup(pool, url);
1431       else
1432         *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1433
1434       /* If our BASE_URL is itself a to-be-committed item, and it is
1435          anything other than an already-versioned directory with
1436          property mods, we'll call its parent directory URL the
1437          BASE_URL.  Why?  Because we can't have a file URL as our base
1438          -- period -- and all other directory operations (removal,
1439          addition, etc.) require that we open that directory's parent
1440          dir first.  */
1441       /* ### I don't understand the strlen()s here, hmmm.  -kff */
1442       if ((strlen(*base_url) == strlen(url))
1443           && (! ((item->kind == svn_node_dir)
1444                  && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1445         *base_url = svn_uri_dirname(*base_url, pool);
1446
1447       /* Stash our item here for the next iteration. */
1448       last_item = item;
1449     }
1450
1451   /* Now that we've settled on a *BASE_URL, go hack that base off
1452      of all of our URLs and store it as session_relpath. */
1453   for (i = 0; i < ci->nelts; i++)
1454     {
1455       svn_client_commit_item3_t *this_item
1456         = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1457
1458       this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1459                                                          this_item->url, pool);
1460     }
1461 #ifdef SVN_CLIENT_COMMIT_DEBUG
1462   /* ### TEMPORARY CODE ### */
1463   SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1464   SVN_DBG(("   FLAGS     REV  REL-URL (COPY-URL)\n"));
1465   for (i = 0; i < ci->nelts; i++)
1466     {
1467       svn_client_commit_item3_t *this_item
1468         = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1469       char flags[6];
1470       flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1471                    ? 'a' : '-';
1472       flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1473                    ? 'd' : '-';
1474       flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1475                    ? 't' : '-';
1476       flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1477                    ? 'p' : '-';
1478       flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1479                    ? 'c' : '-';
1480       flags[5] = '\0';
1481       SVN_DBG(("   %s  %6ld  '%s' (%s)\n",
1482                flags,
1483                this_item->revision,
1484                this_item->url ? this_item->url : "",
1485                this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1486     }
1487 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1488
1489   return SVN_NO_ERROR;
1490 }
1491
1492
1493 struct file_mod_t
1494 {
1495   const svn_client_commit_item3_t *item;
1496   void *file_baton;
1497   apr_pool_t *file_pool;
1498 };
1499
1500
1501 /* A baton for use while driving a path-based editor driver for commit */
1502 struct item_commit_baton
1503 {
1504   const svn_delta_editor_t *editor;    /* commit editor */
1505   void *edit_baton;                    /* commit editor's baton */
1506   apr_hash_t *file_mods;               /* hash: path->file_mod_t */
1507   const char *notify_path_prefix;      /* notification path prefix
1508                                           (NULL is okay, else abs path) */
1509   svn_client_ctx_t *ctx;               /* client context baton */
1510   apr_hash_t *commit_items;            /* the committables */
1511   const char *base_url;                /* The session url for the commit */
1512 };
1513
1514
1515 /* Drive CALLBACK_BATON->editor with the change described by the item in
1516  * CALLBACK_BATON->commit_items that is keyed by PATH.  If the change
1517  * includes a text mod, however, call the editor's file_open() function
1518  * but do not send the text mod to the editor; instead, add a mapping of
1519  * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1520  *
1521  * Before driving the editor, call the cancellation and notification
1522  * callbacks in CALLBACK_BATON->ctx, if present.
1523  *
1524  * This implements svn_delta_path_driver_cb_func_t. */
1525 static svn_error_t *
1526 do_item_commit(void **dir_baton,
1527                void *parent_baton,
1528                void *callback_baton,
1529                const char *path,
1530                apr_pool_t *pool)
1531 {
1532   struct item_commit_baton *icb = callback_baton;
1533   const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1534                                                         path);
1535   svn_node_kind_t kind = item->kind;
1536   void *file_baton = NULL;
1537   apr_pool_t *file_pool = NULL;
1538   const svn_delta_editor_t *editor = icb->editor;
1539   apr_hash_t *file_mods = icb->file_mods;
1540   svn_client_ctx_t *ctx = icb->ctx;
1541   svn_error_t *err;
1542   const char *local_abspath = NULL;
1543
1544   /* Do some initializations. */
1545   *dir_baton = NULL;
1546   if (item->kind != svn_node_none && item->path)
1547     {
1548       /* We always get an absolute path, see svn_client_commit_item3_t. */
1549       SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1550       local_abspath = item->path;
1551     }
1552
1553   /* If this is a file with textual mods, we'll be keeping its baton
1554      around until the end of the commit.  So just lump its memory into
1555      a single, big, all-the-file-batons-in-here pool.  Otherwise, we
1556      can just use POOL, and trust our caller to clean that mess up. */
1557   if ((kind == svn_node_file)
1558       && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1559     file_pool = apr_hash_pool_get(file_mods);
1560   else
1561     file_pool = pool;
1562
1563   /* Subpools are cheap, but memory isn't */
1564   file_pool = svn_pool_create(file_pool);
1565
1566   /* Call the cancellation function. */
1567   if (ctx->cancel_func)
1568     SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1569
1570   /* Validation. */
1571   if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1572     {
1573       if (! item->copyfrom_url)
1574         return svn_error_createf
1575           (SVN_ERR_BAD_URL, NULL,
1576            _("Commit item '%s' has copy flag but no copyfrom URL"),
1577            svn_dirent_local_style(path, pool));
1578       if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1579         return svn_error_createf
1580           (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1581            _("Commit item '%s' has copy flag but an invalid revision"),
1582            svn_dirent_local_style(path, pool));
1583     }
1584
1585   /* If a feedback table was supplied by the application layer,
1586      describe what we're about to do to this item. */
1587   if (ctx->notify_func2 && item->path)
1588     {
1589       const char *npath = item->path;
1590       svn_wc_notify_t *notify;
1591
1592       if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1593           && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1594         {
1595           /* We don't print the "(bin)" notice for binary files when
1596              replacing, only when adding.  So we don't bother to get
1597              the mime-type here. */
1598           if (item->copyfrom_url)
1599             notify = svn_wc_create_notify(npath,
1600                                           svn_wc_notify_commit_copied_replaced,
1601                                           pool);
1602           else
1603             notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1604                                           pool);
1605
1606         }
1607       else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1608         {
1609           notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1610                                         pool);
1611         }
1612       else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1613         {
1614           if (item->copyfrom_url)
1615             notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1616                                           pool);
1617           else
1618             notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1619                                           pool);
1620
1621           if (item->kind == svn_node_file)
1622             {
1623               const svn_string_t *propval;
1624
1625               SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1626                                        SVN_PROP_MIME_TYPE, pool, pool));
1627
1628               if (propval)
1629                 notify->mime_type = propval->data;
1630             }
1631         }
1632       else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1633                || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1634         {
1635           notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1636                                         pool);
1637           if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1638             notify->content_state = svn_wc_notify_state_changed;
1639           else
1640             notify->content_state = svn_wc_notify_state_unchanged;
1641           if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1642             notify->prop_state = svn_wc_notify_state_changed;
1643           else
1644             notify->prop_state = svn_wc_notify_state_unchanged;
1645         }
1646       else
1647         notify = NULL;
1648
1649
1650       if (notify)
1651         {
1652           notify->kind = item->kind;
1653           notify->path_prefix = icb->notify_path_prefix;
1654           ctx->notify_func2(ctx->notify_baton2, notify, pool);
1655         }
1656     }
1657
1658   /* If this item is supposed to be deleted, do so. */
1659   if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1660     {
1661       SVN_ERR_ASSERT(parent_baton);
1662       err = editor->delete_entry(path, item->revision,
1663                                  parent_baton, pool);
1664
1665       if (err)
1666         goto fixup_error;
1667     }
1668
1669   /* If this item is supposed to be added, do so. */
1670   if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1671     {
1672       if (kind == svn_node_file)
1673         {
1674           SVN_ERR_ASSERT(parent_baton);
1675           err = editor->add_file(
1676                    path, parent_baton, item->copyfrom_url,
1677                    item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1678                    file_pool, &file_baton);
1679         }
1680       else /* May be svn_node_none when adding parent dirs for a copy. */
1681         {
1682           SVN_ERR_ASSERT(parent_baton);
1683           err = editor->add_directory(
1684                    path, parent_baton, item->copyfrom_url,
1685                    item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1686                    pool, dir_baton);
1687         }
1688
1689       if (err)
1690         goto fixup_error;
1691
1692       /* Set other prop-changes, if available in the baton */
1693       if (item->outgoing_prop_changes)
1694         {
1695           svn_prop_t *prop;
1696           apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1697           int ctr;
1698           for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1699             {
1700               prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1701               if (kind == svn_node_file)
1702                 {
1703                   err = editor->change_file_prop(file_baton, prop->name,
1704                                                  prop->value, pool);
1705                 }
1706               else
1707                 {
1708                   err = editor->change_dir_prop(*dir_baton, prop->name,
1709                                                 prop->value, pool);
1710                 }
1711
1712               if (err)
1713                 goto fixup_error;
1714             }
1715         }
1716     }
1717
1718   /* Now handle property mods. */
1719   if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1720     {
1721       if (kind == svn_node_file)
1722         {
1723           if (! file_baton)
1724             {
1725               SVN_ERR_ASSERT(parent_baton);
1726               err = editor->open_file(path, parent_baton,
1727                                       item->revision,
1728                                       file_pool, &file_baton);
1729
1730               if (err)
1731                 goto fixup_error;
1732             }
1733         }
1734       else
1735         {
1736           if (! *dir_baton)
1737             {
1738               if (! parent_baton)
1739                 {
1740                   err = editor->open_root(icb->edit_baton, item->revision,
1741                                           pool, dir_baton);
1742                 }
1743               else
1744                 {
1745                   err = editor->open_directory(path, parent_baton,
1746                                                item->revision,
1747                                                pool, dir_baton);
1748                 }
1749
1750               if (err)
1751                 goto fixup_error;
1752             }
1753         }
1754
1755       /* When committing a directory that no longer exists in the
1756          repository, a "not found" error does not occur immediately
1757          upon opening the directory.  It appears here during the delta
1758          transmisssion. */
1759       err = svn_wc_transmit_prop_deltas2(
1760               ctx->wc_ctx, local_abspath, editor,
1761               (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1762
1763       if (err)
1764         goto fixup_error;
1765
1766       /* Make any additional client -> repository prop changes. */
1767       if (item->outgoing_prop_changes)
1768         {
1769           svn_prop_t *prop;
1770           int i;
1771
1772           for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1773             {
1774               prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1775                                    svn_prop_t *);
1776               if (kind == svn_node_file)
1777                 {
1778                   err = editor->change_file_prop(file_baton, prop->name,
1779                                            prop->value, pool);
1780                 }
1781               else
1782                 {
1783                   err = editor->change_dir_prop(*dir_baton, prop->name,
1784                                           prop->value, pool);
1785                 }
1786
1787               if (err)
1788                 goto fixup_error;
1789             }
1790         }
1791     }
1792
1793   /* Finally, handle text mods (in that we need to open a file if it
1794      hasn't already been opened, and we need to put the file baton in
1795      our FILES hash). */
1796   if ((kind == svn_node_file)
1797       && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1798     {
1799       struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1800
1801       if (! file_baton)
1802         {
1803           SVN_ERR_ASSERT(parent_baton);
1804           err = editor->open_file(path, parent_baton,
1805                                     item->revision,
1806                                     file_pool, &file_baton);
1807
1808           if (err)
1809             goto fixup_error;
1810         }
1811
1812       /* Add this file mod to the FILE_MODS hash. */
1813       mod->item = item;
1814       mod->file_baton = file_baton;
1815       mod->file_pool = file_pool;
1816       svn_hash_sets(file_mods, item->session_relpath, mod);
1817     }
1818   else if (file_baton)
1819     {
1820       /* Close any outstanding file batons that didn't get caught by
1821          the "has local mods" conditional above. */
1822       err = editor->close_file(file_baton, NULL, file_pool);
1823       svn_pool_destroy(file_pool);
1824       if (err)
1825         goto fixup_error;
1826     }
1827
1828   return SVN_NO_ERROR;
1829
1830 fixup_error:
1831   return svn_error_trace(fixup_commit_error(local_abspath,
1832                                             icb->base_url,
1833                                             path, kind,
1834                                             err, ctx, pool));
1835 }
1836
1837 svn_error_t *
1838 svn_client__do_commit(const char *base_url,
1839                       const apr_array_header_t *commit_items,
1840                       const svn_delta_editor_t *editor,
1841                       void *edit_baton,
1842                       const char *notify_path_prefix,
1843                       apr_hash_t **sha1_checksums,
1844                       svn_client_ctx_t *ctx,
1845                       apr_pool_t *result_pool,
1846                       apr_pool_t *scratch_pool)
1847 {
1848   apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1849   apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1850   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1851   apr_hash_index_t *hi;
1852   int i;
1853   struct item_commit_baton cb_baton;
1854   apr_array_header_t *paths =
1855     apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1856
1857   /* Ditto for the checksums. */
1858   if (sha1_checksums)
1859     *sha1_checksums = apr_hash_make(result_pool);
1860
1861   /* Build a hash from our COMMIT_ITEMS array, keyed on the
1862      relative paths (which come from the item URLs).  And
1863      keep an array of those decoded paths, too.  */
1864   for (i = 0; i < commit_items->nelts; i++)
1865     {
1866       svn_client_commit_item3_t *item =
1867         APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1868       const char *path = item->session_relpath;
1869       svn_hash_sets(items_hash, path, item);
1870       APR_ARRAY_PUSH(paths, const char *) = path;
1871     }
1872
1873   /* Setup the callback baton. */
1874   cb_baton.editor = editor;
1875   cb_baton.edit_baton = edit_baton;
1876   cb_baton.file_mods = file_mods;
1877   cb_baton.notify_path_prefix = notify_path_prefix;
1878   cb_baton.ctx = ctx;
1879   cb_baton.commit_items = items_hash;
1880   cb_baton.base_url = base_url;
1881
1882   /* Drive the commit editor! */
1883   SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1884                                  do_item_commit, &cb_baton, scratch_pool));
1885
1886   /* Transmit outstanding text deltas. */
1887   for (hi = apr_hash_first(scratch_pool, file_mods);
1888        hi;
1889        hi = apr_hash_next(hi))
1890     {
1891       struct file_mod_t *mod = apr_hash_this_val(hi);
1892       const svn_client_commit_item3_t *item = mod->item;
1893       const svn_checksum_t *new_text_base_md5_checksum;
1894       const svn_checksum_t *new_text_base_sha1_checksum;
1895       svn_boolean_t fulltext = FALSE;
1896       svn_error_t *err;
1897
1898       svn_pool_clear(iterpool);
1899
1900       /* Transmit the entry. */
1901       if (ctx->cancel_func)
1902         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1903
1904       if (ctx->notify_func2)
1905         {
1906           svn_wc_notify_t *notify;
1907           notify = svn_wc_create_notify(item->path,
1908                                         svn_wc_notify_commit_postfix_txdelta,
1909                                         iterpool);
1910           notify->kind = svn_node_file;
1911           notify->path_prefix = notify_path_prefix;
1912           ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1913         }
1914
1915       /* If the node has no history, transmit full text */
1916       if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1917           && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1918         fulltext = TRUE;
1919
1920       err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1921                                          &new_text_base_sha1_checksum,
1922                                          ctx->wc_ctx, item->path,
1923                                          fulltext, editor, mod->file_baton,
1924                                          result_pool, iterpool);
1925
1926       if (err)
1927         {
1928           svn_pool_destroy(iterpool); /* Close tempfiles */
1929           return svn_error_trace(fixup_commit_error(item->path,
1930                                                     base_url,
1931                                                     item->session_relpath,
1932                                                     svn_node_file,
1933                                                     err, ctx, scratch_pool));
1934         }
1935
1936       if (sha1_checksums)
1937         svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1938
1939       svn_pool_destroy(mod->file_pool);
1940     }
1941
1942   if (ctx->notify_func2)
1943     {
1944       svn_wc_notify_t *notify;
1945       notify = svn_wc_create_notify_url(base_url,
1946                                         svn_wc_notify_commit_finalizing,
1947                                         iterpool);
1948       ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1949     }
1950
1951   svn_pool_destroy(iterpool);
1952
1953   /* Close the edit. */
1954   return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1955 }
1956
1957
1958 svn_error_t *
1959 svn_client__get_log_msg(const char **log_msg,
1960                         const char **tmp_file,
1961                         const apr_array_header_t *commit_items,
1962                         svn_client_ctx_t *ctx,
1963                         apr_pool_t *pool)
1964 {
1965   if (ctx->log_msg_func3)
1966     {
1967       /* The client provided a callback function for the current API.
1968          Forward the call to it directly. */
1969       return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1970                                    ctx->log_msg_baton3, pool);
1971     }
1972   else if (ctx->log_msg_func2 || ctx->log_msg_func)
1973     {
1974       /* The client provided a pre-1.5 (or pre-1.3) API callback
1975          function.  Convert the commit_items list to the appropriate
1976          type, and forward call to it. */
1977       svn_error_t *err;
1978       apr_pool_t *scratch_pool = svn_pool_create(pool);
1979       apr_array_header_t *old_commit_items =
1980         apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
1981
1982       int i;
1983       for (i = 0; i < commit_items->nelts; i++)
1984         {
1985           svn_client_commit_item3_t *item =
1986             APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1987
1988           if (ctx->log_msg_func2)
1989             {
1990               svn_client_commit_item2_t *old_item =
1991                 apr_pcalloc(scratch_pool, sizeof(*old_item));
1992
1993               old_item->path = item->path;
1994               old_item->kind = item->kind;
1995               old_item->url = item->url;
1996               old_item->revision = item->revision;
1997               old_item->copyfrom_url = item->copyfrom_url;
1998               old_item->copyfrom_rev = item->copyfrom_rev;
1999               old_item->state_flags = item->state_flags;
2000               old_item->wcprop_changes = item->incoming_prop_changes;
2001
2002               APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
2003                 old_item;
2004             }
2005           else /* ctx->log_msg_func */
2006             {
2007               svn_client_commit_item_t *old_item =
2008                 apr_pcalloc(scratch_pool, sizeof(*old_item));
2009
2010               old_item->path = item->path;
2011               old_item->kind = item->kind;
2012               old_item->url = item->url;
2013               /* The pre-1.3 API used the revision field for copyfrom_rev
2014                  and revision depeding of copyfrom_url. */
2015               old_item->revision = item->copyfrom_url ?
2016                 item->copyfrom_rev : item->revision;
2017               old_item->copyfrom_url = item->copyfrom_url;
2018               old_item->state_flags = item->state_flags;
2019               old_item->wcprop_changes = item->incoming_prop_changes;
2020
2021               APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
2022                 old_item;
2023             }
2024         }
2025
2026       if (ctx->log_msg_func2)
2027         err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
2028                                     ctx->log_msg_baton2, pool);
2029       else
2030         err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
2031                                    ctx->log_msg_baton, pool);
2032       svn_pool_destroy(scratch_pool);
2033       return err;
2034     }
2035   else
2036     {
2037       /* No log message callback was provided by the client. */
2038       *log_msg = "";
2039       *tmp_file = NULL;
2040       return SVN_NO_ERROR;
2041     }
2042 }
2043
2044 svn_error_t *
2045 svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
2046                                  const apr_hash_t *revprop_table_in,
2047                                  const char *log_msg,
2048                                  svn_client_ctx_t *ctx,
2049                                  apr_pool_t *pool)
2050 {
2051   apr_hash_t *new_revprop_table;
2052   if (revprop_table_in)
2053     {
2054       if (svn_prop_has_svn_prop(revprop_table_in, pool))
2055         return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2056                                 _("Standard properties can't be set "
2057                                   "explicitly as revision properties"));
2058       new_revprop_table = apr_hash_copy(pool, revprop_table_in);
2059     }
2060   else
2061     {
2062       new_revprop_table = apr_hash_make(pool);
2063     }
2064   svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
2065                 svn_string_create(log_msg, pool));
2066   *revprop_table_out = new_revprop_table;
2067   return SVN_NO_ERROR;
2068 }