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