1/*
2 * commit_util.c:  Driver for the WC commit process.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27#include <string.h>
28
29#include <apr_pools.h>
30#include <apr_hash.h>
31#include <apr_md5.h>
32
33#include "client.h"
34#include "svn_dirent_uri.h"
35#include "svn_path.h"
36#include "svn_types.h"
37#include "svn_pools.h"
38#include "svn_props.h"
39#include "svn_iter.h"
40#include "svn_hash.h"
41
42#include <assert.h>
43#include <stdlib.h>  /* for qsort() */
44
45#include "svn_private_config.h"
46#include "private/svn_wc_private.h"
47#include "private/svn_client_private.h"
48
49/*** Uncomment this to turn on commit driver debugging. ***/
50/*
51#define SVN_CLIENT_COMMIT_DEBUG
52*/
53
54/* Wrap an RA error in a nicer error if one is available. */
55static svn_error_t *
56fixup_commit_error(const char *local_abspath,
57                   const char *base_url,
58                   const char *path,
59                   svn_node_kind_t kind,
60                   svn_error_t *err,
61                   svn_client_ctx_t *ctx,
62                   apr_pool_t *scratch_pool)
63{
64  if (err->apr_err == SVN_ERR_FS_NOT_FOUND
65      || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS
66      || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE
67      || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND
68      || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS
69      || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE))
70    {
71      if (ctx->notify_func2)
72        {
73          svn_wc_notify_t *notify;
74
75          if (local_abspath)
76            notify = svn_wc_create_notify(local_abspath,
77                                          svn_wc_notify_failed_out_of_date,
78                                          scratch_pool);
79          else
80            notify = svn_wc_create_notify_url(
81                                svn_path_url_add_component2(base_url, path,
82                                                            scratch_pool),
83                                svn_wc_notify_failed_out_of_date,
84                                scratch_pool);
85
86          notify->kind = kind;
87          notify->err = err;
88
89          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
90        }
91
92      return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err,
93                               (kind == svn_node_dir
94                                 ? _("Directory '%s' is out of date")
95                                 : _("File '%s' is out of date")),
96                               local_abspath
97                                  ? svn_dirent_local_style(local_abspath,
98                                                           scratch_pool)
99                                  : svn_path_url_add_component2(base_url,
100                                                                path,
101                                                                scratch_pool));
102    }
103  else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN)
104           || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH
105           || err->apr_err == SVN_ERR_RA_NOT_LOCKED)
106    {
107      if (ctx->notify_func2)
108        {
109          svn_wc_notify_t *notify;
110
111          if (local_abspath)
112            notify = svn_wc_create_notify(local_abspath,
113                                          svn_wc_notify_failed_locked,
114                                          scratch_pool);
115          else
116            notify = svn_wc_create_notify_url(
117                                svn_path_url_add_component2(base_url, path,
118                                                            scratch_pool),
119                                svn_wc_notify_failed_locked,
120                                scratch_pool);
121
122          notify->kind = kind;
123          notify->err = err;
124
125          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
126        }
127
128      return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err,
129                   (kind == svn_node_dir
130                     ? _("Directory '%s' is locked in another working copy")
131                     : _("File '%s' is locked in another working copy")),
132                   local_abspath
133                      ? svn_dirent_local_style(local_abspath,
134                                               scratch_pool)
135                      : svn_path_url_add_component2(base_url,
136                                                    path,
137                                                    scratch_pool));
138    }
139  else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)
140           || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE)
141    {
142      if (ctx->notify_func2)
143        {
144          svn_wc_notify_t *notify;
145
146          if (local_abspath)
147            notify = svn_wc_create_notify(
148                                    local_abspath,
149                                    svn_wc_notify_failed_forbidden_by_server,
150                                    scratch_pool);
151          else
152            notify = svn_wc_create_notify_url(
153                                svn_path_url_add_component2(base_url, path,
154                                                            scratch_pool),
155                                svn_wc_notify_failed_forbidden_by_server,
156                                scratch_pool);
157
158          notify->kind = kind;
159          notify->err = err;
160
161          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
162        }
163
164      return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err,
165                   (kind == svn_node_dir
166                     ? _("Changing directory '%s' is forbidden by the server")
167                     : _("Changing file '%s' is forbidden by the server")),
168                   local_abspath
169                      ? svn_dirent_local_style(local_abspath,
170                                               scratch_pool)
171                      : svn_path_url_add_component2(base_url,
172                                                    path,
173                                                    scratch_pool));
174    }
175  else
176    return err;
177}
178
179
180/*** Harvesting Commit Candidates ***/
181
182
183/* Add a new commit candidate (described by all parameters except
184   `COMMITTABLES') to the COMMITTABLES hash.  All of the commit item's
185   members are allocated out of RESULT_POOL.
186
187   If the state flag specifies that a lock must be used, store the token in LOCK
188   in lock_tokens.
189 */
190static svn_error_t *
191add_committable(svn_client__committables_t *committables,
192                const char *local_abspath,
193                svn_node_kind_t kind,
194                const char *repos_root_url,
195                const char *repos_relpath,
196                svn_revnum_t revision,
197                const char *copyfrom_relpath,
198                svn_revnum_t copyfrom_rev,
199                const char *moved_from_abspath,
200                apr_byte_t state_flags,
201                apr_hash_t *lock_tokens,
202                const svn_lock_t *lock,
203                apr_pool_t *result_pool,
204                apr_pool_t *scratch_pool)
205{
206  apr_array_header_t *array;
207  svn_client_commit_item3_t *new_item;
208
209  /* Sanity checks. */
210  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
211  SVN_ERR_ASSERT(repos_root_url && repos_relpath);
212
213  /* ### todo: Get the canonical repository for this item, which will
214     be the real key for the COMMITTABLES hash, instead of the above
215     bogosity. */
216  array = svn_hash_gets(committables->by_repository, repos_root_url);
217
218  /* E-gads!  There is no array for this repository yet!  Oh, no
219     problem, we'll just create (and add to the hash) one. */
220  if (array == NULL)
221    {
222      array = apr_array_make(result_pool, 1, sizeof(new_item));
223      svn_hash_sets(committables->by_repository,
224                    apr_pstrdup(result_pool, repos_root_url), array);
225    }
226
227  /* Now update pointer values, ensuring that their allocations live
228     in POOL. */
229  new_item = svn_client_commit_item3_create(result_pool);
230  new_item->path           = apr_pstrdup(result_pool, local_abspath);
231  new_item->kind           = kind;
232  new_item->url            = svn_path_url_add_component2(repos_root_url,
233                                                         repos_relpath,
234                                                         result_pool);
235  new_item->revision       = revision;
236  new_item->copyfrom_url   = copyfrom_relpath
237                                ? svn_path_url_add_component2(repos_root_url,
238                                                              copyfrom_relpath,
239                                                              result_pool)
240                                : NULL;
241  new_item->copyfrom_rev   = copyfrom_rev;
242  new_item->state_flags    = state_flags;
243  new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
244                                                   sizeof(svn_prop_t *));
245
246  if (moved_from_abspath)
247    new_item->moved_from_abspath = apr_pstrdup(result_pool,
248                                               moved_from_abspath);
249
250  /* Now, add the commit item to the array. */
251  APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
252
253  /* ... and to the hash. */
254  svn_hash_sets(committables->by_path, new_item->path, new_item);
255
256  if (lock
257      && lock_tokens
258      && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN))
259    {
260      svn_hash_sets(lock_tokens, new_item->url,
261                    apr_pstrdup(result_pool, lock->token));
262    }
263
264  return SVN_NO_ERROR;
265}
266
267/* If there is a commit item for PATH in COMMITTABLES, return it, else
268   return NULL.  Use POOL for temporary allocation only. */
269static svn_client_commit_item3_t *
270look_up_committable(svn_client__committables_t *committables,
271                    const char *path,
272                    apr_pool_t *pool)
273{
274  return (svn_client_commit_item3_t *)
275      svn_hash_gets(committables->by_path, path);
276}
277
278/* Helper function for svn_client__harvest_committables().
279 * Determine whether we are within a tree-conflicted subtree of the
280 * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
281static svn_error_t *
282bail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx,
283                                 const char *local_abspath,
284                                 svn_wc_notify_func2_t notify_func,
285                                 void *notify_baton,
286                                 apr_pool_t *scratch_pool)
287{
288  const char *wcroot_abspath;
289
290  SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath,
291                             scratch_pool, scratch_pool));
292
293  local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
294
295  while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath))
296    {
297      svn_boolean_t tree_conflicted;
298
299      /* Check if the parent has tree conflicts */
300      SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
301                                   wc_ctx, local_abspath, scratch_pool));
302      if (tree_conflicted)
303        {
304          if (notify_func != NULL)
305            {
306              notify_func(notify_baton,
307                          svn_wc_create_notify(local_abspath,
308                                               svn_wc_notify_failed_conflict,
309                                               scratch_pool),
310                          scratch_pool);
311            }
312
313          return svn_error_createf(
314                   SVN_ERR_WC_FOUND_CONFLICT, NULL,
315                   _("Aborting commit: '%s' remains in tree-conflict"),
316                   svn_dirent_local_style(local_abspath, scratch_pool));
317        }
318
319      /* Step outwards */
320      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
321        break;
322      else
323        local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
324    }
325
326  return SVN_NO_ERROR;
327}
328
329
330/* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using
331   WC_CTX and add those candidates to COMMITTABLES.  If in ADDS_ONLY modes,
332   only new additions are recognized.
333
334   DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH
335   when LOCAL_ABSPATH is itself a directory; see
336   svn_client__harvest_committables() for its behavior.
337
338   Lock tokens of candidates will be added to LOCK_TOKENS, if
339   non-NULL.  JUST_LOCKED indicates whether to treat non-modified items with
340   lock tokens as commit candidates.
341
342   If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to
343   be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as
344   items to delete in the copy destination.  COPY_MODE_ROOT should be set TRUE
345   for the first call for which COPY_MODE is TRUE, i.e. not for the
346   recursive calls, and FALSE otherwise.
347
348   If CHANGELISTS is non-NULL, it is a hash whose keys are const char *
349   changelist names used as a restrictive filter
350   when harvesting committables; that is, don't add a path to
351   COMMITTABLES unless it's a member of one of those changelists.
352
353   IS_EXPLICIT_TARGET should always be passed as TRUE, except when
354   harvest_committables() calls itself in recursion. This provides a way to
355   tell whether LOCAL_ABSPATH was an original target or whether it was reached
356   by recursing deeper into a dir target. (This is used to skip all file
357   externals that aren't explicit commit targets.)
358
359   DANGLERS is a hash table mapping const char* absolute paths of a parent
360   to a const char * absolute path of a child. See the comment about
361   danglers at the top of svn_client__harvest_committables().
362
363   If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
364   if the user has cancelled the operation.
365
366   Any items added to COMMITTABLES are allocated from the COMITTABLES
367   hash pool, not POOL.  SCRATCH_POOL is used for temporary allocations. */
368
369struct harvest_baton
370{
371  /* Static data */
372  const char *root_abspath;
373  svn_client__committables_t *committables;
374  apr_hash_t *lock_tokens;
375  const char *commit_relpath; /* Valid for the harvest root */
376  svn_depth_t depth;
377  svn_boolean_t just_locked;
378  apr_hash_t *changelists;
379  apr_hash_t *danglers;
380  svn_client__check_url_kind_t check_url_func;
381  void *check_url_baton;
382  svn_wc_notify_func2_t notify_func;
383  void *notify_baton;
384  svn_wc_context_t *wc_ctx;
385  apr_pool_t *result_pool;
386
387  /* Harvester state */
388  const char *skip_below_abspath; /* If non-NULL, skip everything below */
389};
390
391static svn_error_t *
392harvest_status_callback(void *status_baton,
393                        const char *local_abspath,
394                        const svn_wc_status3_t *status,
395                        apr_pool_t *scratch_pool);
396
397static svn_error_t *
398harvest_committables(const char *local_abspath,
399                     svn_client__committables_t *committables,
400                     apr_hash_t *lock_tokens,
401                     const char *copy_mode_relpath,
402                     svn_depth_t depth,
403                     svn_boolean_t just_locked,
404                     apr_hash_t *changelists,
405                     apr_hash_t *danglers,
406                     svn_client__check_url_kind_t check_url_func,
407                     void *check_url_baton,
408                     svn_cancel_func_t cancel_func,
409                     void *cancel_baton,
410                     svn_wc_notify_func2_t notify_func,
411                     void *notify_baton,
412                     svn_wc_context_t *wc_ctx,
413                     apr_pool_t *result_pool,
414                     apr_pool_t *scratch_pool)
415{
416  struct harvest_baton baton;
417
418  SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked);
419
420  baton.root_abspath = local_abspath;
421  baton.committables = committables;
422  baton.lock_tokens = lock_tokens;
423  baton.commit_relpath = copy_mode_relpath;
424  baton.depth = depth;
425  baton.just_locked = just_locked;
426  baton.changelists = changelists;
427  baton.danglers = danglers;
428  baton.check_url_func = check_url_func;
429  baton.check_url_baton = check_url_baton;
430  baton.notify_func = notify_func;
431  baton.notify_baton = notify_baton;
432  baton.wc_ctx = wc_ctx;
433  baton.result_pool = result_pool;
434
435  baton.skip_below_abspath = NULL;
436
437  SVN_ERR(svn_wc_walk_status(wc_ctx,
438                             local_abspath,
439                             depth,
440                             (copy_mode_relpath != NULL) /* get_all */,
441                             FALSE /* no_ignore */,
442                             FALSE /* ignore_text_mods */,
443                             NULL /* ignore_patterns */,
444                             harvest_status_callback,
445                             &baton,
446                             cancel_func, cancel_baton,
447                             scratch_pool));
448
449  return SVN_NO_ERROR;
450}
451
452static svn_error_t *
453harvest_not_present_for_copy(svn_wc_context_t *wc_ctx,
454                             const char *local_abspath,
455                             svn_client__committables_t *committables,
456                             const char *repos_root_url,
457                             const char *commit_relpath,
458                             svn_client__check_url_kind_t check_url_func,
459                             void *check_url_baton,
460                             apr_pool_t *result_pool,
461                             apr_pool_t *scratch_pool)
462{
463  const apr_array_header_t *children;
464  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
465  int i;
466
467  /* A function to retrieve not present children would be nice to have */
468  SVN_ERR(svn_wc__node_get_children_of_working_node(
469                                    &children, wc_ctx, local_abspath, TRUE,
470                                    scratch_pool, iterpool));
471
472  for (i = 0; i < children->nelts; i++)
473    {
474      const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
475      const char *name = svn_dirent_basename(this_abspath, NULL);
476      const char *this_commit_relpath;
477      svn_boolean_t not_present;
478      svn_node_kind_t kind;
479
480      svn_pool_clear(iterpool);
481
482      SVN_ERR(svn_wc__node_is_not_present(&not_present, NULL, NULL, wc_ctx,
483                                          this_abspath, FALSE, scratch_pool));
484
485      if (!not_present)
486        continue;
487
488      if (commit_relpath == NULL)
489        this_commit_relpath = NULL;
490      else
491        this_commit_relpath = svn_relpath_join(commit_relpath, name,
492                                              iterpool);
493
494      /* We should check if we should really add a delete operation */
495      if (check_url_func)
496        {
497          svn_revnum_t parent_rev;
498          const char *parent_repos_relpath;
499          const char *parent_repos_root_url;
500          const char *node_url;
501
502          /* Determine from what parent we would be the deleted child */
503          SVN_ERR(svn_wc__node_get_origin(
504                              NULL, &parent_rev, &parent_repos_relpath,
505                              &parent_repos_root_url, NULL, NULL,
506                              wc_ctx,
507                              svn_dirent_dirname(this_abspath,
508                                                  scratch_pool),
509                              FALSE, scratch_pool, scratch_pool));
510
511          node_url = svn_path_url_add_component2(
512                        svn_path_url_add_component2(parent_repos_root_url,
513                                                    parent_repos_relpath,
514                                                    scratch_pool),
515                        svn_dirent_basename(this_abspath, NULL),
516                        iterpool);
517
518          SVN_ERR(check_url_func(check_url_baton, &kind,
519                                 node_url, parent_rev, iterpool));
520
521          if (kind == svn_node_none)
522            continue; /* This node can't be deleted */
523        }
524      else
525        SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath,
526                                  TRUE, TRUE, scratch_pool));
527
528      SVN_ERR(add_committable(committables, this_abspath, kind,
529                              repos_root_url,
530                              this_commit_relpath,
531                              SVN_INVALID_REVNUM,
532                              NULL /* copyfrom_relpath */,
533                              SVN_INVALID_REVNUM /* copyfrom_rev */,
534                              NULL /* moved_from_abspath */,
535                              SVN_CLIENT_COMMIT_ITEM_DELETE,
536                              NULL, NULL,
537                              result_pool, scratch_pool));
538    }
539
540  svn_pool_destroy(iterpool);
541  return SVN_NO_ERROR;
542}
543
544/* Implements svn_wc_status_func4_t */
545static svn_error_t *
546harvest_status_callback(void *status_baton,
547                        const char *local_abspath,
548                        const svn_wc_status3_t *status,
549                        apr_pool_t *scratch_pool)
550{
551  apr_byte_t state_flags = 0;
552  svn_revnum_t node_rev;
553  const char *cf_relpath = NULL;
554  svn_revnum_t cf_rev = SVN_INVALID_REVNUM;
555  svn_boolean_t matches_changelists;
556  svn_boolean_t is_added;
557  svn_boolean_t is_deleted;
558  svn_boolean_t is_replaced;
559  svn_boolean_t is_op_root;
560  svn_revnum_t original_rev;
561  const char *original_relpath;
562  svn_boolean_t copy_mode;
563
564  struct harvest_baton *baton = status_baton;
565  svn_boolean_t is_harvest_root =
566                (strcmp(baton->root_abspath, local_abspath) == 0);
567  svn_client__committables_t *committables = baton->committables;
568  const char *repos_root_url = status->repos_root_url;
569  const char *commit_relpath = NULL;
570  svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root);
571  svn_boolean_t just_locked = baton->just_locked;
572  apr_hash_t *changelists = baton->changelists;
573  svn_wc_notify_func2_t notify_func = baton->notify_func;
574  void *notify_baton = baton->notify_baton;
575  svn_wc_context_t *wc_ctx = baton->wc_ctx;
576  apr_pool_t *result_pool = baton->result_pool;
577  const char *moved_from_abspath = NULL;
578
579  if (baton->commit_relpath)
580    commit_relpath = svn_relpath_join(
581                        baton->commit_relpath,
582                        svn_dirent_skip_ancestor(baton->root_abspath,
583                                                 local_abspath),
584                        scratch_pool);
585
586  copy_mode = (commit_relpath != NULL);
587
588  if (baton->skip_below_abspath
589      && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
590    {
591      return SVN_NO_ERROR;
592    }
593  else
594    baton->skip_below_abspath = NULL; /* We have left the skip tree */
595
596  /* Return early for nodes that don't have a committable status */
597  switch (status->node_status)
598    {
599      case svn_wc_status_unversioned:
600      case svn_wc_status_ignored:
601      case svn_wc_status_external:
602      case svn_wc_status_none:
603        /* Unversioned nodes aren't committable, but are reported by the status
604           walker.
605           But if the unversioned node is the root of the walk, we have a user
606           error */
607        if (is_harvest_root)
608          return svn_error_createf(
609                       SVN_ERR_ILLEGAL_TARGET, NULL,
610                       _("'%s' is not under version control"),
611                       svn_dirent_local_style(local_abspath, scratch_pool));
612        return SVN_NO_ERROR;
613      case svn_wc_status_normal:
614        /* Status normal nodes aren't modified, so we don't have to commit them
615           when we perform a normal commit. But if a node is conflicted we want
616           to stop the commit and if we are collecting lock tokens we want to
617           look further anyway.
618
619           When in copy mode we need to compare the revision of the node against
620           the parent node to copy mixed-revision base nodes properly */
621        if (!copy_mode && !status->conflicted
622            && !(just_locked && status->lock))
623          return SVN_NO_ERROR;
624        break;
625      default:
626        /* Fall through */
627        break;
628    }
629
630  /* Early out if the item is already marked as committable. */
631  if (look_up_committable(committables, local_abspath, scratch_pool))
632    return SVN_NO_ERROR;
633
634  SVN_ERR_ASSERT((copy_mode && commit_relpath)
635                 || (! copy_mode && ! commit_relpath));
636  SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root);
637
638  /* Save the result for reuse. */
639  matches_changelists = ((changelists == NULL)
640                         || (status->changelist != NULL
641                             && svn_hash_gets(changelists, status->changelist)
642                                != NULL));
643
644  /* Early exit. */
645  if (status->kind != svn_node_dir && ! matches_changelists)
646    {
647      return SVN_NO_ERROR;
648    }
649
650  /* If NODE is in our changelist, then examine it for conflicts. We
651     need to bail out if any conflicts exist.
652     The status walker checked for conflict marker removal. */
653  if (status->conflicted && matches_changelists)
654    {
655      if (notify_func != NULL)
656        {
657          notify_func(notify_baton,
658                      svn_wc_create_notify(local_abspath,
659                                           svn_wc_notify_failed_conflict,
660                                           scratch_pool),
661                      scratch_pool);
662        }
663
664      return svn_error_createf(
665            SVN_ERR_WC_FOUND_CONFLICT, NULL,
666            _("Aborting commit: '%s' remains in conflict"),
667            svn_dirent_local_style(local_abspath, scratch_pool));
668    }
669  else if (status->node_status == svn_wc_status_obstructed)
670    {
671      /* A node's type has changed before attempting to commit.
672         This also catches symlink vs non symlink changes */
673
674      if (notify_func != NULL)
675        {
676          notify_func(notify_baton,
677                      svn_wc_create_notify(local_abspath,
678                                           svn_wc_notify_failed_obstruction,
679                                           scratch_pool),
680                      scratch_pool);
681        }
682
683      return svn_error_createf(
684                    SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
685                    _("Node '%s' has unexpectedly changed kind"),
686                    svn_dirent_local_style(local_abspath, scratch_pool));
687    }
688
689  if (status->conflicted && status->kind == svn_node_unknown)
690    return SVN_NO_ERROR; /* Ignore delete-delete conflict */
691
692  /* Return error on unknown path kinds.  We check both the entry and
693     the node itself, since a path might have changed kind since its
694     entry was written. */
695  SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted,
696                                         &is_replaced,
697                                         &is_op_root,
698                                         &node_rev,
699                                         &original_rev, &original_relpath,
700                                         wc_ctx, local_abspath,
701                                         scratch_pool, scratch_pool));
702
703  /* Hande file externals only when passed as explicit target. Note that
704   * svn_client_commit6() passes all committable externals in as explicit
705   * targets iff they count. */
706  if (status->file_external && !is_harvest_root)
707    {
708      return SVN_NO_ERROR;
709    }
710
711  if (status->node_status == svn_wc_status_missing && matches_changelists)
712    {
713      /* Added files and directories must exist. See issue #3198. */
714      if (is_added && is_op_root)
715        {
716          if (notify_func != NULL)
717            {
718              notify_func(notify_baton,
719                          svn_wc_create_notify(local_abspath,
720                                               svn_wc_notify_failed_missing,
721                                               scratch_pool),
722                          scratch_pool);
723            }
724          return svn_error_createf(
725             SVN_ERR_WC_PATH_NOT_FOUND, NULL,
726             _("'%s' is scheduled for addition, but is missing"),
727             svn_dirent_local_style(local_abspath, scratch_pool));
728        }
729
730      return SVN_NO_ERROR;
731    }
732
733  if (is_deleted && !is_op_root /* && !is_added */)
734    return SVN_NO_ERROR; /* Not an operational delete and not an add. */
735
736  /* Check for the deletion case.
737     * We delete explicitly deleted nodes (duh!)
738     * We delete not-present children of copies
739     * We delete nodes that directly replace a node in its ancestor
740   */
741
742  if (is_deleted || is_replaced)
743    state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
744
745  /* Check for adds and copies */
746  if (is_added && is_op_root)
747    {
748      /* Root of local add or copy */
749      state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
750
751      if (original_relpath)
752        {
753          /* Root of copy */
754          state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
755          cf_relpath = original_relpath;
756          cf_rev = original_rev;
757
758          if (status->moved_from_abspath && !copy_mode)
759            {
760              state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE;
761              moved_from_abspath = status->moved_from_abspath;
762            }
763        }
764    }
765
766  /* Further copies may occur in copy mode. */
767  else if (copy_mode
768           && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
769    {
770      svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
771
772      if (!copy_mode_root && !status->switched && !is_added)
773        SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, NULL, NULL, NULL, NULL,
774                                      wc_ctx, svn_dirent_dirname(local_abspath,
775                                                                 scratch_pool),
776                                      FALSE /* ignore_enoent */,
777                                      FALSE /* show_hidden */,
778                                      scratch_pool, scratch_pool));
779
780      if (copy_mode_root || status->switched || node_rev != dir_rev)
781        {
782          state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD
783                          | SVN_CLIENT_COMMIT_ITEM_IS_COPY);
784
785          if (status->copied)
786            {
787              /* Copy from original location */
788              cf_rev = original_rev;
789              cf_relpath = original_relpath;
790            }
791          else
792            {
793              /* Copy BASE location, to represent a mixed-rev or switch copy */
794              cf_rev = status->revision;
795              cf_relpath = status->repos_relpath;
796            }
797        }
798    }
799
800  if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
801      || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
802    {
803      svn_boolean_t text_mod = FALSE;
804      svn_boolean_t prop_mod = FALSE;
805
806      if (status->kind == svn_node_file)
807        {
808          /* Check for text modifications on files */
809          if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
810              && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
811            {
812              text_mod = TRUE; /* Local added files are always modified */
813            }
814          else
815            text_mod = (status->text_status != svn_wc_status_normal);
816        }
817
818      prop_mod = (status->prop_status != svn_wc_status_normal
819                  && status->prop_status != svn_wc_status_none);
820
821      /* Set text/prop modification flags accordingly. */
822      if (text_mod)
823        state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
824      if (prop_mod)
825        state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
826    }
827
828  /* If the entry has a lock token and it is already a commit candidate,
829     or the caller wants unmodified locked items to be treated as
830     such, note this fact. */
831  if (status->lock && baton->lock_tokens && (state_flags || just_locked))
832    {
833      state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
834    }
835
836  /* Now, if this is something to commit, add it to our list. */
837  if (matches_changelists
838      && state_flags)
839    {
840      /* Finally, add the committable item. */
841      SVN_ERR(add_committable(committables, local_abspath,
842                              status->kind,
843                              repos_root_url,
844                              copy_mode
845                                      ? commit_relpath
846                                      : status->repos_relpath,
847                              copy_mode
848                                      ? SVN_INVALID_REVNUM
849                                      : node_rev,
850                              cf_relpath,
851                              cf_rev,
852                              moved_from_abspath,
853                              state_flags,
854                              baton->lock_tokens, status->lock,
855                              result_pool, scratch_pool));
856    }
857
858    /* Fetch lock tokens for descendants of deleted BASE nodes. */
859  if (matches_changelists
860      && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
861      && !copy_mode
862      && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */
863      && baton->lock_tokens)
864    {
865      apr_hash_t *local_relpath_tokens;
866      apr_hash_index_t *hi;
867
868      SVN_ERR(svn_wc__node_get_lock_tokens_recursive(
869                  &local_relpath_tokens, wc_ctx, local_abspath,
870                  result_pool, scratch_pool));
871
872      /* Add tokens to existing hash. */
873      for (hi = apr_hash_first(scratch_pool, local_relpath_tokens);
874           hi;
875           hi = apr_hash_next(hi))
876        {
877          const void *key;
878          apr_ssize_t klen;
879          void * val;
880
881          apr_hash_this(hi, &key, &klen, &val);
882
883          apr_hash_set(baton->lock_tokens, key, klen, val);
884        }
885    }
886
887  /* Make sure we check for dangling children on additions
888
889     We perform this operation on the harvest root, and on roots caused by
890     changelist filtering.
891  */
892  if (matches_changelists
893      && (is_harvest_root || baton->changelists)
894      && state_flags
895      && is_added
896      && baton->danglers)
897    {
898      /* If a node is added, its parent must exist in the repository at the
899         time of committing */
900      apr_hash_t *danglers = baton->danglers;
901      svn_boolean_t parent_added;
902      const char *parent_abspath = svn_dirent_dirname(local_abspath,
903                                                      scratch_pool);
904
905      /* First check if parent is already in the list of commits
906         (Common case for GUI clients that provide a list of commit targets) */
907      if (look_up_committable(committables, parent_abspath, scratch_pool))
908        parent_added = FALSE; /* Skip all expensive checks */
909      else
910        SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath,
911                                      scratch_pool));
912
913      if (parent_added)
914        {
915          const char *copy_root_abspath;
916          svn_boolean_t parent_is_copy;
917
918          /* The parent is added, so either it is a copy, or a locally added
919           * directory. In either case, we require the op-root of the parent
920           * to be part of the commit. See issue #4059. */
921          SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL,
922                                          NULL, &copy_root_abspath,
923                                          wc_ctx, parent_abspath,
924                                          FALSE, scratch_pool, scratch_pool));
925
926          if (parent_is_copy)
927            parent_abspath = copy_root_abspath;
928
929          if (!svn_hash_gets(danglers, parent_abspath))
930            {
931              svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath),
932                            apr_pstrdup(result_pool, local_abspath));
933            }
934        }
935    }
936
937  if (is_deleted && !is_added)
938    {
939      /* Skip all descendants */
940      if (status->kind == svn_node_dir)
941        baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
942                                                local_abspath);
943      return SVN_NO_ERROR;
944    }
945
946  /* Recursively handle each node according to depth, except when the
947     node is only being deleted, or is in an added tree (as added trees
948     use the normal commit handling). */
949  if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir)
950    {
951      SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables,
952                                           repos_root_url, commit_relpath,
953                                           baton->check_url_func,
954                                           baton->check_url_baton,
955                                           result_pool, scratch_pool));
956    }
957
958  return SVN_NO_ERROR;
959}
960
961/* Baton for handle_descendants */
962struct handle_descendants_baton
963{
964  svn_wc_context_t *wc_ctx;
965  svn_cancel_func_t cancel_func;
966  void *cancel_baton;
967  svn_client__check_url_kind_t check_url_func;
968  void *check_url_baton;
969};
970
971/* Helper for the commit harvesters */
972static svn_error_t *
973handle_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 */
1076static void
1077create_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
1086svn_error_t *
1087svn_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
1227struct 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
1236static svn_error_t *
1237harvest_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
1286svn_error_t *
1287svn_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
1314int 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
1325svn_error_t *
1326svn_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
1423struct 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 */
1431struct 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. */
1454static svn_error_t *
1455do_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
1754fixup_error:
1755  return svn_error_trace(fixup_commit_error(local_abspath,
1756                                            icb->base_url,
1757                                            path, kind,
1758                                            err, ctx, pool));
1759}
1760
1761svn_error_t *
1762svn_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
1871svn_error_t *
1872svn_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
1957svn_error_t *
1958svn_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}
1982