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