1251881Speter/*
2251881Speter * commit_util.c:  Driver for the WC commit process.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter#include <string.h>
28251881Speter
29251881Speter#include <apr_pools.h>
30251881Speter#include <apr_hash.h>
31251881Speter#include <apr_md5.h>
32251881Speter
33251881Speter#include "client.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_path.h"
36251881Speter#include "svn_types.h"
37251881Speter#include "svn_pools.h"
38251881Speter#include "svn_props.h"
39251881Speter#include "svn_iter.h"
40251881Speter#include "svn_hash.h"
41251881Speter
42251881Speter#include <assert.h>
43251881Speter#include <stdlib.h>  /* for qsort() */
44251881Speter
45251881Speter#include "svn_private_config.h"
46251881Speter#include "private/svn_wc_private.h"
47251881Speter#include "private/svn_client_private.h"
48251881Speter
49251881Speter/*** Uncomment this to turn on commit driver debugging. ***/
50251881Speter/*
51251881Speter#define SVN_CLIENT_COMMIT_DEBUG
52251881Speter*/
53251881Speter
54251881Speter/* Wrap an RA error in a nicer error if one is available. */
55251881Speterstatic svn_error_t *
56251881Speterfixup_commit_error(const char *local_abspath,
57251881Speter                   const char *base_url,
58251881Speter                   const char *path,
59251881Speter                   svn_node_kind_t kind,
60251881Speter                   svn_error_t *err,
61251881Speter                   svn_client_ctx_t *ctx,
62251881Speter                   apr_pool_t *scratch_pool)
63251881Speter{
64251881Speter  if (err->apr_err == SVN_ERR_FS_NOT_FOUND
65251881Speter      || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS
66251881Speter      || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE
67251881Speter      || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND
68251881Speter      || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS
69251881Speter      || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE))
70251881Speter    {
71251881Speter      if (ctx->notify_func2)
72251881Speter        {
73251881Speter          svn_wc_notify_t *notify;
74251881Speter
75251881Speter          if (local_abspath)
76251881Speter            notify = svn_wc_create_notify(local_abspath,
77251881Speter                                          svn_wc_notify_failed_out_of_date,
78251881Speter                                          scratch_pool);
79251881Speter          else
80251881Speter            notify = svn_wc_create_notify_url(
81251881Speter                                svn_path_url_add_component2(base_url, path,
82251881Speter                                                            scratch_pool),
83251881Speter                                svn_wc_notify_failed_out_of_date,
84251881Speter                                scratch_pool);
85251881Speter
86251881Speter          notify->kind = kind;
87251881Speter          notify->err = err;
88251881Speter
89251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
90251881Speter        }
91251881Speter
92251881Speter      return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err,
93251881Speter                               (kind == svn_node_dir
94251881Speter                                 ? _("Directory '%s' is out of date")
95251881Speter                                 : _("File '%s' is out of date")),
96251881Speter                               local_abspath
97251881Speter                                  ? svn_dirent_local_style(local_abspath,
98251881Speter                                                           scratch_pool)
99251881Speter                                  : svn_path_url_add_component2(base_url,
100251881Speter                                                                path,
101251881Speter                                                                scratch_pool));
102251881Speter    }
103251881Speter  else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN)
104251881Speter           || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH
105251881Speter           || err->apr_err == SVN_ERR_RA_NOT_LOCKED)
106251881Speter    {
107251881Speter      if (ctx->notify_func2)
108251881Speter        {
109251881Speter          svn_wc_notify_t *notify;
110251881Speter
111251881Speter          if (local_abspath)
112251881Speter            notify = svn_wc_create_notify(local_abspath,
113251881Speter                                          svn_wc_notify_failed_locked,
114251881Speter                                          scratch_pool);
115251881Speter          else
116251881Speter            notify = svn_wc_create_notify_url(
117251881Speter                                svn_path_url_add_component2(base_url, path,
118251881Speter                                                            scratch_pool),
119251881Speter                                svn_wc_notify_failed_locked,
120251881Speter                                scratch_pool);
121251881Speter
122251881Speter          notify->kind = kind;
123251881Speter          notify->err = err;
124251881Speter
125251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
126251881Speter        }
127251881Speter
128251881Speter      return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err,
129251881Speter                   (kind == svn_node_dir
130251881Speter                     ? _("Directory '%s' is locked in another working copy")
131251881Speter                     : _("File '%s' is locked in another working copy")),
132251881Speter                   local_abspath
133251881Speter                      ? svn_dirent_local_style(local_abspath,
134251881Speter                                               scratch_pool)
135251881Speter                      : svn_path_url_add_component2(base_url,
136251881Speter                                                    path,
137251881Speter                                                    scratch_pool));
138251881Speter    }
139251881Speter  else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)
140251881Speter           || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE)
141251881Speter    {
142251881Speter      if (ctx->notify_func2)
143251881Speter        {
144251881Speter          svn_wc_notify_t *notify;
145251881Speter
146251881Speter          if (local_abspath)
147251881Speter            notify = svn_wc_create_notify(
148251881Speter                                    local_abspath,
149251881Speter                                    svn_wc_notify_failed_forbidden_by_server,
150251881Speter                                    scratch_pool);
151251881Speter          else
152251881Speter            notify = svn_wc_create_notify_url(
153251881Speter                                svn_path_url_add_component2(base_url, path,
154251881Speter                                                            scratch_pool),
155251881Speter                                svn_wc_notify_failed_forbidden_by_server,
156251881Speter                                scratch_pool);
157251881Speter
158251881Speter          notify->kind = kind;
159251881Speter          notify->err = err;
160251881Speter
161251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
162251881Speter        }
163251881Speter
164251881Speter      return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err,
165251881Speter                   (kind == svn_node_dir
166251881Speter                     ? _("Changing directory '%s' is forbidden by the server")
167251881Speter                     : _("Changing file '%s' is forbidden by the server")),
168251881Speter                   local_abspath
169251881Speter                      ? svn_dirent_local_style(local_abspath,
170251881Speter                                               scratch_pool)
171251881Speter                      : svn_path_url_add_component2(base_url,
172251881Speter                                                    path,
173251881Speter                                                    scratch_pool));
174251881Speter    }
175251881Speter  else
176251881Speter    return err;
177251881Speter}
178251881Speter
179251881Speter
180251881Speter/*** Harvesting Commit Candidates ***/
181251881Speter
182251881Speter
183251881Speter/* Add a new commit candidate (described by all parameters except
184251881Speter   `COMMITTABLES') to the COMMITTABLES hash.  All of the commit item's
185251881Speter   members are allocated out of RESULT_POOL.
186251881Speter
187251881Speter   If the state flag specifies that a lock must be used, store the token in LOCK
188251881Speter   in lock_tokens.
189251881Speter */
190251881Speterstatic svn_error_t *
191251881Speteradd_committable(svn_client__committables_t *committables,
192251881Speter                const char *local_abspath,
193251881Speter                svn_node_kind_t kind,
194251881Speter                const char *repos_root_url,
195251881Speter                const char *repos_relpath,
196251881Speter                svn_revnum_t revision,
197251881Speter                const char *copyfrom_relpath,
198251881Speter                svn_revnum_t copyfrom_rev,
199251881Speter                const char *moved_from_abspath,
200251881Speter                apr_byte_t state_flags,
201251881Speter                apr_hash_t *lock_tokens,
202251881Speter                const svn_lock_t *lock,
203251881Speter                apr_pool_t *result_pool,
204251881Speter                apr_pool_t *scratch_pool)
205251881Speter{
206251881Speter  apr_array_header_t *array;
207251881Speter  svn_client_commit_item3_t *new_item;
208251881Speter
209251881Speter  /* Sanity checks. */
210251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
211251881Speter  SVN_ERR_ASSERT(repos_root_url && repos_relpath);
212251881Speter
213251881Speter  /* ### todo: Get the canonical repository for this item, which will
214251881Speter     be the real key for the COMMITTABLES hash, instead of the above
215251881Speter     bogosity. */
216251881Speter  array = svn_hash_gets(committables->by_repository, repos_root_url);
217251881Speter
218251881Speter  /* E-gads!  There is no array for this repository yet!  Oh, no
219251881Speter     problem, we'll just create (and add to the hash) one. */
220251881Speter  if (array == NULL)
221251881Speter    {
222251881Speter      array = apr_array_make(result_pool, 1, sizeof(new_item));
223251881Speter      svn_hash_sets(committables->by_repository,
224251881Speter                    apr_pstrdup(result_pool, repos_root_url), array);
225251881Speter    }
226251881Speter
227251881Speter  /* Now update pointer values, ensuring that their allocations live
228251881Speter     in POOL. */
229251881Speter  new_item = svn_client_commit_item3_create(result_pool);
230251881Speter  new_item->path           = apr_pstrdup(result_pool, local_abspath);
231251881Speter  new_item->kind           = kind;
232251881Speter  new_item->url            = svn_path_url_add_component2(repos_root_url,
233251881Speter                                                         repos_relpath,
234251881Speter                                                         result_pool);
235251881Speter  new_item->revision       = revision;
236251881Speter  new_item->copyfrom_url   = copyfrom_relpath
237251881Speter                                ? svn_path_url_add_component2(repos_root_url,
238251881Speter                                                              copyfrom_relpath,
239251881Speter                                                              result_pool)
240251881Speter                                : NULL;
241251881Speter  new_item->copyfrom_rev   = copyfrom_rev;
242251881Speter  new_item->state_flags    = state_flags;
243251881Speter  new_item->incoming_prop_changes = apr_array_make(result_pool, 1,
244251881Speter                                                   sizeof(svn_prop_t *));
245251881Speter
246251881Speter  if (moved_from_abspath)
247251881Speter    new_item->moved_from_abspath = apr_pstrdup(result_pool,
248251881Speter                                               moved_from_abspath);
249251881Speter
250251881Speter  /* Now, add the commit item to the array. */
251251881Speter  APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
252251881Speter
253251881Speter  /* ... and to the hash. */
254251881Speter  svn_hash_sets(committables->by_path, new_item->path, new_item);
255251881Speter
256251881Speter  if (lock
257251881Speter      && lock_tokens
258251881Speter      && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN))
259251881Speter    {
260251881Speter      svn_hash_sets(lock_tokens, new_item->url,
261251881Speter                    apr_pstrdup(result_pool, lock->token));
262251881Speter    }
263251881Speter
264251881Speter  return SVN_NO_ERROR;
265251881Speter}
266251881Speter
267251881Speter/* If there is a commit item for PATH in COMMITTABLES, return it, else
268251881Speter   return NULL.  Use POOL for temporary allocation only. */
269251881Speterstatic svn_client_commit_item3_t *
270251881Speterlook_up_committable(svn_client__committables_t *committables,
271251881Speter                    const char *path,
272251881Speter                    apr_pool_t *pool)
273251881Speter{
274251881Speter  return (svn_client_commit_item3_t *)
275251881Speter      svn_hash_gets(committables->by_path, path);
276251881Speter}
277251881Speter
278251881Speter/* Helper function for svn_client__harvest_committables().
279251881Speter * Determine whether we are within a tree-conflicted subtree of the
280251881Speter * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
281251881Speterstatic svn_error_t *
282251881Speterbail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx,
283251881Speter                                 const char *local_abspath,
284251881Speter                                 svn_wc_notify_func2_t notify_func,
285251881Speter                                 void *notify_baton,
286251881Speter                                 apr_pool_t *scratch_pool)
287251881Speter{
288251881Speter  const char *wcroot_abspath;
289251881Speter
290251881Speter  SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath,
291251881Speter                             scratch_pool, scratch_pool));
292251881Speter
293251881Speter  local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
294251881Speter
295251881Speter  while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath))
296251881Speter    {
297251881Speter      svn_boolean_t tree_conflicted;
298251881Speter
299251881Speter      /* Check if the parent has tree conflicts */
300251881Speter      SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
301251881Speter                                   wc_ctx, local_abspath, scratch_pool));
302251881Speter      if (tree_conflicted)
303251881Speter        {
304251881Speter          if (notify_func != NULL)
305251881Speter            {
306251881Speter              notify_func(notify_baton,
307251881Speter                          svn_wc_create_notify(local_abspath,
308251881Speter                                               svn_wc_notify_failed_conflict,
309251881Speter                                               scratch_pool),
310251881Speter                          scratch_pool);
311251881Speter            }
312251881Speter
313251881Speter          return svn_error_createf(
314251881Speter                   SVN_ERR_WC_FOUND_CONFLICT, NULL,
315251881Speter                   _("Aborting commit: '%s' remains in tree-conflict"),
316251881Speter                   svn_dirent_local_style(local_abspath, scratch_pool));
317251881Speter        }
318251881Speter
319251881Speter      /* Step outwards */
320251881Speter      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
321251881Speter        break;
322251881Speter      else
323251881Speter        local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
324251881Speter    }
325251881Speter
326251881Speter  return SVN_NO_ERROR;
327251881Speter}
328251881Speter
329251881Speter
330251881Speter/* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using
331251881Speter   WC_CTX and add those candidates to COMMITTABLES.  If in ADDS_ONLY modes,
332251881Speter   only new additions are recognized.
333251881Speter
334251881Speter   DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH
335251881Speter   when LOCAL_ABSPATH is itself a directory; see
336251881Speter   svn_client__harvest_committables() for its behavior.
337251881Speter
338251881Speter   Lock tokens of candidates will be added to LOCK_TOKENS, if
339251881Speter   non-NULL.  JUST_LOCKED indicates whether to treat non-modified items with
340251881Speter   lock tokens as commit candidates.
341251881Speter
342251881Speter   If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to
343251881Speter   be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as
344251881Speter   items to delete in the copy destination.  COPY_MODE_ROOT should be set TRUE
345251881Speter   for the first call for which COPY_MODE is TRUE, i.e. not for the
346251881Speter   recursive calls, and FALSE otherwise.
347251881Speter
348251881Speter   If CHANGELISTS is non-NULL, it is a hash whose keys are const char *
349251881Speter   changelist names used as a restrictive filter
350251881Speter   when harvesting committables; that is, don't add a path to
351251881Speter   COMMITTABLES unless it's a member of one of those changelists.
352251881Speter
353251881Speter   IS_EXPLICIT_TARGET should always be passed as TRUE, except when
354251881Speter   harvest_committables() calls itself in recursion. This provides a way to
355251881Speter   tell whether LOCAL_ABSPATH was an original target or whether it was reached
356251881Speter   by recursing deeper into a dir target. (This is used to skip all file
357251881Speter   externals that aren't explicit commit targets.)
358251881Speter
359251881Speter   DANGLERS is a hash table mapping const char* absolute paths of a parent
360251881Speter   to a const char * absolute path of a child. See the comment about
361251881Speter   danglers at the top of svn_client__harvest_committables().
362251881Speter
363251881Speter   If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
364251881Speter   if the user has cancelled the operation.
365251881Speter
366251881Speter   Any items added to COMMITTABLES are allocated from the COMITTABLES
367251881Speter   hash pool, not POOL.  SCRATCH_POOL is used for temporary allocations. */
368251881Speter
369251881Speterstruct harvest_baton
370251881Speter{
371251881Speter  /* Static data */
372251881Speter  const char *root_abspath;
373251881Speter  svn_client__committables_t *committables;
374251881Speter  apr_hash_t *lock_tokens;
375251881Speter  const char *commit_relpath; /* Valid for the harvest root */
376251881Speter  svn_depth_t depth;
377251881Speter  svn_boolean_t just_locked;
378251881Speter  apr_hash_t *changelists;
379251881Speter  apr_hash_t *danglers;
380251881Speter  svn_client__check_url_kind_t check_url_func;
381251881Speter  void *check_url_baton;
382251881Speter  svn_wc_notify_func2_t notify_func;
383251881Speter  void *notify_baton;
384251881Speter  svn_wc_context_t *wc_ctx;
385251881Speter  apr_pool_t *result_pool;
386251881Speter
387251881Speter  /* Harvester state */
388251881Speter  const char *skip_below_abspath; /* If non-NULL, skip everything below */
389251881Speter};
390251881Speter
391251881Speterstatic svn_error_t *
392251881Speterharvest_status_callback(void *status_baton,
393251881Speter                        const char *local_abspath,
394251881Speter                        const svn_wc_status3_t *status,
395251881Speter                        apr_pool_t *scratch_pool);
396251881Speter
397251881Speterstatic svn_error_t *
398251881Speterharvest_committables(const char *local_abspath,
399251881Speter                     svn_client__committables_t *committables,
400251881Speter                     apr_hash_t *lock_tokens,
401251881Speter                     const char *copy_mode_relpath,
402251881Speter                     svn_depth_t depth,
403251881Speter                     svn_boolean_t just_locked,
404251881Speter                     apr_hash_t *changelists,
405251881Speter                     apr_hash_t *danglers,
406251881Speter                     svn_client__check_url_kind_t check_url_func,
407251881Speter                     void *check_url_baton,
408251881Speter                     svn_cancel_func_t cancel_func,
409251881Speter                     void *cancel_baton,
410251881Speter                     svn_wc_notify_func2_t notify_func,
411251881Speter                     void *notify_baton,
412251881Speter                     svn_wc_context_t *wc_ctx,
413251881Speter                     apr_pool_t *result_pool,
414251881Speter                     apr_pool_t *scratch_pool)
415251881Speter{
416251881Speter  struct harvest_baton baton;
417251881Speter
418251881Speter  SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked);
419251881Speter
420251881Speter  baton.root_abspath = local_abspath;
421251881Speter  baton.committables = committables;
422251881Speter  baton.lock_tokens = lock_tokens;
423251881Speter  baton.commit_relpath = copy_mode_relpath;
424251881Speter  baton.depth = depth;
425251881Speter  baton.just_locked = just_locked;
426251881Speter  baton.changelists = changelists;
427251881Speter  baton.danglers = danglers;
428251881Speter  baton.check_url_func = check_url_func;
429251881Speter  baton.check_url_baton = check_url_baton;
430251881Speter  baton.notify_func = notify_func;
431251881Speter  baton.notify_baton = notify_baton;
432251881Speter  baton.wc_ctx = wc_ctx;
433251881Speter  baton.result_pool = result_pool;
434251881Speter
435251881Speter  baton.skip_below_abspath = NULL;
436251881Speter
437251881Speter  SVN_ERR(svn_wc_walk_status(wc_ctx,
438251881Speter                             local_abspath,
439251881Speter                             depth,
440251881Speter                             (copy_mode_relpath != NULL) /* get_all */,
441251881Speter                             FALSE /* no_ignore */,
442251881Speter                             FALSE /* ignore_text_mods */,
443251881Speter                             NULL /* ignore_patterns */,
444251881Speter                             harvest_status_callback,
445251881Speter                             &baton,
446251881Speter                             cancel_func, cancel_baton,
447251881Speter                             scratch_pool));
448251881Speter
449251881Speter  return SVN_NO_ERROR;
450251881Speter}
451251881Speter
452251881Speterstatic svn_error_t *
453251881Speterharvest_not_present_for_copy(svn_wc_context_t *wc_ctx,
454251881Speter                             const char *local_abspath,
455251881Speter                             svn_client__committables_t *committables,
456251881Speter                             const char *repos_root_url,
457251881Speter                             const char *commit_relpath,
458251881Speter                             svn_client__check_url_kind_t check_url_func,
459251881Speter                             void *check_url_baton,
460251881Speter                             apr_pool_t *result_pool,
461251881Speter                             apr_pool_t *scratch_pool)
462251881Speter{
463251881Speter  const apr_array_header_t *children;
464251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
465251881Speter  int i;
466251881Speter
467251881Speter  /* A function to retrieve not present children would be nice to have */
468251881Speter  SVN_ERR(svn_wc__node_get_children_of_working_node(
469251881Speter                                    &children, wc_ctx, local_abspath, TRUE,
470251881Speter                                    scratch_pool, iterpool));
471251881Speter
472251881Speter  for (i = 0; i < children->nelts; i++)
473251881Speter    {
474251881Speter      const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
475251881Speter      const char *name = svn_dirent_basename(this_abspath, NULL);
476251881Speter      const char *this_commit_relpath;
477251881Speter      svn_boolean_t not_present;
478251881Speter      svn_node_kind_t kind;
479251881Speter
480251881Speter      svn_pool_clear(iterpool);
481251881Speter
482251881Speter      SVN_ERR(svn_wc__node_is_not_present(&not_present, NULL, NULL, wc_ctx,
483251881Speter                                          this_abspath, FALSE, scratch_pool));
484251881Speter
485251881Speter      if (!not_present)
486251881Speter        continue;
487251881Speter
488251881Speter      if (commit_relpath == NULL)
489251881Speter        this_commit_relpath = NULL;
490251881Speter      else
491251881Speter        this_commit_relpath = svn_relpath_join(commit_relpath, name,
492251881Speter                                              iterpool);
493251881Speter
494251881Speter      /* We should check if we should really add a delete operation */
495251881Speter      if (check_url_func)
496251881Speter        {
497251881Speter          svn_revnum_t parent_rev;
498251881Speter          const char *parent_repos_relpath;
499251881Speter          const char *parent_repos_root_url;
500251881Speter          const char *node_url;
501251881Speter
502251881Speter          /* Determine from what parent we would be the deleted child */
503251881Speter          SVN_ERR(svn_wc__node_get_origin(
504251881Speter                              NULL, &parent_rev, &parent_repos_relpath,
505251881Speter                              &parent_repos_root_url, NULL, NULL,
506251881Speter                              wc_ctx,
507251881Speter                              svn_dirent_dirname(this_abspath,
508251881Speter                                                  scratch_pool),
509251881Speter                              FALSE, scratch_pool, scratch_pool));
510251881Speter
511251881Speter          node_url = svn_path_url_add_component2(
512251881Speter                        svn_path_url_add_component2(parent_repos_root_url,
513251881Speter                                                    parent_repos_relpath,
514251881Speter                                                    scratch_pool),
515251881Speter                        svn_dirent_basename(this_abspath, NULL),
516251881Speter                        iterpool);
517251881Speter
518251881Speter          SVN_ERR(check_url_func(check_url_baton, &kind,
519251881Speter                                 node_url, parent_rev, iterpool));
520251881Speter
521251881Speter          if (kind == svn_node_none)
522251881Speter            continue; /* This node can't be deleted */
523251881Speter        }
524251881Speter      else
525251881Speter        SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath,
526251881Speter                                  TRUE, TRUE, scratch_pool));
527251881Speter
528251881Speter      SVN_ERR(add_committable(committables, this_abspath, kind,
529251881Speter                              repos_root_url,
530251881Speter                              this_commit_relpath,
531251881Speter                              SVN_INVALID_REVNUM,
532251881Speter                              NULL /* copyfrom_relpath */,
533251881Speter                              SVN_INVALID_REVNUM /* copyfrom_rev */,
534251881Speter                              NULL /* moved_from_abspath */,
535251881Speter                              SVN_CLIENT_COMMIT_ITEM_DELETE,
536251881Speter                              NULL, NULL,
537251881Speter                              result_pool, scratch_pool));
538251881Speter    }
539251881Speter
540251881Speter  svn_pool_destroy(iterpool);
541251881Speter  return SVN_NO_ERROR;
542251881Speter}
543251881Speter
544251881Speter/* Implements svn_wc_status_func4_t */
545251881Speterstatic svn_error_t *
546251881Speterharvest_status_callback(void *status_baton,
547251881Speter                        const char *local_abspath,
548251881Speter                        const svn_wc_status3_t *status,
549251881Speter                        apr_pool_t *scratch_pool)
550251881Speter{
551251881Speter  apr_byte_t state_flags = 0;
552251881Speter  svn_revnum_t node_rev;
553251881Speter  const char *cf_relpath = NULL;
554251881Speter  svn_revnum_t cf_rev = SVN_INVALID_REVNUM;
555251881Speter  svn_boolean_t matches_changelists;
556251881Speter  svn_boolean_t is_added;
557251881Speter  svn_boolean_t is_deleted;
558251881Speter  svn_boolean_t is_replaced;
559251881Speter  svn_boolean_t is_op_root;
560251881Speter  svn_revnum_t original_rev;
561251881Speter  const char *original_relpath;
562251881Speter  svn_boolean_t copy_mode;
563251881Speter
564251881Speter  struct harvest_baton *baton = status_baton;
565251881Speter  svn_boolean_t is_harvest_root =
566251881Speter                (strcmp(baton->root_abspath, local_abspath) == 0);
567251881Speter  svn_client__committables_t *committables = baton->committables;
568251881Speter  const char *repos_root_url = status->repos_root_url;
569251881Speter  const char *commit_relpath = NULL;
570251881Speter  svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root);
571251881Speter  svn_boolean_t just_locked = baton->just_locked;
572251881Speter  apr_hash_t *changelists = baton->changelists;
573251881Speter  svn_wc_notify_func2_t notify_func = baton->notify_func;
574251881Speter  void *notify_baton = baton->notify_baton;
575251881Speter  svn_wc_context_t *wc_ctx = baton->wc_ctx;
576251881Speter  apr_pool_t *result_pool = baton->result_pool;
577251881Speter  const char *moved_from_abspath = NULL;
578251881Speter
579251881Speter  if (baton->commit_relpath)
580251881Speter    commit_relpath = svn_relpath_join(
581251881Speter                        baton->commit_relpath,
582251881Speter                        svn_dirent_skip_ancestor(baton->root_abspath,
583251881Speter                                                 local_abspath),
584251881Speter                        scratch_pool);
585251881Speter
586251881Speter  copy_mode = (commit_relpath != NULL);
587251881Speter
588251881Speter  if (baton->skip_below_abspath
589251881Speter      && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
590251881Speter    {
591251881Speter      return SVN_NO_ERROR;
592251881Speter    }
593251881Speter  else
594251881Speter    baton->skip_below_abspath = NULL; /* We have left the skip tree */
595251881Speter
596251881Speter  /* Return early for nodes that don't have a committable status */
597251881Speter  switch (status->node_status)
598251881Speter    {
599251881Speter      case svn_wc_status_unversioned:
600251881Speter      case svn_wc_status_ignored:
601251881Speter      case svn_wc_status_external:
602251881Speter      case svn_wc_status_none:
603251881Speter        /* Unversioned nodes aren't committable, but are reported by the status
604251881Speter           walker.
605251881Speter           But if the unversioned node is the root of the walk, we have a user
606251881Speter           error */
607251881Speter        if (is_harvest_root)
608251881Speter          return svn_error_createf(
609251881Speter                       SVN_ERR_ILLEGAL_TARGET, NULL,
610251881Speter                       _("'%s' is not under version control"),
611251881Speter                       svn_dirent_local_style(local_abspath, scratch_pool));
612251881Speter        return SVN_NO_ERROR;
613251881Speter      case svn_wc_status_normal:
614251881Speter        /* Status normal nodes aren't modified, so we don't have to commit them
615251881Speter           when we perform a normal commit. But if a node is conflicted we want
616251881Speter           to stop the commit and if we are collecting lock tokens we want to
617251881Speter           look further anyway.
618251881Speter
619251881Speter           When in copy mode we need to compare the revision of the node against
620251881Speter           the parent node to copy mixed-revision base nodes properly */
621251881Speter        if (!copy_mode && !status->conflicted
622251881Speter            && !(just_locked && status->lock))
623251881Speter          return SVN_NO_ERROR;
624251881Speter        break;
625251881Speter      default:
626251881Speter        /* Fall through */
627251881Speter        break;
628251881Speter    }
629251881Speter
630251881Speter  /* Early out if the item is already marked as committable. */
631251881Speter  if (look_up_committable(committables, local_abspath, scratch_pool))
632251881Speter    return SVN_NO_ERROR;
633251881Speter
634251881Speter  SVN_ERR_ASSERT((copy_mode && commit_relpath)
635251881Speter                 || (! copy_mode && ! commit_relpath));
636251881Speter  SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root);
637251881Speter
638251881Speter  /* Save the result for reuse. */
639251881Speter  matches_changelists = ((changelists == NULL)
640251881Speter                         || (status->changelist != NULL
641251881Speter                             && svn_hash_gets(changelists, status->changelist)
642251881Speter                                != NULL));
643251881Speter
644251881Speter  /* Early exit. */
645251881Speter  if (status->kind != svn_node_dir && ! matches_changelists)
646251881Speter    {
647251881Speter      return SVN_NO_ERROR;
648251881Speter    }
649251881Speter
650251881Speter  /* If NODE is in our changelist, then examine it for conflicts. We
651251881Speter     need to bail out if any conflicts exist.
652251881Speter     The status walker checked for conflict marker removal. */
653251881Speter  if (status->conflicted && matches_changelists)
654251881Speter    {
655251881Speter      if (notify_func != NULL)
656251881Speter        {
657251881Speter          notify_func(notify_baton,
658251881Speter                      svn_wc_create_notify(local_abspath,
659251881Speter                                           svn_wc_notify_failed_conflict,
660251881Speter                                           scratch_pool),
661251881Speter                      scratch_pool);
662251881Speter        }
663251881Speter
664251881Speter      return svn_error_createf(
665251881Speter            SVN_ERR_WC_FOUND_CONFLICT, NULL,
666251881Speter            _("Aborting commit: '%s' remains in conflict"),
667251881Speter            svn_dirent_local_style(local_abspath, scratch_pool));
668251881Speter    }
669251881Speter  else if (status->node_status == svn_wc_status_obstructed)
670251881Speter    {
671251881Speter      /* A node's type has changed before attempting to commit.
672251881Speter         This also catches symlink vs non symlink changes */
673251881Speter
674251881Speter      if (notify_func != NULL)
675251881Speter        {
676251881Speter          notify_func(notify_baton,
677251881Speter                      svn_wc_create_notify(local_abspath,
678251881Speter                                           svn_wc_notify_failed_obstruction,
679251881Speter                                           scratch_pool),
680251881Speter                      scratch_pool);
681251881Speter        }
682251881Speter
683251881Speter      return svn_error_createf(
684251881Speter                    SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
685251881Speter                    _("Node '%s' has unexpectedly changed kind"),
686251881Speter                    svn_dirent_local_style(local_abspath, scratch_pool));
687251881Speter    }
688251881Speter
689251881Speter  if (status->conflicted && status->kind == svn_node_unknown)
690251881Speter    return SVN_NO_ERROR; /* Ignore delete-delete conflict */
691251881Speter
692251881Speter  /* Return error on unknown path kinds.  We check both the entry and
693251881Speter     the node itself, since a path might have changed kind since its
694251881Speter     entry was written. */
695251881Speter  SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted,
696251881Speter                                         &is_replaced,
697251881Speter                                         &is_op_root,
698251881Speter                                         &node_rev,
699251881Speter                                         &original_rev, &original_relpath,
700251881Speter                                         wc_ctx, local_abspath,
701251881Speter                                         scratch_pool, scratch_pool));
702251881Speter
703251881Speter  /* Hande file externals only when passed as explicit target. Note that
704251881Speter   * svn_client_commit6() passes all committable externals in as explicit
705251881Speter   * targets iff they count. */
706251881Speter  if (status->file_external && !is_harvest_root)
707251881Speter    {
708251881Speter      return SVN_NO_ERROR;
709251881Speter    }
710251881Speter
711251881Speter  if (status->node_status == svn_wc_status_missing && matches_changelists)
712251881Speter    {
713251881Speter      /* Added files and directories must exist. See issue #3198. */
714251881Speter      if (is_added && is_op_root)
715251881Speter        {
716251881Speter          if (notify_func != NULL)
717251881Speter            {
718251881Speter              notify_func(notify_baton,
719251881Speter                          svn_wc_create_notify(local_abspath,
720251881Speter                                               svn_wc_notify_failed_missing,
721251881Speter                                               scratch_pool),
722251881Speter                          scratch_pool);
723251881Speter            }
724251881Speter          return svn_error_createf(
725251881Speter             SVN_ERR_WC_PATH_NOT_FOUND, NULL,
726251881Speter             _("'%s' is scheduled for addition, but is missing"),
727251881Speter             svn_dirent_local_style(local_abspath, scratch_pool));
728251881Speter        }
729251881Speter
730251881Speter      return SVN_NO_ERROR;
731251881Speter    }
732251881Speter
733251881Speter  if (is_deleted && !is_op_root /* && !is_added */)
734251881Speter    return SVN_NO_ERROR; /* Not an operational delete and not an add. */
735251881Speter
736251881Speter  /* Check for the deletion case.
737251881Speter     * We delete explicitly deleted nodes (duh!)
738251881Speter     * We delete not-present children of copies
739251881Speter     * We delete nodes that directly replace a node in its ancestor
740251881Speter   */
741251881Speter
742251881Speter  if (is_deleted || is_replaced)
743251881Speter    state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
744251881Speter
745251881Speter  /* Check for adds and copies */
746251881Speter  if (is_added && is_op_root)
747251881Speter    {
748251881Speter      /* Root of local add or copy */
749251881Speter      state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
750251881Speter
751251881Speter      if (original_relpath)
752251881Speter        {
753251881Speter          /* Root of copy */
754251881Speter          state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
755251881Speter          cf_relpath = original_relpath;
756251881Speter          cf_rev = original_rev;
757251881Speter
758251881Speter          if (status->moved_from_abspath && !copy_mode)
759251881Speter            {
760251881Speter              state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE;
761251881Speter              moved_from_abspath = status->moved_from_abspath;
762251881Speter            }
763251881Speter        }
764251881Speter    }
765251881Speter
766251881Speter  /* Further copies may occur in copy mode. */
767251881Speter  else if (copy_mode
768251881Speter           && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
769251881Speter    {
770251881Speter      svn_revnum_t dir_rev = SVN_INVALID_REVNUM;
771251881Speter
772251881Speter      if (!copy_mode_root && !status->switched && !is_added)
773251881Speter        SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, NULL, NULL, NULL, NULL,
774251881Speter                                      wc_ctx, svn_dirent_dirname(local_abspath,
775251881Speter                                                                 scratch_pool),
776251881Speter                                      FALSE /* ignore_enoent */,
777251881Speter                                      FALSE /* show_hidden */,
778251881Speter                                      scratch_pool, scratch_pool));
779251881Speter
780251881Speter      if (copy_mode_root || status->switched || node_rev != dir_rev)
781251881Speter        {
782251881Speter          state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD
783251881Speter                          | SVN_CLIENT_COMMIT_ITEM_IS_COPY);
784251881Speter
785251881Speter          if (status->copied)
786251881Speter            {
787251881Speter              /* Copy from original location */
788251881Speter              cf_rev = original_rev;
789251881Speter              cf_relpath = original_relpath;
790251881Speter            }
791251881Speter          else
792251881Speter            {
793251881Speter              /* Copy BASE location, to represent a mixed-rev or switch copy */
794251881Speter              cf_rev = status->revision;
795251881Speter              cf_relpath = status->repos_relpath;
796251881Speter            }
797251881Speter        }
798251881Speter    }
799251881Speter
800251881Speter  if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
801251881Speter      || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
802251881Speter    {
803251881Speter      svn_boolean_t text_mod = FALSE;
804251881Speter      svn_boolean_t prop_mod = FALSE;
805251881Speter
806251881Speter      if (status->kind == svn_node_file)
807251881Speter        {
808251881Speter          /* Check for text modifications on files */
809251881Speter          if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
810251881Speter              && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
811251881Speter            {
812251881Speter              text_mod = TRUE; /* Local added files are always modified */
813251881Speter            }
814251881Speter          else
815251881Speter            text_mod = (status->text_status != svn_wc_status_normal);
816251881Speter        }
817251881Speter
818251881Speter      prop_mod = (status->prop_status != svn_wc_status_normal
819251881Speter                  && status->prop_status != svn_wc_status_none);
820251881Speter
821251881Speter      /* Set text/prop modification flags accordingly. */
822251881Speter      if (text_mod)
823251881Speter        state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
824251881Speter      if (prop_mod)
825251881Speter        state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
826251881Speter    }
827251881Speter
828251881Speter  /* If the entry has a lock token and it is already a commit candidate,
829251881Speter     or the caller wants unmodified locked items to be treated as
830251881Speter     such, note this fact. */
831251881Speter  if (status->lock && baton->lock_tokens && (state_flags || just_locked))
832251881Speter    {
833251881Speter      state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
834251881Speter    }
835251881Speter
836251881Speter  /* Now, if this is something to commit, add it to our list. */
837251881Speter  if (matches_changelists
838251881Speter      && state_flags)
839251881Speter    {
840251881Speter      /* Finally, add the committable item. */
841251881Speter      SVN_ERR(add_committable(committables, local_abspath,
842251881Speter                              status->kind,
843251881Speter                              repos_root_url,
844251881Speter                              copy_mode
845251881Speter                                      ? commit_relpath
846251881Speter                                      : status->repos_relpath,
847251881Speter                              copy_mode
848251881Speter                                      ? SVN_INVALID_REVNUM
849251881Speter                                      : node_rev,
850251881Speter                              cf_relpath,
851251881Speter                              cf_rev,
852251881Speter                              moved_from_abspath,
853251881Speter                              state_flags,
854251881Speter                              baton->lock_tokens, status->lock,
855251881Speter                              result_pool, scratch_pool));
856251881Speter    }
857251881Speter
858251881Speter    /* Fetch lock tokens for descendants of deleted BASE nodes. */
859251881Speter  if (matches_changelists
860251881Speter      && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
861251881Speter      && !copy_mode
862251881Speter      && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */
863251881Speter      && baton->lock_tokens)
864251881Speter    {
865251881Speter      apr_hash_t *local_relpath_tokens;
866251881Speter      apr_hash_index_t *hi;
867251881Speter
868251881Speter      SVN_ERR(svn_wc__node_get_lock_tokens_recursive(
869251881Speter                  &local_relpath_tokens, wc_ctx, local_abspath,
870251881Speter                  result_pool, scratch_pool));
871251881Speter
872251881Speter      /* Add tokens to existing hash. */
873251881Speter      for (hi = apr_hash_first(scratch_pool, local_relpath_tokens);
874251881Speter           hi;
875251881Speter           hi = apr_hash_next(hi))
876251881Speter        {
877251881Speter          const void *key;
878251881Speter          apr_ssize_t klen;
879251881Speter          void * val;
880251881Speter
881251881Speter          apr_hash_this(hi, &key, &klen, &val);
882251881Speter
883251881Speter          apr_hash_set(baton->lock_tokens, key, klen, val);
884251881Speter        }
885251881Speter    }
886251881Speter
887251881Speter  /* Make sure we check for dangling children on additions
888251881Speter
889251881Speter     We perform this operation on the harvest root, and on roots caused by
890251881Speter     changelist filtering.
891251881Speter  */
892251881Speter  if (matches_changelists
893251881Speter      && (is_harvest_root || baton->changelists)
894251881Speter      && state_flags
895269847Speter      && (is_added || (is_deleted && is_op_root && status->copied))
896251881Speter      && baton->danglers)
897251881Speter    {
898251881Speter      /* If a node is added, its parent must exist in the repository at the
899251881Speter         time of committing */
900251881Speter      apr_hash_t *danglers = baton->danglers;
901251881Speter      svn_boolean_t parent_added;
902251881Speter      const char *parent_abspath = svn_dirent_dirname(local_abspath,
903251881Speter                                                      scratch_pool);
904251881Speter
905251881Speter      /* First check if parent is already in the list of commits
906251881Speter         (Common case for GUI clients that provide a list of commit targets) */
907251881Speter      if (look_up_committable(committables, parent_abspath, scratch_pool))
908251881Speter        parent_added = FALSE; /* Skip all expensive checks */
909251881Speter      else
910251881Speter        SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath,
911251881Speter                                      scratch_pool));
912251881Speter
913251881Speter      if (parent_added)
914251881Speter        {
915251881Speter          const char *copy_root_abspath;
916251881Speter          svn_boolean_t parent_is_copy;
917251881Speter
918251881Speter          /* The parent is added, so either it is a copy, or a locally added
919251881Speter           * directory. In either case, we require the op-root of the parent
920251881Speter           * to be part of the commit. See issue #4059. */
921251881Speter          SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL,
922251881Speter                                          NULL, &copy_root_abspath,
923251881Speter                                          wc_ctx, parent_abspath,
924251881Speter                                          FALSE, scratch_pool, scratch_pool));
925251881Speter
926251881Speter          if (parent_is_copy)
927251881Speter            parent_abspath = copy_root_abspath;
928251881Speter
929251881Speter          if (!svn_hash_gets(danglers, parent_abspath))
930251881Speter            {
931251881Speter              svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath),
932251881Speter                            apr_pstrdup(result_pool, local_abspath));
933251881Speter            }
934251881Speter        }
935251881Speter    }
936251881Speter
937251881Speter  if (is_deleted && !is_added)
938251881Speter    {
939251881Speter      /* Skip all descendants */
940251881Speter      if (status->kind == svn_node_dir)
941251881Speter        baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
942251881Speter                                                local_abspath);
943251881Speter      return SVN_NO_ERROR;
944251881Speter    }
945251881Speter
946251881Speter  /* Recursively handle each node according to depth, except when the
947251881Speter     node is only being deleted, or is in an added tree (as added trees
948251881Speter     use the normal commit handling). */
949251881Speter  if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir)
950251881Speter    {
951251881Speter      SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables,
952251881Speter                                           repos_root_url, commit_relpath,
953251881Speter                                           baton->check_url_func,
954251881Speter                                           baton->check_url_baton,
955251881Speter                                           result_pool, scratch_pool));
956251881Speter    }
957251881Speter
958251881Speter  return SVN_NO_ERROR;
959251881Speter}
960251881Speter
961251881Speter/* Baton for handle_descendants */
962251881Speterstruct handle_descendants_baton
963251881Speter{
964251881Speter  svn_wc_context_t *wc_ctx;
965251881Speter  svn_cancel_func_t cancel_func;
966251881Speter  void *cancel_baton;
967251881Speter  svn_client__check_url_kind_t check_url_func;
968251881Speter  void *check_url_baton;
969269847Speter  svn_client__committables_t *committables;
970251881Speter};
971251881Speter
972251881Speter/* Helper for the commit harvesters */
973251881Speterstatic svn_error_t *
974251881Speterhandle_descendants(void *baton,
975269847Speter                   const void *key, apr_ssize_t klen, void *val,
976269847Speter                   apr_pool_t *pool)
977251881Speter{
978251881Speter  struct handle_descendants_baton *hdb = baton;
979251881Speter  apr_array_header_t *commit_items = val;
980251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
981269847Speter  const char *repos_root_url = key;
982251881Speter  int i;
983251881Speter
984251881Speter  for (i = 0; i < commit_items->nelts; i++)
985251881Speter    {
986251881Speter      svn_client_commit_item3_t *item =
987251881Speter        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
988251881Speter      const apr_array_header_t *absent_descendants;
989251881Speter      int j;
990251881Speter
991251881Speter      /* Is this a copy operation? */
992251881Speter      if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
993251881Speter          || ! item->copyfrom_url)
994251881Speter        continue;
995251881Speter
996251881Speter      if (hdb->cancel_func)
997251881Speter        SVN_ERR(hdb->cancel_func(hdb->cancel_baton));
998251881Speter
999251881Speter      svn_pool_clear(iterpool);
1000251881Speter
1001251881Speter      SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants,
1002251881Speter                                                  hdb->wc_ctx, item->path,
1003251881Speter                                                  iterpool, iterpool));
1004251881Speter
1005251881Speter      for (j = 0; j < absent_descendants->nelts; j++)
1006251881Speter        {
1007251881Speter          svn_node_kind_t kind;
1008269847Speter          svn_client_commit_item3_t *desc_item;
1009251881Speter          const char *relpath = APR_ARRAY_IDX(absent_descendants, j,
1010251881Speter                                              const char *);
1011251881Speter          const char *local_abspath = svn_dirent_join(item->path, relpath,
1012251881Speter                                                      iterpool);
1013251881Speter
1014269847Speter          /* ### Need a sub-iterpool? */
1015269847Speter
1016269847Speter
1017269847Speter          /* We found a 'not present' descendant during a copy (at op_depth>0),
1018269847Speter             this is most commonly caused by copying some mixed revision tree.
1019269847Speter
1020269847Speter             In this case not present can imply that the node does not exist
1021269847Speter             in the parent revision, or that the node does. But we want to copy
1022269847Speter             the working copy state in which it does not exist, but might be
1023269847Speter             replaced. */
1024269847Speter
1025269847Speter          desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath);
1026269847Speter
1027269847Speter          /* If the path has a commit operation (possibly at an higher
1028269847Speter             op_depth, we might want to turn an add in a replace. */
1029269847Speter          if (desc_item)
1030251881Speter            {
1031269847Speter              const char *dir;
1032269847Speter              svn_boolean_t found_intermediate = FALSE;
1033251881Speter
1034269847Speter              if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1035269847Speter                continue; /* We already have a delete or replace */
1036269847Speter              else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1037269847Speter                continue; /* Not a copy/add, just a modification */
1038269847Speter
1039269847Speter              dir = svn_dirent_dirname(local_abspath, iterpool);
1040269847Speter
1041269847Speter              while (strcmp(dir, item->path))
1042251881Speter                {
1043269847Speter                  svn_client_commit_item3_t *i_item;
1044269847Speter
1045269847Speter                  i_item = svn_hash_gets(hdb->committables->by_path, dir);
1046269847Speter
1047269847Speter                  if (i_item)
1048269847Speter                    {
1049269847Speter                      if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1050269847Speter                          || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1051269847Speter                        {
1052269847Speter                          found_intermediate = TRUE;
1053269847Speter                          break;
1054269847Speter                        }
1055269847Speter                    }
1056269847Speter                  dir = svn_dirent_dirname(dir, iterpool);
1057251881Speter                }
1058251881Speter
1059269847Speter              if (found_intermediate)
1060269847Speter                continue; /* Some intermediate ancestor is an add or delete */
1061251881Speter
1062269847Speter              /* Fall through to detect if we need to turn the add in a
1063269847Speter                 replace. */
1064269847Speter            }
1065251881Speter
1066251881Speter          if (hdb->check_url_func)
1067251881Speter            {
1068251881Speter              const char *from_url = svn_path_url_add_component2(
1069251881Speter                                                item->copyfrom_url, relpath,
1070251881Speter                                                iterpool);
1071251881Speter
1072251881Speter              SVN_ERR(hdb->check_url_func(hdb->check_url_baton,
1073251881Speter                                          &kind, from_url, item->copyfrom_rev,
1074251881Speter                                          iterpool));
1075251881Speter
1076251881Speter              if (kind == svn_node_none)
1077251881Speter                continue; /* This node is already deleted */
1078251881Speter            }
1079251881Speter          else
1080251881Speter            kind = svn_node_unknown; /* 'Ok' for a delete of something */
1081251881Speter
1082269847Speter          if (desc_item)
1083269847Speter            {
1084269847Speter              /* Extend the existing add/copy item to create a replace */
1085269847Speter              desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
1086269847Speter              continue;
1087269847Speter            }
1088251881Speter
1089269847Speter          /* Add a new commit item that describes the delete */
1090251881Speter
1091269847Speter          SVN_ERR(add_committable(hdb->committables,
1092269847Speter                                  svn_dirent_join(item->path, relpath,
1093269847Speter                                                  iterpool),
1094269847Speter                                  kind,
1095269847Speter                                  repos_root_url,
1096269847Speter                                  svn_uri_skip_ancestor(
1097269847Speter                                        repos_root_url,
1098269847Speter                                        svn_path_url_add_component2(item->url,
1099269847Speter                                                                    relpath,
1100269847Speter                                                                    iterpool),
1101269847Speter                                        iterpool),
1102269847Speter                                  SVN_INVALID_REVNUM,
1103269847Speter                                  NULL /* copyfrom_relpath */,
1104269847Speter                                  SVN_INVALID_REVNUM,
1105269847Speter                                  NULL /* moved_from_abspath */,
1106269847Speter                                  SVN_CLIENT_COMMIT_ITEM_DELETE,
1107269847Speter                                  NULL /* lock tokens */,
1108269847Speter                                  NULL /* lock */,
1109269847Speter                                  commit_items->pool,
1110269847Speter                                  iterpool));
1111251881Speter        }
1112251881Speter      }
1113251881Speter
1114251881Speter  svn_pool_destroy(iterpool);
1115251881Speter  return SVN_NO_ERROR;
1116251881Speter}
1117251881Speter
1118251881Speter/* Allocate and initialize the COMMITTABLES structure from POOL.
1119251881Speter */
1120251881Speterstatic void
1121251881Spetercreate_committables(svn_client__committables_t **committables,
1122251881Speter                    apr_pool_t *pool)
1123251881Speter{
1124251881Speter  *committables = apr_palloc(pool, sizeof(**committables));
1125251881Speter
1126251881Speter  (*committables)->by_repository = apr_hash_make(pool);
1127251881Speter  (*committables)->by_path = apr_hash_make(pool);
1128251881Speter}
1129251881Speter
1130251881Spetersvn_error_t *
1131251881Spetersvn_client__harvest_committables(svn_client__committables_t **committables,
1132251881Speter                                 apr_hash_t **lock_tokens,
1133251881Speter                                 const char *base_dir_abspath,
1134251881Speter                                 const apr_array_header_t *targets,
1135251881Speter                                 int depth_empty_start,
1136251881Speter                                 svn_depth_t depth,
1137251881Speter                                 svn_boolean_t just_locked,
1138251881Speter                                 const apr_array_header_t *changelists,
1139251881Speter                                 svn_client__check_url_kind_t check_url_func,
1140251881Speter                                 void *check_url_baton,
1141251881Speter                                 svn_client_ctx_t *ctx,
1142251881Speter                                 apr_pool_t *result_pool,
1143251881Speter                                 apr_pool_t *scratch_pool)
1144251881Speter{
1145251881Speter  int i;
1146251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1147251881Speter  apr_hash_t *changelist_hash = NULL;
1148251881Speter  struct handle_descendants_baton hdb;
1149251881Speter  apr_hash_index_t *hi;
1150251881Speter
1151251881Speter  /* It's possible that one of the named targets has a parent that is
1152251881Speter   * itself scheduled for addition or replacement -- that is, the
1153251881Speter   * parent is not yet versioned in the repository.  This is okay, as
1154251881Speter   * long as the parent itself is part of this same commit, either
1155251881Speter   * directly, or by virtue of a grandparent, great-grandparent, etc,
1156251881Speter   * being part of the commit.
1157251881Speter   *
1158251881Speter   * Since we don't know what's included in the commit until we've
1159251881Speter   * harvested all the targets, we can't reliably check this as we
1160251881Speter   * go.  So in `danglers', we record named targets whose parents
1161251881Speter   * do not yet exist in the repository. Then after harvesting the total
1162251881Speter   * commit group, we check to make sure those parents are included.
1163251881Speter   *
1164251881Speter   * Each key of danglers is a parent which does not exist in the
1165251881Speter   * repository.  The (const char *) value is one of that parent's
1166251881Speter   * children which is named as part of the commit; the child is
1167251881Speter   * included only to make a better error message.
1168251881Speter   *
1169251881Speter   * (The reason we don't bother to check unnamed -- i.e, implicit --
1170251881Speter   * targets is that they can only join the commit if their parents
1171251881Speter   * did too, so this situation can't arise for them.)
1172251881Speter   */
1173251881Speter  apr_hash_t *danglers = apr_hash_make(scratch_pool);
1174251881Speter
1175251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath));
1176251881Speter
1177251881Speter  /* Create the COMMITTABLES structure. */
1178251881Speter  create_committables(committables, result_pool);
1179251881Speter
1180251881Speter  /* And the LOCK_TOKENS dito. */
1181251881Speter  *lock_tokens = apr_hash_make(result_pool);
1182251881Speter
1183251881Speter  /* If we have a list of changelists, convert that into a hash with
1184251881Speter     changelist keys. */
1185251881Speter  if (changelists && changelists->nelts)
1186251881Speter    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists,
1187251881Speter                                       scratch_pool));
1188251881Speter
1189251881Speter  for (i = 0; i < targets->nelts; ++i)
1190251881Speter    {
1191251881Speter      const char *target_abspath;
1192251881Speter
1193251881Speter      svn_pool_clear(iterpool);
1194251881Speter
1195251881Speter      /* Add the relative portion to the base abspath.  */
1196251881Speter      target_abspath = svn_dirent_join(base_dir_abspath,
1197251881Speter                                       APR_ARRAY_IDX(targets, i, const char *),
1198251881Speter                                       iterpool);
1199251881Speter
1200251881Speter      /* Handle our TARGET. */
1201251881Speter      /* Make sure this isn't inside a working copy subtree that is
1202251881Speter       * marked as tree-conflicted. */
1203251881Speter      SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath,
1204251881Speter                                               ctx->notify_func2,
1205251881Speter                                               ctx->notify_baton2,
1206251881Speter                                               iterpool));
1207251881Speter
1208251881Speter      /* Are the remaining items externals with depth empty? */
1209251881Speter      if (i == depth_empty_start)
1210251881Speter        depth = svn_depth_empty;
1211251881Speter
1212251881Speter      SVN_ERR(harvest_committables(target_abspath,
1213251881Speter                                   *committables, *lock_tokens,
1214251881Speter                                   NULL /* COPY_MODE_RELPATH */,
1215251881Speter                                   depth, just_locked, changelist_hash,
1216251881Speter                                   danglers,
1217251881Speter                                   check_url_func, check_url_baton,
1218251881Speter                                   ctx->cancel_func, ctx->cancel_baton,
1219251881Speter                                   ctx->notify_func2, ctx->notify_baton2,
1220251881Speter                                   ctx->wc_ctx, result_pool, iterpool));
1221251881Speter    }
1222251881Speter
1223251881Speter  hdb.wc_ctx = ctx->wc_ctx;
1224251881Speter  hdb.cancel_func = ctx->cancel_func;
1225251881Speter  hdb.cancel_baton = ctx->cancel_baton;
1226251881Speter  hdb.check_url_func = check_url_func;
1227251881Speter  hdb.check_url_baton = check_url_baton;
1228269847Speter  hdb.committables = *committables;
1229251881Speter
1230251881Speter  SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository,
1231251881Speter                            handle_descendants, &hdb, iterpool));
1232251881Speter
1233251881Speter  /* Make sure that every path in danglers is part of the commit. */
1234251881Speter  for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi))
1235251881Speter    {
1236251881Speter      const char *dangling_parent = svn__apr_hash_index_key(hi);
1237251881Speter
1238251881Speter      svn_pool_clear(iterpool);
1239251881Speter
1240251881Speter      if (! look_up_committable(*committables, dangling_parent, iterpool))
1241251881Speter        {
1242251881Speter          const char *dangling_child = svn__apr_hash_index_val(hi);
1243251881Speter
1244251881Speter          if (ctx->notify_func2 != NULL)
1245251881Speter            {
1246251881Speter              svn_wc_notify_t *notify;
1247251881Speter
1248251881Speter              notify = svn_wc_create_notify(dangling_child,
1249251881Speter                                            svn_wc_notify_failed_no_parent,
1250251881Speter                                            scratch_pool);
1251251881Speter
1252251881Speter              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1253251881Speter            }
1254251881Speter
1255251881Speter          return svn_error_createf(
1256251881Speter                           SVN_ERR_ILLEGAL_TARGET, NULL,
1257251881Speter                           _("'%s' is not known to exist in the repository "
1258251881Speter                             "and is not part of the commit, "
1259251881Speter                             "yet its child '%s' is part of the commit"),
1260251881Speter                           /* Probably one or both of these is an entry, but
1261251881Speter                              safest to local_stylize just in case. */
1262251881Speter                           svn_dirent_local_style(dangling_parent, iterpool),
1263251881Speter                           svn_dirent_local_style(dangling_child, iterpool));
1264251881Speter        }
1265251881Speter    }
1266251881Speter
1267251881Speter  svn_pool_destroy(iterpool);
1268251881Speter
1269251881Speter  return SVN_NO_ERROR;
1270251881Speter}
1271251881Speter
1272251881Speterstruct copy_committables_baton
1273251881Speter{
1274251881Speter  svn_client_ctx_t *ctx;
1275251881Speter  svn_client__committables_t *committables;
1276251881Speter  apr_pool_t *result_pool;
1277251881Speter  svn_client__check_url_kind_t check_url_func;
1278251881Speter  void *check_url_baton;
1279251881Speter};
1280251881Speter
1281251881Speterstatic svn_error_t *
1282251881Speterharvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
1283251881Speter{
1284251881Speter  struct copy_committables_baton *btn = baton;
1285251881Speter  svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item;
1286251881Speter  const char *repos_root_url;
1287251881Speter  const char *commit_relpath;
1288251881Speter  struct handle_descendants_baton hdb;
1289251881Speter
1290251881Speter  /* Read the entry for this SRC. */
1291251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
1292251881Speter
1293251881Speter  SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL,
1294251881Speter                                      btn->ctx->wc_ctx,
1295251881Speter                                      pair->src_abspath_or_url,
1296251881Speter                                      pool, pool));
1297251881Speter
1298251881Speter  commit_relpath = svn_uri_skip_ancestor(repos_root_url,
1299251881Speter                                         pair->dst_abspath_or_url, pool);
1300251881Speter
1301251881Speter  /* Handle this SRC. */
1302251881Speter  SVN_ERR(harvest_committables(pair->src_abspath_or_url,
1303251881Speter                               btn->committables, NULL,
1304251881Speter                               commit_relpath,
1305251881Speter                               svn_depth_infinity,
1306251881Speter                               FALSE,  /* JUST_LOCKED */
1307251881Speter                               NULL /* changelists */,
1308251881Speter                               NULL,
1309251881Speter                               btn->check_url_func,
1310251881Speter                               btn->check_url_baton,
1311251881Speter                               btn->ctx->cancel_func,
1312251881Speter                               btn->ctx->cancel_baton,
1313251881Speter                               btn->ctx->notify_func2,
1314251881Speter                               btn->ctx->notify_baton2,
1315251881Speter                               btn->ctx->wc_ctx, btn->result_pool, pool));
1316251881Speter
1317251881Speter  hdb.wc_ctx = btn->ctx->wc_ctx;
1318251881Speter  hdb.cancel_func = btn->ctx->cancel_func;
1319251881Speter  hdb.cancel_baton = btn->ctx->cancel_baton;
1320251881Speter  hdb.check_url_func = btn->check_url_func;
1321251881Speter  hdb.check_url_baton = btn->check_url_baton;
1322269847Speter  hdb.committables = btn->committables;
1323251881Speter
1324251881Speter  SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository,
1325251881Speter                            handle_descendants, &hdb, pool));
1326251881Speter
1327251881Speter  return SVN_NO_ERROR;
1328251881Speter}
1329251881Speter
1330251881Speter
1331251881Speter
1332251881Spetersvn_error_t *
1333251881Spetersvn_client__get_copy_committables(svn_client__committables_t **committables,
1334251881Speter                                  const apr_array_header_t *copy_pairs,
1335251881Speter                                  svn_client__check_url_kind_t check_url_func,
1336251881Speter                                  void *check_url_baton,
1337251881Speter                                  svn_client_ctx_t *ctx,
1338251881Speter                                  apr_pool_t *result_pool,
1339251881Speter                                  apr_pool_t *scratch_pool)
1340251881Speter{
1341251881Speter  struct copy_committables_baton btn;
1342251881Speter
1343251881Speter  /* Create the COMMITTABLES structure. */
1344251881Speter  create_committables(committables, result_pool);
1345251881Speter
1346251881Speter  btn.ctx = ctx;
1347251881Speter  btn.committables = *committables;
1348251881Speter  btn.result_pool = result_pool;
1349251881Speter
1350251881Speter  btn.check_url_func = check_url_func;
1351251881Speter  btn.check_url_baton = check_url_baton;
1352251881Speter
1353251881Speter  /* For each copy pair, harvest the committables for that pair into the
1354251881Speter     committables hash. */
1355251881Speter  return svn_iter_apr_array(NULL, copy_pairs,
1356251881Speter                            harvest_copy_committables, &btn, scratch_pool);
1357251881Speter}
1358251881Speter
1359251881Speter
1360251881Speterint svn_client__sort_commit_item_urls(const void *a, const void *b)
1361251881Speter{
1362251881Speter  const svn_client_commit_item3_t *item1
1363251881Speter    = *((const svn_client_commit_item3_t * const *) a);
1364251881Speter  const svn_client_commit_item3_t *item2
1365251881Speter    = *((const svn_client_commit_item3_t * const *) b);
1366251881Speter  return svn_path_compare_paths(item1->url, item2->url);
1367251881Speter}
1368251881Speter
1369251881Speter
1370251881Speter
1371251881Spetersvn_error_t *
1372251881Spetersvn_client__condense_commit_items(const char **base_url,
1373251881Speter                                  apr_array_header_t *commit_items,
1374251881Speter                                  apr_pool_t *pool)
1375251881Speter{
1376251881Speter  apr_array_header_t *ci = commit_items; /* convenience */
1377251881Speter  const char *url;
1378251881Speter  svn_client_commit_item3_t *item, *last_item = NULL;
1379251881Speter  int i;
1380251881Speter
1381251881Speter  SVN_ERR_ASSERT(ci && ci->nelts);
1382251881Speter
1383251881Speter  /* Sort our commit items by their URLs. */
1384251881Speter  qsort(ci->elts, ci->nelts,
1385251881Speter        ci->elt_size, svn_client__sort_commit_item_urls);
1386251881Speter
1387251881Speter  /* Loop through the URLs, finding the longest usable ancestor common
1388251881Speter     to all of them, and making sure there are no duplicate URLs.  */
1389251881Speter  for (i = 0; i < ci->nelts; i++)
1390251881Speter    {
1391251881Speter      item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1392251881Speter      url = item->url;
1393251881Speter
1394251881Speter      if ((last_item) && (strcmp(last_item->url, url) == 0))
1395251881Speter        return svn_error_createf
1396251881Speter          (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
1397251881Speter           _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
1398251881Speter           svn_dirent_local_style(item->path, pool),
1399251881Speter           svn_dirent_local_style(last_item->path, pool));
1400251881Speter
1401251881Speter      /* In the first iteration, our BASE_URL is just our only
1402251881Speter         encountered commit URL to date.  After that, we find the
1403251881Speter         longest ancestor between the current BASE_URL and the current
1404251881Speter         commit URL.  */
1405251881Speter      if (i == 0)
1406251881Speter        *base_url = apr_pstrdup(pool, url);
1407251881Speter      else
1408251881Speter        *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool);
1409251881Speter
1410251881Speter      /* If our BASE_URL is itself a to-be-committed item, and it is
1411251881Speter         anything other than an already-versioned directory with
1412251881Speter         property mods, we'll call its parent directory URL the
1413251881Speter         BASE_URL.  Why?  Because we can't have a file URL as our base
1414251881Speter         -- period -- and all other directory operations (removal,
1415251881Speter         addition, etc.) require that we open that directory's parent
1416251881Speter         dir first.  */
1417251881Speter      /* ### I don't understand the strlen()s here, hmmm.  -kff */
1418251881Speter      if ((strlen(*base_url) == strlen(url))
1419251881Speter          && (! ((item->kind == svn_node_dir)
1420251881Speter                 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1421251881Speter        *base_url = svn_uri_dirname(*base_url, pool);
1422251881Speter
1423251881Speter      /* Stash our item here for the next iteration. */
1424251881Speter      last_item = item;
1425251881Speter    }
1426251881Speter
1427251881Speter  /* Now that we've settled on a *BASE_URL, go hack that base off
1428251881Speter     of all of our URLs and store it as session_relpath. */
1429251881Speter  for (i = 0; i < ci->nelts; i++)
1430251881Speter    {
1431251881Speter      svn_client_commit_item3_t *this_item
1432251881Speter        = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1433251881Speter
1434251881Speter      this_item->session_relpath = svn_uri_skip_ancestor(*base_url,
1435251881Speter                                                         this_item->url, pool);
1436251881Speter    }
1437251881Speter#ifdef SVN_CLIENT_COMMIT_DEBUG
1438251881Speter  /* ### TEMPORARY CODE ### */
1439251881Speter  SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url));
1440251881Speter  SVN_DBG(("   FLAGS     REV  REL-URL (COPY-URL)\n"));
1441251881Speter  for (i = 0; i < ci->nelts; i++)
1442251881Speter    {
1443251881Speter      svn_client_commit_item3_t *this_item
1444251881Speter        = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1445251881Speter      char flags[6];
1446251881Speter      flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1447251881Speter                   ? 'a' : '-';
1448251881Speter      flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1449251881Speter                   ? 'd' : '-';
1450251881Speter      flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1451251881Speter                   ? 't' : '-';
1452251881Speter      flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1453251881Speter                   ? 'p' : '-';
1454251881Speter      flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1455251881Speter                   ? 'c' : '-';
1456251881Speter      flags[5] = '\0';
1457251881Speter      SVN_DBG(("   %s  %6ld  '%s' (%s)\n",
1458251881Speter               flags,
1459251881Speter               this_item->revision,
1460251881Speter               this_item->url ? this_item->url : "",
1461251881Speter               this_item->copyfrom_url ? this_item->copyfrom_url : "none"));
1462251881Speter    }
1463251881Speter#endif /* SVN_CLIENT_COMMIT_DEBUG */
1464251881Speter
1465251881Speter  return SVN_NO_ERROR;
1466251881Speter}
1467251881Speter
1468251881Speter
1469251881Speterstruct file_mod_t
1470251881Speter{
1471251881Speter  const svn_client_commit_item3_t *item;
1472251881Speter  void *file_baton;
1473251881Speter};
1474251881Speter
1475251881Speter
1476251881Speter/* A baton for use while driving a path-based editor driver for commit */
1477251881Speterstruct item_commit_baton
1478251881Speter{
1479251881Speter  const svn_delta_editor_t *editor;    /* commit editor */
1480251881Speter  void *edit_baton;                    /* commit editor's baton */
1481251881Speter  apr_hash_t *file_mods;               /* hash: path->file_mod_t */
1482251881Speter  const char *notify_path_prefix;      /* notification path prefix
1483251881Speter                                          (NULL is okay, else abs path) */
1484251881Speter  svn_client_ctx_t *ctx;               /* client context baton */
1485251881Speter  apr_hash_t *commit_items;            /* the committables */
1486251881Speter  const char *base_url;                /* The session url for the commit */
1487251881Speter};
1488251881Speter
1489251881Speter
1490251881Speter/* Drive CALLBACK_BATON->editor with the change described by the item in
1491251881Speter * CALLBACK_BATON->commit_items that is keyed by PATH.  If the change
1492251881Speter * includes a text mod, however, call the editor's file_open() function
1493251881Speter * but do not send the text mod to the editor; instead, add a mapping of
1494251881Speter * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods.
1495251881Speter *
1496251881Speter * Before driving the editor, call the cancellation and notification
1497251881Speter * callbacks in CALLBACK_BATON->ctx, if present.
1498251881Speter *
1499251881Speter * This implements svn_delta_path_driver_cb_func_t. */
1500251881Speterstatic svn_error_t *
1501251881Speterdo_item_commit(void **dir_baton,
1502251881Speter               void *parent_baton,
1503251881Speter               void *callback_baton,
1504251881Speter               const char *path,
1505251881Speter               apr_pool_t *pool)
1506251881Speter{
1507251881Speter  struct item_commit_baton *icb = callback_baton;
1508251881Speter  const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items,
1509251881Speter                                                        path);
1510251881Speter  svn_node_kind_t kind = item->kind;
1511251881Speter  void *file_baton = NULL;
1512251881Speter  apr_pool_t *file_pool = NULL;
1513251881Speter  const svn_delta_editor_t *editor = icb->editor;
1514251881Speter  apr_hash_t *file_mods = icb->file_mods;
1515251881Speter  svn_client_ctx_t *ctx = icb->ctx;
1516251881Speter  svn_error_t *err;
1517251881Speter  const char *local_abspath = NULL;
1518251881Speter
1519251881Speter  /* Do some initializations. */
1520251881Speter  *dir_baton = NULL;
1521251881Speter  if (item->kind != svn_node_none && item->path)
1522251881Speter    {
1523251881Speter      /* We always get an absolute path, see svn_client_commit_item3_t. */
1524251881Speter      SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
1525251881Speter      local_abspath = item->path;
1526251881Speter    }
1527251881Speter
1528251881Speter  /* If this is a file with textual mods, we'll be keeping its baton
1529251881Speter     around until the end of the commit.  So just lump its memory into
1530251881Speter     a single, big, all-the-file-batons-in-here pool.  Otherwise, we
1531251881Speter     can just use POOL, and trust our caller to clean that mess up. */
1532251881Speter  if ((kind == svn_node_file)
1533251881Speter      && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1534251881Speter    file_pool = apr_hash_pool_get(file_mods);
1535251881Speter  else
1536251881Speter    file_pool = pool;
1537251881Speter
1538251881Speter  /* Call the cancellation function. */
1539251881Speter  if (ctx->cancel_func)
1540251881Speter    SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1541251881Speter
1542251881Speter  /* Validation. */
1543251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1544251881Speter    {
1545251881Speter      if (! item->copyfrom_url)
1546251881Speter        return svn_error_createf
1547251881Speter          (SVN_ERR_BAD_URL, NULL,
1548251881Speter           _("Commit item '%s' has copy flag but no copyfrom URL"),
1549251881Speter           svn_dirent_local_style(path, pool));
1550251881Speter      if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1551251881Speter        return svn_error_createf
1552251881Speter          (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1553251881Speter           _("Commit item '%s' has copy flag but an invalid revision"),
1554251881Speter           svn_dirent_local_style(path, pool));
1555251881Speter    }
1556251881Speter
1557251881Speter  /* If a feedback table was supplied by the application layer,
1558251881Speter     describe what we're about to do to this item. */
1559251881Speter  if (ctx->notify_func2 && item->path)
1560251881Speter    {
1561251881Speter      const char *npath = item->path;
1562251881Speter      svn_wc_notify_t *notify;
1563251881Speter
1564251881Speter      if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1565251881Speter          && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1566251881Speter        {
1567251881Speter          /* We don't print the "(bin)" notice for binary files when
1568251881Speter             replacing, only when adding.  So we don't bother to get
1569251881Speter             the mime-type here. */
1570251881Speter          if (item->copyfrom_url)
1571251881Speter            notify = svn_wc_create_notify(npath,
1572251881Speter                                          svn_wc_notify_commit_copied_replaced,
1573251881Speter                                          pool);
1574251881Speter          else
1575251881Speter            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1576251881Speter                                          pool);
1577251881Speter
1578251881Speter        }
1579251881Speter      else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1580251881Speter        {
1581251881Speter          notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1582251881Speter                                        pool);
1583251881Speter        }
1584251881Speter      else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1585251881Speter        {
1586251881Speter          if (item->copyfrom_url)
1587251881Speter            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied,
1588251881Speter                                          pool);
1589251881Speter          else
1590251881Speter            notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1591251881Speter                                          pool);
1592251881Speter
1593251881Speter          if (item->kind == svn_node_file)
1594251881Speter            {
1595251881Speter              const svn_string_t *propval;
1596251881Speter
1597251881Speter              SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath,
1598251881Speter                                       SVN_PROP_MIME_TYPE, pool, pool));
1599251881Speter
1600251881Speter              if (propval)
1601251881Speter                notify->mime_type = propval->data;
1602251881Speter            }
1603251881Speter        }
1604251881Speter      else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1605251881Speter               || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1606251881Speter        {
1607251881Speter          notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1608251881Speter                                        pool);
1609251881Speter          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1610251881Speter            notify->content_state = svn_wc_notify_state_changed;
1611251881Speter          else
1612251881Speter            notify->content_state = svn_wc_notify_state_unchanged;
1613251881Speter          if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1614251881Speter            notify->prop_state = svn_wc_notify_state_changed;
1615251881Speter          else
1616251881Speter            notify->prop_state = svn_wc_notify_state_unchanged;
1617251881Speter        }
1618251881Speter      else
1619251881Speter        notify = NULL;
1620251881Speter
1621251881Speter      if (notify)
1622251881Speter        {
1623251881Speter          notify->kind = item->kind;
1624251881Speter          notify->path_prefix = icb->notify_path_prefix;
1625251881Speter          (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1626251881Speter        }
1627251881Speter    }
1628251881Speter
1629251881Speter  /* If this item is supposed to be deleted, do so. */
1630251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1631251881Speter    {
1632251881Speter      SVN_ERR_ASSERT(parent_baton);
1633251881Speter      err = editor->delete_entry(path, item->revision,
1634251881Speter                                 parent_baton, pool);
1635251881Speter
1636251881Speter      if (err)
1637251881Speter        goto fixup_error;
1638251881Speter    }
1639251881Speter
1640251881Speter  /* If this item is supposed to be added, do so. */
1641251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1642251881Speter    {
1643251881Speter      if (kind == svn_node_file)
1644251881Speter        {
1645251881Speter          SVN_ERR_ASSERT(parent_baton);
1646251881Speter          err = editor->add_file(
1647251881Speter                   path, parent_baton, item->copyfrom_url,
1648251881Speter                   item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1649251881Speter                   file_pool, &file_baton);
1650251881Speter        }
1651251881Speter      else /* May be svn_node_none when adding parent dirs for a copy. */
1652251881Speter        {
1653251881Speter          SVN_ERR_ASSERT(parent_baton);
1654251881Speter          err = editor->add_directory(
1655251881Speter                   path, parent_baton, item->copyfrom_url,
1656251881Speter                   item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1657251881Speter                   pool, dir_baton);
1658251881Speter        }
1659251881Speter
1660251881Speter      if (err)
1661251881Speter        goto fixup_error;
1662251881Speter
1663251881Speter      /* Set other prop-changes, if available in the baton */
1664251881Speter      if (item->outgoing_prop_changes)
1665251881Speter        {
1666251881Speter          svn_prop_t *prop;
1667251881Speter          apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1668251881Speter          int ctr;
1669251881Speter          for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1670251881Speter            {
1671251881Speter              prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1672251881Speter              if (kind == svn_node_file)
1673251881Speter                {
1674251881Speter                  err = editor->change_file_prop(file_baton, prop->name,
1675251881Speter                                                 prop->value, pool);
1676251881Speter                }
1677251881Speter              else
1678251881Speter                {
1679251881Speter                  err = editor->change_dir_prop(*dir_baton, prop->name,
1680251881Speter                                                prop->value, pool);
1681251881Speter                }
1682251881Speter
1683251881Speter              if (err)
1684251881Speter                goto fixup_error;
1685251881Speter            }
1686251881Speter        }
1687251881Speter    }
1688251881Speter
1689251881Speter  /* Now handle property mods. */
1690251881Speter  if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1691251881Speter    {
1692251881Speter      if (kind == svn_node_file)
1693251881Speter        {
1694251881Speter          if (! file_baton)
1695251881Speter            {
1696251881Speter              SVN_ERR_ASSERT(parent_baton);
1697251881Speter              err = editor->open_file(path, parent_baton,
1698251881Speter                                      item->revision,
1699251881Speter                                      file_pool, &file_baton);
1700251881Speter
1701251881Speter              if (err)
1702251881Speter                goto fixup_error;
1703251881Speter            }
1704251881Speter        }
1705251881Speter      else
1706251881Speter        {
1707251881Speter          if (! *dir_baton)
1708251881Speter            {
1709251881Speter              if (! parent_baton)
1710251881Speter                {
1711251881Speter                  err = editor->open_root(icb->edit_baton, item->revision,
1712251881Speter                                          pool, dir_baton);
1713251881Speter                }
1714251881Speter              else
1715251881Speter                {
1716251881Speter                  err = editor->open_directory(path, parent_baton,
1717251881Speter                                               item->revision,
1718251881Speter                                               pool, dir_baton);
1719251881Speter                }
1720251881Speter
1721251881Speter              if (err)
1722251881Speter                goto fixup_error;
1723251881Speter            }
1724251881Speter        }
1725251881Speter
1726251881Speter      /* When committing a directory that no longer exists in the
1727251881Speter         repository, a "not found" error does not occur immediately
1728251881Speter         upon opening the directory.  It appears here during the delta
1729251881Speter         transmisssion. */
1730251881Speter      err = svn_wc_transmit_prop_deltas2(
1731251881Speter              ctx->wc_ctx, local_abspath, editor,
1732251881Speter              (kind == svn_node_dir) ? *dir_baton : file_baton, pool);
1733251881Speter
1734251881Speter      if (err)
1735251881Speter        goto fixup_error;
1736251881Speter
1737251881Speter      /* Make any additional client -> repository prop changes. */
1738251881Speter      if (item->outgoing_prop_changes)
1739251881Speter        {
1740251881Speter          svn_prop_t *prop;
1741251881Speter          int i;
1742251881Speter
1743251881Speter          for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1744251881Speter            {
1745251881Speter              prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1746251881Speter                                   svn_prop_t *);
1747251881Speter              if (kind == svn_node_file)
1748251881Speter                {
1749251881Speter                  err = editor->change_file_prop(file_baton, prop->name,
1750251881Speter                                           prop->value, pool);
1751251881Speter                }
1752251881Speter              else
1753251881Speter                {
1754251881Speter                  err = editor->change_dir_prop(*dir_baton, prop->name,
1755251881Speter                                          prop->value, pool);
1756251881Speter                }
1757251881Speter
1758251881Speter              if (err)
1759251881Speter                goto fixup_error;
1760251881Speter            }
1761251881Speter        }
1762251881Speter    }
1763251881Speter
1764251881Speter  /* Finally, handle text mods (in that we need to open a file if it
1765251881Speter     hasn't already been opened, and we need to put the file baton in
1766251881Speter     our FILES hash). */
1767251881Speter  if ((kind == svn_node_file)
1768251881Speter      && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1769251881Speter    {
1770251881Speter      struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1771251881Speter
1772251881Speter      if (! file_baton)
1773251881Speter        {
1774251881Speter          SVN_ERR_ASSERT(parent_baton);
1775251881Speter          err = editor->open_file(path, parent_baton,
1776251881Speter                                    item->revision,
1777251881Speter                                    file_pool, &file_baton);
1778251881Speter
1779251881Speter          if (err)
1780251881Speter            goto fixup_error;
1781251881Speter        }
1782251881Speter
1783251881Speter      /* Add this file mod to the FILE_MODS hash. */
1784251881Speter      mod->item = item;
1785251881Speter      mod->file_baton = file_baton;
1786251881Speter      svn_hash_sets(file_mods, item->session_relpath, mod);
1787251881Speter    }
1788251881Speter  else if (file_baton)
1789251881Speter    {
1790251881Speter      /* Close any outstanding file batons that didn't get caught by
1791251881Speter         the "has local mods" conditional above. */
1792251881Speter      err = editor->close_file(file_baton, NULL, file_pool);
1793251881Speter
1794251881Speter      if (err)
1795251881Speter        goto fixup_error;
1796251881Speter    }
1797251881Speter
1798251881Speter  return SVN_NO_ERROR;
1799251881Speter
1800251881Speterfixup_error:
1801251881Speter  return svn_error_trace(fixup_commit_error(local_abspath,
1802251881Speter                                            icb->base_url,
1803251881Speter                                            path, kind,
1804251881Speter                                            err, ctx, pool));
1805251881Speter}
1806251881Speter
1807251881Spetersvn_error_t *
1808251881Spetersvn_client__do_commit(const char *base_url,
1809251881Speter                      const apr_array_header_t *commit_items,
1810251881Speter                      const svn_delta_editor_t *editor,
1811251881Speter                      void *edit_baton,
1812251881Speter                      const char *notify_path_prefix,
1813251881Speter                      apr_hash_t **sha1_checksums,
1814251881Speter                      svn_client_ctx_t *ctx,
1815251881Speter                      apr_pool_t *result_pool,
1816251881Speter                      apr_pool_t *scratch_pool)
1817251881Speter{
1818251881Speter  apr_hash_t *file_mods = apr_hash_make(scratch_pool);
1819251881Speter  apr_hash_t *items_hash = apr_hash_make(scratch_pool);
1820251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1821251881Speter  apr_hash_index_t *hi;
1822251881Speter  int i;
1823251881Speter  struct item_commit_baton cb_baton;
1824251881Speter  apr_array_header_t *paths =
1825251881Speter    apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *));
1826251881Speter
1827251881Speter  /* Ditto for the checksums. */
1828251881Speter  if (sha1_checksums)
1829251881Speter    *sha1_checksums = apr_hash_make(result_pool);
1830251881Speter
1831251881Speter  /* Build a hash from our COMMIT_ITEMS array, keyed on the
1832251881Speter     relative paths (which come from the item URLs).  And
1833251881Speter     keep an array of those decoded paths, too.  */
1834251881Speter  for (i = 0; i < commit_items->nelts; i++)
1835251881Speter    {
1836251881Speter      svn_client_commit_item3_t *item =
1837251881Speter        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1838251881Speter      const char *path = item->session_relpath;
1839251881Speter      svn_hash_sets(items_hash, path, item);
1840251881Speter      APR_ARRAY_PUSH(paths, const char *) = path;
1841251881Speter    }
1842251881Speter
1843251881Speter  /* Setup the callback baton. */
1844251881Speter  cb_baton.editor = editor;
1845251881Speter  cb_baton.edit_baton = edit_baton;
1846251881Speter  cb_baton.file_mods = file_mods;
1847251881Speter  cb_baton.notify_path_prefix = notify_path_prefix;
1848251881Speter  cb_baton.ctx = ctx;
1849251881Speter  cb_baton.commit_items = items_hash;
1850251881Speter  cb_baton.base_url = base_url;
1851251881Speter
1852251881Speter  /* Drive the commit editor! */
1853251881Speter  SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1854251881Speter                                 do_item_commit, &cb_baton, scratch_pool));
1855251881Speter
1856251881Speter  /* Transmit outstanding text deltas. */
1857251881Speter  for (hi = apr_hash_first(scratch_pool, file_mods);
1858251881Speter       hi;
1859251881Speter       hi = apr_hash_next(hi))
1860251881Speter    {
1861251881Speter      struct file_mod_t *mod = svn__apr_hash_index_val(hi);
1862251881Speter      const svn_client_commit_item3_t *item = mod->item;
1863251881Speter      const svn_checksum_t *new_text_base_md5_checksum;
1864251881Speter      const svn_checksum_t *new_text_base_sha1_checksum;
1865251881Speter      svn_boolean_t fulltext = FALSE;
1866251881Speter      svn_error_t *err;
1867251881Speter
1868251881Speter      svn_pool_clear(iterpool);
1869251881Speter
1870251881Speter      /* Transmit the entry. */
1871251881Speter      if (ctx->cancel_func)
1872251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1873251881Speter
1874251881Speter      if (ctx->notify_func2)
1875251881Speter        {
1876251881Speter          svn_wc_notify_t *notify;
1877251881Speter          notify = svn_wc_create_notify(item->path,
1878251881Speter                                        svn_wc_notify_commit_postfix_txdelta,
1879251881Speter                                        iterpool);
1880251881Speter          notify->kind = svn_node_file;
1881251881Speter          notify->path_prefix = notify_path_prefix;
1882251881Speter          ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
1883251881Speter        }
1884251881Speter
1885251881Speter      /* If the node has no history, transmit full text */
1886251881Speter      if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1887251881Speter          && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY))
1888251881Speter        fulltext = TRUE;
1889251881Speter
1890251881Speter      err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum,
1891251881Speter                                         &new_text_base_sha1_checksum,
1892251881Speter                                         ctx->wc_ctx, item->path,
1893251881Speter                                         fulltext, editor, mod->file_baton,
1894251881Speter                                         result_pool, iterpool);
1895251881Speter
1896251881Speter      if (err)
1897251881Speter        {
1898251881Speter          svn_pool_destroy(iterpool); /* Close tempfiles */
1899251881Speter          return svn_error_trace(fixup_commit_error(item->path,
1900251881Speter                                                    base_url,
1901251881Speter                                                    item->session_relpath,
1902251881Speter                                                    svn_node_file,
1903251881Speter                                                    err, ctx, scratch_pool));
1904251881Speter        }
1905251881Speter
1906251881Speter      if (sha1_checksums)
1907251881Speter        svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum);
1908251881Speter    }
1909251881Speter
1910251881Speter  svn_pool_destroy(iterpool);
1911251881Speter
1912251881Speter  /* Close the edit. */
1913251881Speter  return svn_error_trace(editor->close_edit(edit_baton, scratch_pool));
1914251881Speter}
1915251881Speter
1916251881Speter
1917251881Spetersvn_error_t *
1918251881Spetersvn_client__get_log_msg(const char **log_msg,
1919251881Speter                        const char **tmp_file,
1920251881Speter                        const apr_array_header_t *commit_items,
1921251881Speter                        svn_client_ctx_t *ctx,
1922251881Speter                        apr_pool_t *pool)
1923251881Speter{
1924251881Speter  if (ctx->log_msg_func3)
1925251881Speter    {
1926251881Speter      /* The client provided a callback function for the current API.
1927251881Speter         Forward the call to it directly. */
1928251881Speter      return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1929251881Speter                                   ctx->log_msg_baton3, pool);
1930251881Speter    }
1931251881Speter  else if (ctx->log_msg_func2 || ctx->log_msg_func)
1932251881Speter    {
1933251881Speter      /* The client provided a pre-1.5 (or pre-1.3) API callback
1934251881Speter         function.  Convert the commit_items list to the appropriate
1935251881Speter         type, and forward call to it. */
1936251881Speter      svn_error_t *err;
1937251881Speter      apr_pool_t *scratch_pool = svn_pool_create(pool);
1938251881Speter      apr_array_header_t *old_commit_items =
1939251881Speter        apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*));
1940251881Speter
1941251881Speter      int i;
1942251881Speter      for (i = 0; i < commit_items->nelts; i++)
1943251881Speter        {
1944251881Speter          svn_client_commit_item3_t *item =
1945251881Speter            APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1946251881Speter
1947251881Speter          if (ctx->log_msg_func2)
1948251881Speter            {
1949251881Speter              svn_client_commit_item2_t *old_item =
1950251881Speter                apr_pcalloc(scratch_pool, sizeof(*old_item));
1951251881Speter
1952251881Speter              old_item->path = item->path;
1953251881Speter              old_item->kind = item->kind;
1954251881Speter              old_item->url = item->url;
1955251881Speter              old_item->revision = item->revision;
1956251881Speter              old_item->copyfrom_url = item->copyfrom_url;
1957251881Speter              old_item->copyfrom_rev = item->copyfrom_rev;
1958251881Speter              old_item->state_flags = item->state_flags;
1959251881Speter              old_item->wcprop_changes = item->incoming_prop_changes;
1960251881Speter
1961251881Speter              APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
1962251881Speter                old_item;
1963251881Speter            }
1964251881Speter          else /* ctx->log_msg_func */
1965251881Speter            {
1966251881Speter              svn_client_commit_item_t *old_item =
1967251881Speter                apr_pcalloc(scratch_pool, sizeof(*old_item));
1968251881Speter
1969251881Speter              old_item->path = item->path;
1970251881Speter              old_item->kind = item->kind;
1971251881Speter              old_item->url = item->url;
1972251881Speter              /* The pre-1.3 API used the revision field for copyfrom_rev
1973251881Speter                 and revision depeding of copyfrom_url. */
1974251881Speter              old_item->revision = item->copyfrom_url ?
1975251881Speter                item->copyfrom_rev : item->revision;
1976251881Speter              old_item->copyfrom_url = item->copyfrom_url;
1977251881Speter              old_item->state_flags = item->state_flags;
1978251881Speter              old_item->wcprop_changes = item->incoming_prop_changes;
1979251881Speter
1980251881Speter              APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
1981251881Speter                old_item;
1982251881Speter            }
1983251881Speter        }
1984251881Speter
1985251881Speter      if (ctx->log_msg_func2)
1986251881Speter        err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items,
1987251881Speter                                    ctx->log_msg_baton2, pool);
1988251881Speter      else
1989251881Speter        err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
1990251881Speter                                   ctx->log_msg_baton, pool);
1991251881Speter      svn_pool_destroy(scratch_pool);
1992251881Speter      return err;
1993251881Speter    }
1994251881Speter  else
1995251881Speter    {
1996251881Speter      /* No log message callback was provided by the client. */
1997251881Speter      *log_msg = "";
1998251881Speter      *tmp_file = NULL;
1999251881Speter      return SVN_NO_ERROR;
2000251881Speter    }
2001251881Speter}
2002251881Speter
2003251881Spetersvn_error_t *
2004251881Spetersvn_client__ensure_revprop_table(apr_hash_t **revprop_table_out,
2005251881Speter                                 const apr_hash_t *revprop_table_in,
2006251881Speter                                 const char *log_msg,
2007251881Speter                                 svn_client_ctx_t *ctx,
2008251881Speter                                 apr_pool_t *pool)
2009251881Speter{
2010251881Speter  apr_hash_t *new_revprop_table;
2011251881Speter  if (revprop_table_in)
2012251881Speter    {
2013251881Speter      if (svn_prop_has_svn_prop(revprop_table_in, pool))
2014251881Speter        return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2015251881Speter                                _("Standard properties can't be set "
2016251881Speter                                  "explicitly as revision properties"));
2017251881Speter      new_revprop_table = apr_hash_copy(pool, revprop_table_in);
2018251881Speter    }
2019251881Speter  else
2020251881Speter    {
2021251881Speter      new_revprop_table = apr_hash_make(pool);
2022251881Speter    }
2023251881Speter  svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG,
2024251881Speter                svn_string_create(log_msg, pool));
2025251881Speter  *revprop_table_out = new_revprop_table;
2026251881Speter  return SVN_NO_ERROR;
2027251881Speter}
2028