1 /*
2  * commit.c:  wrappers around wc commit functionality.
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 /* ==================================================================== */
25 
26 
27 
28 /*** Includes. ***/
29 
30 #include <string.h>
31 #include <apr_strings.h>
32 #include <apr_hash.h>
33 #include "svn_hash.h"
34 #include "svn_wc.h"
35 #include "svn_ra.h"
36 #include "svn_client.h"
37 #include "svn_string.h"
38 #include "svn_pools.h"
39 #include "svn_error.h"
40 #include "svn_error_codes.h"
41 #include "svn_dirent_uri.h"
42 #include "svn_path.h"
43 #include "svn_sorts.h"
44 
45 #include "client.h"
46 #include "private/svn_wc_private.h"
47 #include "private/svn_ra_private.h"
48 #include "private/svn_sorts_private.h"
49 
50 #include "svn_private_config.h"
51 
52 struct capture_baton_t {
53   svn_commit_callback2_t original_callback;
54   void *original_baton;
55 
56   svn_commit_info_t **info;
57   apr_pool_t *pool;
58 };
59 
60 
61 static svn_error_t *
capture_commit_info(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)62 capture_commit_info(const svn_commit_info_t *commit_info,
63                     void *baton,
64                     apr_pool_t *pool)
65 {
66   struct capture_baton_t *cb = baton;
67 
68   *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
69 
70   if (cb->original_callback)
71     SVN_ERR((cb->original_callback)(commit_info, cb->original_baton, pool));
72 
73   return SVN_NO_ERROR;
74 }
75 
76 
77 static svn_error_t *
get_ra_editor(const svn_delta_editor_t ** editor,void ** edit_baton,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,const char * log_msg,const apr_array_header_t * commit_items,const apr_hash_t * revprop_table,apr_hash_t * lock_tokens,svn_boolean_t keep_locks,svn_commit_callback2_t commit_callback,void * commit_baton,apr_pool_t * pool)78 get_ra_editor(const svn_delta_editor_t **editor,
79               void **edit_baton,
80               svn_ra_session_t *ra_session,
81               svn_client_ctx_t *ctx,
82               const char *log_msg,
83               const apr_array_header_t *commit_items,
84               const apr_hash_t *revprop_table,
85               apr_hash_t *lock_tokens,
86               svn_boolean_t keep_locks,
87               svn_commit_callback2_t commit_callback,
88               void *commit_baton,
89               apr_pool_t *pool)
90 {
91   apr_hash_t *commit_revprops;
92   apr_hash_t *relpath_map = NULL;
93 
94   SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
95                                            log_msg, ctx, pool));
96 
97 #ifdef ENABLE_EV2_SHIMS
98   if (commit_items)
99     {
100       int i;
101       apr_pool_t *iterpool = svn_pool_create(pool);
102 
103       relpath_map = apr_hash_make(pool);
104       for (i = 0; i < commit_items->nelts; i++)
105         {
106           svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
107                                                   svn_client_commit_item3_t *);
108           const char *relpath;
109 
110           if (!item->path)
111             continue;
112 
113           svn_pool_clear(iterpool);
114           SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
115                                           NULL, NULL,
116                                           ctx->wc_ctx, item->path, FALSE, pool,
117                                           iterpool));
118           if (relpath)
119             svn_hash_sets(relpath_map, relpath, item->path);
120         }
121       svn_pool_destroy(iterpool);
122     }
123 #endif
124 
125   /* Fetch RA commit editor. */
126   SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
127                         svn_client__get_shim_callbacks(ctx->wc_ctx,
128                                                        relpath_map, pool)));
129   SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
130                                     commit_revprops, commit_callback,
131                                     commit_baton, lock_tokens, keep_locks,
132                                     pool));
133 
134   return SVN_NO_ERROR;
135 }
136 
137 
138 /*** Public Interfaces. ***/
139 
140 static svn_error_t *
reconcile_errors(svn_error_t * commit_err,svn_error_t * unlock_err,svn_error_t * bump_err,apr_pool_t * pool)141 reconcile_errors(svn_error_t *commit_err,
142                  svn_error_t *unlock_err,
143                  svn_error_t *bump_err,
144                  apr_pool_t *pool)
145 {
146   svn_error_t *err;
147 
148   /* Early release (for good behavior). */
149   if (! (commit_err || unlock_err || bump_err))
150     return SVN_NO_ERROR;
151 
152   /* If there was a commit error, start off our error chain with
153      that. */
154   if (commit_err)
155     {
156       commit_err = svn_error_quick_wrap
157         (commit_err, _("Commit failed (details follow):"));
158       err = commit_err;
159     }
160 
161   /* Else, create a new "general" error that will lead off the errors
162      that follow. */
163   else
164     err = svn_error_create(SVN_ERR_BASE, NULL,
165                            _("Commit succeeded, but other errors follow:"));
166 
167   /* If there was an unlock error... */
168   if (unlock_err)
169     {
170       /* Wrap the error with some headers. */
171       unlock_err = svn_error_quick_wrap
172         (unlock_err, _("Error unlocking locked dirs (details follow):"));
173 
174       /* Append this error to the chain. */
175       svn_error_compose(err, unlock_err);
176     }
177 
178   /* If there was a bumping error... */
179   if (bump_err)
180     {
181       /* Wrap the error with some headers. */
182       bump_err = svn_error_quick_wrap
183         (bump_err, _("Error bumping revisions post-commit (details follow):"));
184 
185       /* Append this error to the chain. */
186       svn_error_compose(err, bump_err);
187     }
188 
189   return err;
190 }
191 
192 /* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
193    to a new hashtable allocated in POOL.  *RESULT is set to point to this
194    new hash table.  *RESULT will be keyed on const char * URI-decoded paths
195    relative to BASE_URL.  The lock tokens will not be duplicated. */
196 static svn_error_t *
collect_lock_tokens(apr_hash_t ** result,apr_hash_t * all_tokens,const char * base_url,apr_pool_t * pool)197 collect_lock_tokens(apr_hash_t **result,
198                     apr_hash_t *all_tokens,
199                     const char *base_url,
200                     apr_pool_t *pool)
201 {
202   apr_hash_index_t *hi;
203 
204   *result = apr_hash_make(pool);
205 
206   for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
207     {
208       const char *url = apr_hash_this_key(hi);
209       const char *token = apr_hash_this_val(hi);
210       const char *relpath = svn_uri_skip_ancestor(base_url, url, pool);
211 
212       if (relpath)
213         {
214           svn_hash_sets(*result, relpath, token);
215         }
216     }
217 
218   return SVN_NO_ERROR;
219 }
220 
221 /* Put ITEM onto QUEUE, allocating it in QUEUE's pool...
222  * If a checksum is provided, it can be the MD5 and/or the SHA1. */
223 static svn_error_t *
post_process_commit_item(svn_wc_committed_queue_t * queue,const svn_client_commit_item3_t * item,svn_wc_context_t * wc_ctx,svn_boolean_t keep_changelists,svn_boolean_t keep_locks,svn_boolean_t commit_as_operations,const svn_checksum_t * sha1_checksum,apr_pool_t * scratch_pool)224 post_process_commit_item(svn_wc_committed_queue_t *queue,
225                          const svn_client_commit_item3_t *item,
226                          svn_wc_context_t *wc_ctx,
227                          svn_boolean_t keep_changelists,
228                          svn_boolean_t keep_locks,
229                          svn_boolean_t commit_as_operations,
230                          const svn_checksum_t *sha1_checksum,
231                          apr_pool_t *scratch_pool)
232 {
233   svn_boolean_t loop_recurse = FALSE;
234   svn_boolean_t remove_lock;
235 
236   if (! commit_as_operations
237       && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
238       && (item->kind == svn_node_dir)
239       && (item->copyfrom_url))
240     loop_recurse = TRUE;
241 
242   remove_lock = (! keep_locks && (item->state_flags
243                                        & (SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN
244                                           | SVN_CLIENT_COMMIT_ITEM_ADD
245                                           | SVN_CLIENT_COMMIT_ITEM_DELETE)));
246 
247   /* When the node was deleted (or replaced), we need to always remove the
248      locks, as they're invalidated on the server. We cannot honor the
249      SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell
250      us whether we have locked children. */
251   if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
252     remove_lock = TRUE;
253 
254   return svn_error_trace(
255          svn_wc_queue_committed4(queue, wc_ctx, item->path,
256                                  loop_recurse,
257                                  0 != (item->state_flags &
258                                        (SVN_CLIENT_COMMIT_ITEM_ADD
259                                         | SVN_CLIENT_COMMIT_ITEM_DELETE
260                                         | SVN_CLIENT_COMMIT_ITEM_TEXT_MODS
261                                         | SVN_CLIENT_COMMIT_ITEM_PROP_MODS)),
262                                  item->incoming_prop_changes,
263                                  remove_lock, !keep_changelists,
264                                  sha1_checksum, scratch_pool));
265 }
266 
267 /* Given a list of committables described by their common base abspath
268    BASE_ABSPATH and a list of relative dirents TARGET_RELPATHS determine
269    which absolute paths must be locked to commit all these targets and
270    return this as a const char * array in LOCK_TARGETS
271 
272    Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporary
273    storage */
274 static svn_error_t *
determine_lock_targets(apr_array_header_t ** lock_targets,svn_wc_context_t * wc_ctx,const char * base_abspath,const apr_array_header_t * target_relpaths,apr_pool_t * result_pool,apr_pool_t * scratch_pool)275 determine_lock_targets(apr_array_header_t **lock_targets,
276                        svn_wc_context_t *wc_ctx,
277                        const char *base_abspath,
278                        const apr_array_header_t *target_relpaths,
279                        apr_pool_t *result_pool,
280                        apr_pool_t *scratch_pool)
281 {
282   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
283   apr_hash_t *wc_items; /* const char *wcroot -> apr_array_header_t */
284   apr_hash_index_t *hi;
285   int i;
286 
287   wc_items = apr_hash_make(scratch_pool);
288 
289   /* Create an array of targets for each working copy used */
290   for (i = 0; i < target_relpaths->nelts; i++)
291     {
292       const char *target_abspath;
293       const char *wcroot_abspath;
294       apr_array_header_t *wc_targets;
295       svn_error_t *err;
296       const char *target_relpath = APR_ARRAY_IDX(target_relpaths, i,
297                                                  const char *);
298 
299       svn_pool_clear(iterpool);
300       target_abspath = svn_dirent_join(base_abspath, target_relpath,
301                                        scratch_pool);
302 
303       err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath,
304                                iterpool, iterpool);
305 
306       if (err)
307         {
308           if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
309             {
310               svn_error_clear(err);
311               continue;
312             }
313           return svn_error_trace(err);
314         }
315 
316       wc_targets = svn_hash_gets(wc_items, wcroot_abspath);
317 
318       if (! wc_targets)
319         {
320           wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *));
321           svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath),
322                         wc_targets);
323         }
324 
325       APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath;
326     }
327 
328   *lock_targets = apr_array_make(result_pool, apr_hash_count(wc_items),
329                                  sizeof(const char *));
330 
331   /* For each working copy determine where to lock */
332   for (hi = apr_hash_first(scratch_pool, wc_items);
333        hi;
334        hi = apr_hash_next(hi))
335     {
336       const char *common;
337       const char *wcroot_abspath = apr_hash_this_key(hi);
338       apr_array_header_t *wc_targets = apr_hash_this_val(hi);
339 
340       svn_pool_clear(iterpool);
341 
342       if (wc_targets->nelts == 1)
343         {
344           const char *target_abspath;
345           target_abspath = APR_ARRAY_IDX(wc_targets, 0, const char *);
346 
347           if (! strcmp(wcroot_abspath, target_abspath))
348             {
349               APR_ARRAY_PUSH(*lock_targets, const char *)
350                       = apr_pstrdup(result_pool, target_abspath);
351             }
352           else
353             {
354               /* Lock the parent to allow deleting the target */
355               APR_ARRAY_PUSH(*lock_targets, const char *)
356                       = svn_dirent_dirname(target_abspath, result_pool);
357             }
358         }
359       else if (wc_targets->nelts > 1)
360         {
361           SVN_ERR(svn_dirent_condense_targets(&common, &wc_targets, wc_targets,
362                                               FALSE, iterpool, iterpool));
363 
364           svn_sort__array(wc_targets, svn_sort_compare_paths);
365 
366           if (wc_targets->nelts == 0
367               || !svn_path_is_empty(APR_ARRAY_IDX(wc_targets, 0, const char*))
368               || !strcmp(common, wcroot_abspath))
369             {
370               APR_ARRAY_PUSH(*lock_targets, const char *)
371                     = apr_pstrdup(result_pool, common);
372             }
373           else
374             {
375               /* Lock the parent to allow deleting the target */
376               APR_ARRAY_PUSH(*lock_targets, const char *)
377                        = svn_dirent_dirname(common, result_pool);
378             }
379         }
380     }
381 
382   svn_pool_destroy(iterpool);
383   return SVN_NO_ERROR;
384 }
385 
386 /* Baton for check_url_kind */
387 struct check_url_kind_baton
388 {
389   apr_pool_t *pool;
390   svn_ra_session_t *session;
391   const char *repos_root_url;
392   svn_client_ctx_t *ctx;
393 };
394 
395 /* Implements svn_client__check_url_kind_t for svn_client_commit5 */
396 static svn_error_t *
check_url_kind(void * baton,svn_node_kind_t * kind,const char * url,svn_revnum_t revision,apr_pool_t * scratch_pool)397 check_url_kind(void *baton,
398                svn_node_kind_t *kind,
399                const char *url,
400                svn_revnum_t revision,
401                apr_pool_t *scratch_pool)
402 {
403   struct check_url_kind_baton *cukb = baton;
404 
405   /* If we don't have a session or can't use the session, get one */
406   if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url))
407     {
408       SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx,
409                                           cukb->pool, scratch_pool));
410       SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url,
411                                      cukb->pool));
412     }
413   else
414     SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
415 
416   return svn_error_trace(
417                 svn_ra_check_path(cukb->session, "", revision,
418                                   kind, scratch_pool));
419 }
420 
421 /* Recurse into every target in REL_TARGETS, finding committable externals
422  * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS
423  * are assumed to be / will be created relative to BASE_ABSPATH. The remaining
424  * arguments correspond to those of svn_client_commit6(). */
425 static svn_error_t*
append_externals_as_explicit_targets(apr_array_header_t * rel_targets,const char * base_abspath,svn_boolean_t include_file_externals,svn_boolean_t include_dir_externals,svn_depth_t depth,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)426 append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
427                                      const char *base_abspath,
428                                      svn_boolean_t include_file_externals,
429                                      svn_boolean_t include_dir_externals,
430                                      svn_depth_t depth,
431                                      svn_client_ctx_t *ctx,
432                                      apr_pool_t *result_pool,
433                                      apr_pool_t *scratch_pool)
434 {
435   int rel_targets_nelts_fixed;
436   int i;
437   apr_pool_t *iterpool;
438 
439   if (! (include_file_externals || include_dir_externals))
440     return SVN_NO_ERROR;
441 
442   /* Easy part of applying DEPTH to externals. */
443   if (depth == svn_depth_empty)
444     {
445       /* Don't recurse. */
446       return SVN_NO_ERROR;
447     }
448 
449   /* Iterate *and* grow REL_TARGETS at the same time. */
450   rel_targets_nelts_fixed = rel_targets->nelts;
451 
452   iterpool = svn_pool_create(scratch_pool);
453 
454   for (i = 0; i < rel_targets_nelts_fixed; i++)
455     {
456       int j;
457       const char *target;
458       apr_array_header_t *externals = NULL;
459 
460       svn_pool_clear(iterpool);
461 
462       target = svn_dirent_join(base_abspath,
463                                APR_ARRAY_IDX(rel_targets, i, const char *),
464                                iterpool);
465 
466       /* ### TODO: Possible optimization: No need to do this for file targets.
467        * ### But what's cheaper, stat'ing the file system or querying the db?
468        * ### --> future. */
469 
470       SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx,
471                                                   target, depth,
472                                                   iterpool, iterpool));
473 
474       if (externals != NULL)
475         {
476           const char *rel_target;
477 
478           for (j = 0; j < externals->nelts; j++)
479             {
480               svn_wc__committable_external_info_t *xinfo =
481                          APR_ARRAY_IDX(externals, j,
482                                        svn_wc__committable_external_info_t *);
483 
484               if ((xinfo->kind == svn_node_file && ! include_file_externals)
485                   || (xinfo->kind == svn_node_dir && ! include_dir_externals))
486                 continue;
487 
488               rel_target = svn_dirent_skip_ancestor(base_abspath,
489                                                     xinfo->local_abspath);
490 
491               SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0');
492 
493               APR_ARRAY_PUSH(rel_targets, const char *) =
494                                          apr_pstrdup(result_pool, rel_target);
495             }
496         }
497     }
498 
499   svn_pool_destroy(iterpool);
500   return SVN_NO_ERROR;
501 }
502 
503 /* Crawl the working copy for commit items.
504  */
505 static svn_error_t *
harvest_committables(apr_array_header_t ** commit_items_p,apr_hash_t ** committables_by_path_p,apr_hash_t ** lock_tokens,const char * base_dir_abspath,const apr_array_header_t * targets,int depth_empty_start,svn_depth_t depth,svn_boolean_t just_locked,const apr_array_header_t * changelists,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)506 harvest_committables(apr_array_header_t **commit_items_p,
507                      apr_hash_t **committables_by_path_p,
508                      apr_hash_t **lock_tokens,
509                      const char *base_dir_abspath,
510                      const apr_array_header_t *targets,
511                      int depth_empty_start,
512                      svn_depth_t depth,
513                      svn_boolean_t just_locked,
514                      const apr_array_header_t *changelists,
515                      svn_client_ctx_t *ctx,
516                      apr_pool_t *result_pool,
517                      apr_pool_t *scratch_pool)
518 {
519   struct check_url_kind_baton cukb;
520   svn_client__committables_t *committables;
521   apr_hash_index_t *hi;
522 
523   /* Prepare for when we have a copy containing not-present nodes. */
524   cukb.pool = scratch_pool;
525   cukb.session = NULL; /* ### Can we somehow reuse session? */
526   cukb.repos_root_url = NULL;
527   cukb.ctx = ctx;
528 
529   SVN_ERR(svn_client__harvest_committables(&committables, lock_tokens,
530                                            base_dir_abspath, targets,
531                                            depth_empty_start, depth,
532                                            just_locked,
533                                            changelists,
534                                            check_url_kind, &cukb,
535                                            ctx, result_pool, scratch_pool));
536   if (apr_hash_count(committables->by_repository) == 0)
537     {
538       *commit_items_p = NULL;
539       return SVN_NO_ERROR;  /* Nothing to do */
540     }
541   else if (apr_hash_count(committables->by_repository) > 1)
542     {
543       return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
544           _("Commit can only commit to a single repository at a time.\n"
545             "Are all targets part of the same working copy?"));
546     }
547 
548   hi = apr_hash_first(scratch_pool, committables->by_repository);
549   *commit_items_p = apr_hash_this_val(hi);
550   if (committables_by_path_p)
551     *committables_by_path_p = committables->by_path;
552   return SVN_NO_ERROR;
553 }
554 
555 svn_error_t *
svn_client__wc_replay(const char * src_wc_abspath,const apr_array_header_t * targets,svn_depth_t depth,const apr_array_header_t * changelists,const svn_delta_editor_t * editor,void * edit_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)556 svn_client__wc_replay(const char *src_wc_abspath,
557                       const apr_array_header_t *targets,
558                       svn_depth_t depth,
559                       const apr_array_header_t *changelists,
560                       const svn_delta_editor_t *editor,
561                       void *edit_baton,
562                       svn_wc_notify_func2_t notify_func,
563                       void *notify_baton,
564                       svn_client_ctx_t *ctx,
565                       apr_pool_t *pool)
566 {
567   const char *base_abspath;
568   apr_array_header_t *rel_targets;
569   apr_hash_t *lock_tokens;
570   apr_array_header_t *commit_items;
571   svn_client__pathrev_t *base;
572   const char *base_url;
573   svn_wc_notify_func2_t saved_notify_func;
574   void *saved_notify_baton;
575 
576   /* Condense the target list. This makes all targets absolute. */
577   SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
578                                       FALSE, pool, pool));
579 
580   /* No targets means nothing to commit, so just return. */
581   if (base_abspath == NULL)
582     return SVN_NO_ERROR;
583 
584   SVN_ERR_ASSERT(rel_targets != NULL);
585 
586   /* If we calculated only a base and no relative targets, this
587      must mean that we are being asked to commit (effectively) a
588      single path. */
589   if (rel_targets->nelts == 0)
590     APR_ARRAY_PUSH(rel_targets, const char *) = "";
591 
592   /* Crawl the working copy for commit items. */
593   SVN_ERR(harvest_committables(&commit_items, NULL /*committables_by_path_p*/,
594                                &lock_tokens,
595                                base_abspath, rel_targets,
596                                -1 /*depth_empty_start*/,
597                                depth,
598                                FALSE /*just_locked*/,
599                                changelists,
600                                ctx, pool, pool));
601   if (!commit_items)
602     {
603       return SVN_NO_ERROR;
604     }
605 
606   SVN_ERR(svn_client__wc_node_get_base(&base,
607                                        src_wc_abspath, ctx->wc_ctx, pool, pool));
608   base_url = base->url;
609   /* Sort our COMMIT_ITEMS by URL and find their relative URL-paths. */
610   SVN_ERR(svn_client__condense_commit_items2(base_url, commit_items, pool));
611 
612   saved_notify_func = ctx->notify_func2;
613   saved_notify_baton = ctx->notify_baton2;
614   ctx->notify_func2 = notify_func;
615   ctx->notify_baton2 = notify_baton;
616   /* BASE_URL is only used here in notifications & errors */
617   SVN_ERR(svn_client__do_commit(base_url, commit_items,
618                                 editor, edit_baton,
619                                 NULL /*notify_prefix*/, NULL /*sha1_checksums*/,
620                                 ctx, pool, pool));
621   ctx->notify_func2 = saved_notify_func;
622   ctx->notify_baton2 = saved_notify_baton;
623   return SVN_NO_ERROR;
624 }
625 
626 svn_error_t *
svn_client_commit6(const apr_array_header_t * targets,svn_depth_t depth,svn_boolean_t keep_locks,svn_boolean_t keep_changelists,svn_boolean_t commit_as_operations,svn_boolean_t include_file_externals,svn_boolean_t include_dir_externals,const apr_array_header_t * changelists,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)627 svn_client_commit6(const apr_array_header_t *targets,
628                    svn_depth_t depth,
629                    svn_boolean_t keep_locks,
630                    svn_boolean_t keep_changelists,
631                    svn_boolean_t commit_as_operations,
632                    svn_boolean_t include_file_externals,
633                    svn_boolean_t include_dir_externals,
634                    const apr_array_header_t *changelists,
635                    const apr_hash_t *revprop_table,
636                    svn_commit_callback2_t commit_callback,
637                    void *commit_baton,
638                    svn_client_ctx_t *ctx,
639                    apr_pool_t *pool)
640 {
641   const svn_delta_editor_t *editor;
642   void *edit_baton;
643   struct capture_baton_t cb;
644   svn_ra_session_t *ra_session;
645   const char *log_msg;
646   const char *base_abspath;
647   const char *base_url;
648   apr_array_header_t *rel_targets;
649   apr_array_header_t *lock_targets;
650   apr_array_header_t *locks_obtained;
651   apr_hash_t *committables_by_path;
652   apr_hash_t *lock_tokens;
653   apr_hash_t *sha1_checksums;
654   apr_array_header_t *commit_items;
655   svn_error_t *cmt_err = SVN_NO_ERROR;
656   svn_error_t *bump_err = SVN_NO_ERROR;
657   svn_error_t *unlock_err = SVN_NO_ERROR;
658   svn_boolean_t commit_in_progress = FALSE;
659   svn_boolean_t timestamp_sleep = FALSE;
660   svn_commit_info_t *commit_info = NULL;
661   apr_pool_t *iterpool = svn_pool_create(pool);
662   const char *current_abspath;
663   const char *notify_prefix;
664   int depth_empty_after = -1;
665   apr_hash_t *move_youngest = NULL;
666   int i;
667 
668   SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude);
669 
670   /* Committing URLs doesn't make sense, so error if it's tried. */
671   for (i = 0; i < targets->nelts; i++)
672     {
673       const char *target = APR_ARRAY_IDX(targets, i, const char *);
674       if (svn_path_is_url(target))
675         return svn_error_createf
676           (SVN_ERR_ILLEGAL_TARGET, NULL,
677            _("'%s' is a URL, but URLs cannot be commit targets"), target);
678     }
679 
680   /* Condense the target list. This makes all targets absolute. */
681   SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
682                                       FALSE, pool, iterpool));
683 
684   /* No targets means nothing to commit, so just return. */
685   if (base_abspath == NULL)
686     return SVN_NO_ERROR;
687 
688   SVN_ERR_ASSERT(rel_targets != NULL);
689 
690   /* If we calculated only a base and no relative targets, this
691      must mean that we are being asked to commit (effectively) a
692      single path. */
693   if (rel_targets->nelts == 0)
694     APR_ARRAY_PUSH(rel_targets, const char *) = "";
695 
696   if (include_file_externals || include_dir_externals)
697     {
698       if (depth != svn_depth_unknown && depth != svn_depth_infinity)
699         {
700           /* All targets after this will be handled as depth empty */
701           depth_empty_after = rel_targets->nelts;
702         }
703 
704       SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath,
705                                                    include_file_externals,
706                                                    include_dir_externals,
707                                                    depth, ctx,
708                                                    pool, pool));
709     }
710 
711   SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
712                                  rel_targets, pool, iterpool));
713 
714   locks_obtained = apr_array_make(pool, lock_targets->nelts,
715                                   sizeof(const char *));
716 
717   for (i = 0; i < lock_targets->nelts; i++)
718     {
719       const char *lock_root;
720       const char *target = APR_ARRAY_IDX(lock_targets, i, const char *);
721 
722       svn_pool_clear(iterpool);
723 
724       cmt_err = svn_error_trace(
725                     svn_wc__acquire_write_lock(&lock_root, ctx->wc_ctx, target,
726                                            FALSE, pool, iterpool));
727 
728       if (cmt_err)
729         goto cleanup;
730 
731       APR_ARRAY_PUSH(locks_obtained, const char *) = lock_root;
732     }
733 
734   /* Determine prefix to strip from the commit notify messages */
735   SVN_ERR(svn_dirent_get_absolute(&current_abspath, "", pool));
736   notify_prefix = svn_dirent_get_longest_ancestor(current_abspath,
737                                                   base_abspath,
738                                                   pool);
739 
740   /* Crawl the working copy for commit items. */
741   cmt_err = svn_error_trace(
742               harvest_committables(&commit_items, &committables_by_path,
743                                    &lock_tokens,
744                                    base_abspath,
745                                    rel_targets,
746                                    depth_empty_after,
747                                    depth,
748                                    ! keep_locks,
749                                    changelists,
750                                    ctx,
751                                    pool,
752                                    iterpool));
753   svn_pool_clear(iterpool);
754 
755   if (cmt_err)
756     goto cleanup;
757 
758   if (!commit_items)
759     {
760       goto cleanup; /* Nothing to do */
761     }
762 
763   /* If our array of targets contains only locks (and no actual file
764      or prop modifications), then we return here to avoid committing a
765      revision with no changes. */
766   {
767     svn_boolean_t found_changed_path = FALSE;
768 
769     for (i = 0; i < commit_items->nelts; ++i)
770       {
771         svn_client_commit_item3_t *item =
772           APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
773 
774         if (item->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
775           {
776             found_changed_path = TRUE;
777             break;
778           }
779       }
780 
781     if (!found_changed_path)
782       goto cleanup;
783   }
784 
785   /* For every target that was moved verify that both halves of the
786    * move are part of the commit. */
787   for (i = 0; i < commit_items->nelts; i++)
788     {
789       svn_client_commit_item3_t *item =
790         APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
791 
792       svn_pool_clear(iterpool);
793 
794       if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
795         {
796           /* ### item->moved_from_abspath contains the move origin */
797           const char *moved_from_abspath;
798           const char *delete_op_root_abspath;
799 
800           cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
801                                       &moved_from_abspath,
802                                       &delete_op_root_abspath,
803                                       ctx->wc_ctx, item->path,
804                                       iterpool, iterpool));
805           if (cmt_err)
806             goto cleanup;
807 
808           if (moved_from_abspath && delete_op_root_abspath)
809             {
810               svn_client_commit_item3_t *delete_half =
811                 svn_hash_gets(committables_by_path, delete_op_root_abspath);
812 
813               if (!delete_half)
814                 {
815                   cmt_err = svn_error_createf(
816                               SVN_ERR_ILLEGAL_TARGET, NULL,
817                               _("Cannot commit '%s' because it was moved from "
818                                 "'%s' which is not part of the commit; both "
819                                 "sides of the move must be committed together"),
820                               svn_dirent_local_style(item->path, iterpool),
821                               svn_dirent_local_style(delete_op_root_abspath,
822                                                      iterpool));
823 
824                   if (ctx->notify_func2)
825                     {
826                       svn_wc_notify_t *notify;
827                       notify = svn_wc_create_notify(
828                                     delete_op_root_abspath,
829                                     svn_wc_notify_failed_requires_target,
830                                     iterpool);
831                       notify->err = cmt_err;
832 
833                       ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
834                     }
835 
836                   goto cleanup;
837                 }
838               else if (delete_half->revision == item->copyfrom_rev)
839                 {
840                   /* Ok, now we know that we perform an out-of-date check
841                      on the copyfrom location. Remember this for a fixup
842                      round right before committing. */
843 
844                   if (!move_youngest)
845                     move_youngest = apr_hash_make(pool);
846 
847                   svn_hash_sets(move_youngest, item->path, item);
848                 }
849             }
850         }
851 
852       if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
853         {
854           const char *moved_to_abspath;
855           const char *copy_op_root_abspath;
856 
857           cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
858                                       &moved_to_abspath,
859                                       &copy_op_root_abspath,
860                                       ctx->wc_ctx, item->path,
861                                       iterpool, iterpool));
862           if (cmt_err)
863             goto cleanup;
864 
865           if (moved_to_abspath && copy_op_root_abspath &&
866               strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
867               svn_hash_gets(committables_by_path, copy_op_root_abspath)
868               == NULL)
869             {
870               cmt_err = svn_error_createf(
871                           SVN_ERR_ILLEGAL_TARGET, NULL,
872                          _("Cannot commit '%s' because it was moved to '%s' "
873                            "which is not part of the commit; both sides of "
874                            "the move must be committed together"),
875                          svn_dirent_local_style(item->path, iterpool),
876                          svn_dirent_local_style(copy_op_root_abspath,
877                                                 iterpool));
878 
879               if (ctx->notify_func2)
880                 {
881                     svn_wc_notify_t *notify;
882                     notify = svn_wc_create_notify(
883                                 copy_op_root_abspath,
884                                 svn_wc_notify_failed_requires_target,
885                                 iterpool);
886                     notify->err = cmt_err;
887 
888                     ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
889                 }
890 
891               goto cleanup;
892             }
893         }
894     }
895 
896   /* Go get a log message.  If an error occurs, or no log message is
897      specified, abort the operation. */
898   if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
899     {
900       const char *tmp_file;
901       cmt_err = svn_error_trace(
902                      svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
903                                              ctx, pool));
904 
905       if (cmt_err || (! log_msg))
906         goto cleanup;
907     }
908   else
909     log_msg = "";
910 
911   /* Sort and condense our COMMIT_ITEMS. */
912   cmt_err = svn_error_trace(svn_client__condense_commit_items(&base_url,
913                                                               commit_items,
914                                                               pool));
915 
916   if (cmt_err)
917     goto cleanup;
918 
919   /* Collect our lock tokens with paths relative to base_url. */
920   cmt_err = svn_error_trace(collect_lock_tokens(&lock_tokens, lock_tokens,
921                                                 base_url, pool));
922 
923   if (cmt_err)
924     goto cleanup;
925 
926   cb.original_callback = commit_callback;
927   cb.original_baton = commit_baton;
928   cb.info = &commit_info;
929   cb.pool = pool;
930 
931   /* Get the RA editor from the first lock target, rather than BASE_ABSPATH.
932    * When committing from multiple WCs, BASE_ABSPATH might be an unrelated
933    * parent of nested working copies. We don't support commits to multiple
934    * repositories so using the first WC to get the RA session is safe. */
935   cmt_err = svn_error_trace(
936               svn_client__open_ra_session_internal(&ra_session, NULL, base_url,
937                                                    APR_ARRAY_IDX(lock_targets,
938                                                                  0,
939                                                                  const char *),
940                                                    commit_items,
941                                                    TRUE, TRUE, ctx,
942                                                    pool, pool));
943 
944   if (cmt_err)
945     goto cleanup;
946 
947   if (move_youngest != NULL)
948     {
949       apr_hash_index_t *hi;
950       svn_revnum_t youngest;
951 
952       SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool));
953 
954       for (hi = apr_hash_first(iterpool, move_youngest);
955            hi;
956            hi = apr_hash_next(hi))
957         {
958           svn_client_commit_item3_t *item = apr_hash_this_val(hi);
959 
960           /* We delete the original side with its original revision and will
961              receive an out-of-date error if that node changed since that
962              revision.
963 
964              The copy is of that same revision and we know that this revision
965              didn't change between this revision and youngest. So we can just
966              as well commit a copy from youngest.
967 
968             Note that it is still possible to see gaps between the delete and
969             copy revisions as the repository might handle multiple commits
970             at the same time (or when an out of date proxy is involved), but
971             in general it should decrease the number of gaps. */
972 
973           if (item->copyfrom_rev < youngest)
974             item->copyfrom_rev = youngest;
975         }
976     }
977 
978   cmt_err = svn_error_trace(
979               get_ra_editor(&editor, &edit_baton, ra_session, ctx,
980                             log_msg, commit_items, revprop_table,
981                             lock_tokens, keep_locks, capture_commit_info,
982                             &cb, pool));
983 
984   if (cmt_err)
985     goto cleanup;
986 
987   /* Make a note that we have a commit-in-progress. */
988   commit_in_progress = TRUE;
989 
990   /* We'll assume that, once we pass this point, we are going to need to
991    * sleep for timestamps.  Really, we may not need to do unless and until
992    * we reach the point where we post-commit 'bump' the WC metadata. */
993   timestamp_sleep = TRUE;
994 
995   /* Perform the commit. */
996   cmt_err = svn_error_trace(
997               svn_client__do_commit(base_url, commit_items, editor, edit_baton,
998                                     notify_prefix, &sha1_checksums, ctx, pool,
999                                     iterpool));
1000 
1001   /* Handle a successful commit. */
1002   if ((! cmt_err)
1003       || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
1004     {
1005       svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
1006 
1007       /* Make a note that our commit is finished. */
1008       commit_in_progress = FALSE;
1009 
1010       for (i = 0; i < commit_items->nelts; i++)
1011         {
1012           svn_client_commit_item3_t *item
1013             = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1014 
1015           svn_pool_clear(iterpool);
1016           bump_err = post_process_commit_item(
1017                        queue, item, ctx->wc_ctx,
1018                        keep_changelists, keep_locks, commit_as_operations,
1019                        svn_hash_gets(sha1_checksums, item->path),
1020                        iterpool);
1021           if (bump_err)
1022             goto cleanup;
1023         }
1024 
1025       SVN_ERR_ASSERT(commit_info);
1026       bump_err = svn_wc_process_committed_queue2(
1027                    queue, ctx->wc_ctx,
1028                    commit_info->revision,
1029                    commit_info->date,
1030                    commit_info->author,
1031                    ctx->cancel_func, ctx->cancel_baton,
1032                    iterpool);
1033 
1034       if (bump_err)
1035         goto cleanup;
1036     }
1037 
1038  cleanup:
1039   /* Sleep to ensure timestamp integrity.  BASE_ABSPATH may have been
1040      removed by the commit or it may the common ancestor of multiple
1041      working copies. */
1042   if (timestamp_sleep)
1043     {
1044       const char *sleep_abspath;
1045       svn_error_t *err = svn_wc__get_wcroot(&sleep_abspath, ctx->wc_ctx,
1046                                             base_abspath, pool, pool);
1047       if (err)
1048         {
1049           svn_error_clear(err);
1050           sleep_abspath = base_abspath;
1051         }
1052 
1053       svn_io_sleep_for_timestamps(sleep_abspath, pool);
1054     }
1055 
1056   /* Abort the commit if it is still in progress. */
1057   svn_pool_clear(iterpool); /* Close open handles before aborting */
1058   if (commit_in_progress)
1059     cmt_err = svn_error_compose_create(cmt_err,
1060                                        editor->abort_edit(edit_baton, pool));
1061 
1062   /* A bump error is likely to occur while running a working copy log file,
1063      explicitly unlocking and removing temporary files would be wrong in
1064      that case.  A commit error (cmt_err) should only occur before any
1065      attempt to modify the working copy, so it doesn't prevent explicit
1066      clean-up. */
1067   if (! bump_err)
1068     {
1069       /* Release all locks we obtained */
1070       for (i = 0; i < locks_obtained->nelts; i++)
1071         {
1072           const char *lock_root = APR_ARRAY_IDX(locks_obtained, i,
1073                                                 const char *);
1074 
1075           svn_pool_clear(iterpool);
1076 
1077           unlock_err = svn_error_compose_create(
1078                            svn_wc__release_write_lock(ctx->wc_ctx, lock_root,
1079                                                       iterpool),
1080                            unlock_err);
1081         }
1082     }
1083 
1084   svn_pool_destroy(iterpool);
1085 
1086   return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err,
1087                                           pool));
1088 }
1089