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