In the command-line client, forbid
[svn.git] / subversion / libsvn_client / commit_util.c
blobcdf2bb1165285fd197eee4bf722ef14689007223
1 /*
2 * commit_util.c: Driver for the WC commit process.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
22 #include <string.h>
24 #include <apr_pools.h>
25 #include <apr_hash.h>
27 #include "client.h"
28 #include "svn_path.h"
29 #include "svn_types.h"
30 #include "svn_pools.h"
31 #include "svn_props.h"
32 #include "svn_md5.h"
33 #include "svn_iter.h"
35 #include <assert.h>
36 #include <stdlib.h> /* for qsort() */
38 #include "svn_private_config.h"
39 #include "private/svn_wc_private.h"
41 /*** Uncomment this to turn on commit driver debugging. ***/
43 #define SVN_CLIENT_COMMIT_DEBUG
48 /*** Harvesting Commit Candidates ***/
51 /* Add a new commit candidate (described by all parameters except
52 `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's
53 members are allocated out of the COMMITTABLES hash pool. */
54 static void
55 add_committable(apr_hash_t *committables,
56 const char *path,
57 svn_node_kind_t kind,
58 const char *url,
59 svn_revnum_t revision,
60 const char *copyfrom_url,
61 svn_revnum_t copyfrom_rev,
62 apr_byte_t state_flags)
64 apr_pool_t *pool = apr_hash_pool_get(committables);
65 const char *repos_name = SVN_CLIENT__SINGLE_REPOS_NAME;
66 apr_array_header_t *array;
67 svn_client_commit_item3_t *new_item;
69 /* Sanity checks. */
70 assert(path && url);
72 /* ### todo: Get the canonical repository for this item, which will
73 be the real key for the COMMITTABLES hash, instead of the above
74 bogosity. */
75 array = apr_hash_get(committables, repos_name, APR_HASH_KEY_STRING);
77 /* E-gads! There is no array for this repository yet! Oh, no
78 problem, we'll just create (and add to the hash) one. */
79 if (array == NULL)
81 array = apr_array_make(pool, 1, sizeof(new_item));
82 apr_hash_set(committables, repos_name, APR_HASH_KEY_STRING, array);
85 /* Now update pointer values, ensuring that their allocations live
86 in POOL. */
87 svn_client_commit_item_create((const svn_client_commit_item3_t **) &new_item,
88 pool);
89 new_item->path = apr_pstrdup(pool, path);
90 new_item->kind = kind;
91 new_item->url = apr_pstrdup(pool, url);
92 new_item->revision = revision;
93 new_item->copyfrom_url = copyfrom_url
94 ? apr_pstrdup(pool, copyfrom_url) : NULL;
95 new_item->copyfrom_rev = copyfrom_rev;
96 new_item->state_flags = state_flags;
97 new_item->incoming_prop_changes = apr_array_make(pool, 1,
98 sizeof(svn_prop_t *));
100 /* Now, add the commit item to the array. */
101 APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item;
105 static svn_error_t *
106 check_prop_mods(svn_boolean_t *props_changed,
107 svn_boolean_t *eol_prop_changed,
108 const char *path,
109 svn_wc_adm_access_t *adm_access,
110 apr_pool_t *pool)
112 apr_array_header_t *prop_mods;
113 int i;
115 *eol_prop_changed = *props_changed = FALSE;
116 SVN_ERR(svn_wc_props_modified_p(props_changed, path, adm_access, pool));
117 if (! *props_changed)
118 return SVN_NO_ERROR;
119 SVN_ERR(svn_wc_get_prop_diffs(&prop_mods, NULL, path, adm_access, pool));
120 for (i = 0; i < prop_mods->nelts; i++)
122 svn_prop_t *prop_mod = &APR_ARRAY_IDX(prop_mods, i, svn_prop_t);
123 if (strcmp(prop_mod->name, SVN_PROP_EOL_STYLE) == 0)
124 *eol_prop_changed = TRUE;
126 return SVN_NO_ERROR;
130 /* If there is a commit item for PATH in COMMITTABLES, return it, else
131 return NULL. Use POOL for temporary allocation only. */
132 static svn_client_commit_item3_t *
133 look_up_committable(apr_hash_t *committables,
134 const char *path,
135 apr_pool_t *pool)
137 apr_hash_index_t *hi;
139 for (hi = apr_hash_first(pool, committables); hi; hi = apr_hash_next(hi))
141 void *val;
142 apr_array_header_t *these_committables;
143 int i;
145 apr_hash_this(hi, NULL, NULL, &val);
146 these_committables = val;
148 for (i = 0; i < these_committables->nelts; i++)
150 svn_client_commit_item3_t *this_committable
151 = APR_ARRAY_IDX(these_committables, i,
152 svn_client_commit_item3_t *);
154 if (strcmp(this_committable->path, path) == 0)
155 return this_committable;
159 return NULL;
162 /* This implements the svn_wc_entry_callbacks_t->found_entry interface. */
163 static svn_error_t *
164 add_lock_token(const char *path, const svn_wc_entry_t *entry,
165 void *walk_baton, apr_pool_t *pool)
167 apr_hash_t *lock_tokens = walk_baton;
168 apr_pool_t *token_pool = apr_hash_pool_get(lock_tokens);
170 /* I want every lock-token I can get my dirty hands on!
171 If this entry is switched, so what. We will send an irrelevant lock
172 token. */
173 if (entry->url && entry->lock_token)
174 apr_hash_set(lock_tokens, apr_pstrdup(token_pool, entry->url),
175 APR_HASH_KEY_STRING,
176 apr_pstrdup(token_pool, entry->lock_token));
178 return SVN_NO_ERROR;
181 /* Entry walker callback table to add lock tokens in an hierarchy. */
182 static svn_wc_entry_callbacks2_t add_tokens_callbacks = {
183 add_lock_token,
184 svn_client__default_walker_error_handler
187 /* Whether CHANGELIST_NAME is NULL, or ENTRY->changelist (which may be
188 NULL) matches CHANGELIST_NAME. */
189 static APR_INLINE svn_boolean_t
190 considered_committable(const char *changelist_name,
191 const svn_wc_entry_t *entry)
193 return (changelist_name == NULL ||
194 (entry->changelist &&
195 strcmp(changelist_name, entry->changelist) == 0) ? TRUE : FALSE);
198 /* Recursively search for commit candidates in (and under) PATH (with
199 entry ENTRY and ancestry URL), and add those candidates to
200 COMMITTABLES. If in ADDS_ONLY modes, only new additions are
201 recognized. COPYFROM_URL is the default copyfrom-url for children
202 of copied directories.
204 DEPTH indicates how to treat files and subdirectories of PATH when
205 PATH is itself a directory; see svn_client__harvest_committables()
206 for its behavior.
208 Lock tokens of candidates will be added to LOCK_TOKENS, if
209 non-NULL. JUST_LOCKED indicates whether to treat non-modified items with
210 lock tokens as commit candidates.
212 If in COPY_MODE, treat the entry as if it is destined to be added
213 with history as URL, and add 'deleted' entries to COMMITTABLES as
214 items to delete in the copy destination.
216 If CHANGELIST_NAME is non-NULL, then use it as a restrictive filter
217 when harvesting committables; that is, don't add a path to
218 COMMITTABLES unless it's a member of the changelist.
220 If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to see
221 if the user has cancelled the operation.
223 Any items added to COMMITTABLES are allocated from the COMITTABLES hash pool,
224 not POOL. POOL is used for temporary allocations. */
225 static svn_error_t *
226 harvest_committables(apr_hash_t *committables,
227 apr_hash_t *lock_tokens,
228 const char *path,
229 svn_wc_adm_access_t *adm_access,
230 const char *url,
231 const char *copyfrom_url,
232 const svn_wc_entry_t *entry,
233 const svn_wc_entry_t *parent_entry,
234 svn_boolean_t adds_only,
235 svn_boolean_t copy_mode,
236 svn_depth_t depth,
237 svn_boolean_t just_locked,
238 const char *changelist_name,
239 svn_client_ctx_t *ctx,
240 apr_pool_t *pool)
242 apr_hash_t *entries = NULL;
243 svn_boolean_t text_mod = FALSE, prop_mod = FALSE;
244 apr_byte_t state_flags = 0;
245 svn_node_kind_t kind;
246 const char *p_path;
247 svn_boolean_t tc, pc;
248 const char *cf_url = NULL;
249 svn_revnum_t cf_rev = entry->copyfrom_rev;
250 const svn_string_t *propval;
251 svn_boolean_t is_special;
252 apr_pool_t *token_pool = (lock_tokens ? apr_hash_pool_get(lock_tokens)
253 : NULL);
255 /* Early out if the item is already marked as committable. */
256 if (look_up_committable(committables, path, pool))
257 return SVN_NO_ERROR;
259 assert(entry);
260 assert(url);
262 if (ctx->cancel_func)
263 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
265 /* Make P_PATH the parent dir. */
266 p_path = svn_path_dirname(path, pool);
268 /* Return error on unknown path kinds. We check both the entry and
269 the node itself, since a path might have changed kind since its
270 entry was written. */
271 if ((entry->kind != svn_node_file) && (entry->kind != svn_node_dir))
272 return svn_error_createf
273 (SVN_ERR_NODE_UNKNOWN_KIND, NULL, _("Unknown entry kind for '%s'"),
274 svn_path_local_style(path, pool));
276 SVN_ERR(svn_io_check_special_path(path, &kind, &is_special, pool));
278 if ((kind != svn_node_file)
279 && (kind != svn_node_dir)
280 && (kind != svn_node_none))
282 return svn_error_createf
283 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
284 _("Unknown entry kind for '%s'"),
285 svn_path_local_style(path, pool));
288 /* Verify that the node's type has not changed before attempting to
289 commit. */
290 SVN_ERR(svn_wc_prop_get(&propval, SVN_PROP_SPECIAL, path, adm_access,
291 pool));
293 if ((((! propval) && (is_special))
294 #ifdef HAVE_SYMLINK
295 || ((propval) && (! is_special))
296 #endif /* HAVE_SYMLINK */
297 ) && (kind != svn_node_none))
299 return svn_error_createf
300 (SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
301 _("Entry '%s' has unexpectedly changed special status"),
302 svn_path_local_style(path, pool));
305 /* Get a fully populated entry for PATH if we can, and check for
306 conflicts. If this is a directory ... */
307 if (entry->kind == svn_node_dir)
309 /* ... then try to read its own entries file so we have a full
310 entry for it (we were going to have to do this eventually to
311 recurse anyway, so... ) */
312 svn_error_t *err;
313 const svn_wc_entry_t *e = NULL;
314 err = svn_wc_entries_read(&entries, adm_access, copy_mode, pool);
316 /* If we failed to get an entries hash for the directory, no
317 sweat. Cleanup and move along. */
318 if (err)
320 svn_error_clear(err);
321 entries = NULL;
324 /* If we got an entries hash, and the "this dir" entry is
325 present, override our current ENTRY with it, and check for
326 conflicts. */
327 if ((entries) && ((e = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
328 APR_HASH_KEY_STRING))))
330 entry = e;
331 SVN_ERR(svn_wc_conflicted_p(&tc, &pc, path, entry, pool));
334 /* No new entry? Just check the parent's pointer for
335 conflicts. */
336 else
338 SVN_ERR(svn_wc_conflicted_p(&tc, &pc, p_path, entry, pool));
342 /* If this is not a directory, check for conflicts using the
343 parent's path. */
344 else
346 SVN_ERR(svn_wc_conflicted_p(&tc, &pc, p_path, entry, pool));
349 /* Bail now if any conflicts exist for the ENTRY. */
350 if (tc || pc)
352 /* Paths in conflict which are not part of our changelist should
353 be ignored. */
354 if (considered_committable(changelist_name, entry))
355 return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
356 _("Aborting commit: '%s' remains in conflict"),
357 svn_path_local_style(path, pool));
360 /* If we have our own URL, and we're NOT in COPY_MODE, it wins over
361 the telescoping one(s). In COPY_MODE, URL will always be the
362 URL-to-be of the copied item. */
363 if ((entry->url) && (! copy_mode))
364 url = entry->url;
366 /* Check for the deletion case. Deletes occur only when not in
367 "adds-only mode". We use the SVN_CLIENT_COMMIT_ITEM_DELETE flag
368 to represent two slightly different conditions:
370 - The entry is marked as 'deleted'. When copying a mixed-rev wc,
371 we still need to send a delete for that entry, otherwise the
372 object will wrongly exist in the repository copy.
374 - The entry is scheduled for deletion or replacement, which case
375 we need to send a delete either way.
377 if ((! adds_only)
378 && ((entry->deleted && entry->schedule == svn_wc_schedule_normal)
379 || (entry->schedule == svn_wc_schedule_delete)
380 || (entry->schedule == svn_wc_schedule_replace)))
382 state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
385 /* Check for the trivial addition case. Adds can be explicit
386 (schedule == add) or implicit (schedule == replace ::= delete+add).
387 We also note whether or not this is an add with history here. */
388 if ((entry->schedule == svn_wc_schedule_add)
389 || (entry->schedule == svn_wc_schedule_replace))
391 state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
392 if (entry->copyfrom_url)
394 state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
395 cf_url = entry->copyfrom_url;
396 adds_only = FALSE;
398 else
400 adds_only = TRUE;
404 /* Check for the copied-subtree addition case. */
405 if ((entry->copied || copy_mode)
406 && (! entry->deleted)
407 && (entry->schedule == svn_wc_schedule_normal))
409 svn_revnum_t p_rev = entry->revision - 1; /* arbitrary non-equal value */
410 svn_boolean_t wc_root = FALSE;
412 /* If this is not a WC root then its parent's revision is
413 admissible for comparative purposes. */
414 SVN_ERR(svn_wc_is_wc_root(&wc_root, path, adm_access, pool));
415 if (! wc_root)
417 if (parent_entry)
418 p_rev = parent_entry->revision;
420 else if (! copy_mode)
421 return svn_error_createf
422 (SVN_ERR_WC_CORRUPT, NULL,
423 _("Did not expect '%s' to be a working copy root"),
424 svn_path_local_style(path, pool));
426 /* If the ENTRY's revision differs from that of its parent, we
427 have to explicitly commit ENTRY as a copy. */
428 if (entry->revision != p_rev)
430 state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD;
431 state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY;
432 adds_only = FALSE;
433 cf_rev = entry->revision;
434 if (copy_mode)
435 cf_url = entry->url;
436 else if (copyfrom_url)
437 cf_url = copyfrom_url;
438 else /* ### See issue #830 */
439 return svn_error_createf
440 (SVN_ERR_BAD_URL, NULL,
441 _("Commit item '%s' has copy flag but no copyfrom URL"),
442 svn_path_local_style(path, pool));
446 /* If an add is scheduled to occur, dig around for some more
447 information about it. */
448 if (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
450 svn_boolean_t eol_prop_changed;
452 /* See if there are property modifications to send. */
453 SVN_ERR(check_prop_mods(&prop_mod, &eol_prop_changed, path,
454 adm_access, pool));
456 /* Regular adds of files have text mods, but for copies we have
457 to test for textual mods. Directories simply don't have text! */
458 if (entry->kind == svn_node_file)
460 /* Check for text mods. If EOL_PROP_CHANGED is TRUE, then
461 we need to force a translated byte-for-byte comparison
462 against the text-base so that a timestamp comparison
463 won't bail out early. Depending on how the svn:eol-style
464 prop was changed, we might have to send new text to the
465 server to match the new newline style. */
466 if (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
467 SVN_ERR(svn_wc_text_modified_p(&text_mod, path,
468 eol_prop_changed,
469 adm_access, pool));
470 else
471 text_mod = TRUE;
475 /* Else, if we aren't deleting this item, we'll have to look for
476 local text or property mods to determine if the path might be
477 committable. */
478 else if (! (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
480 svn_boolean_t eol_prop_changed;
482 /* See if there are property modifications to send. */
483 SVN_ERR(check_prop_mods(&prop_mod, &eol_prop_changed, path,
484 adm_access, pool));
486 /* Check for text mods on files. If EOL_PROP_CHANGED is TRUE,
487 then we need to force a translated byte-for-byte comparison
488 against the text-base so that a timestamp comparison won't
489 bail out early. Depending on how the svn:eol-style prop was
490 changed, we might have to send new text to the server to
491 match the new newline style. */
492 if (entry->kind == svn_node_file)
493 SVN_ERR(svn_wc_text_modified_p(&text_mod, path, eol_prop_changed,
494 adm_access, pool));
497 /* Set text/prop modification flags accordingly. */
498 if (text_mod)
499 state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS;
500 if (prop_mod)
501 state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
503 /* If the entry has a lock token and it is already a commit candidate,
504 or the caller wants unmodified locked items to be treated as
505 such, note this fact. */
506 if (entry->lock_token
507 && (state_flags || just_locked))
508 state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN;
510 /* Now, if this is something to commit, add it to our list. */
511 if (state_flags)
513 if (considered_committable(changelist_name, entry))
515 /* Finally, add the committable item. */
516 add_committable(committables, path, entry->kind, url,
517 entry->revision,
518 cf_url,
519 cf_rev,
520 state_flags);
521 if (lock_tokens && entry->lock_token)
522 apr_hash_set(lock_tokens, apr_pstrdup(token_pool, url),
523 APR_HASH_KEY_STRING,
524 apr_pstrdup(token_pool, entry->lock_token));
528 /* For directories, recursively handle each entry according to depth
529 (except when the directory is being deleted, unless the deletion
530 is part of a replacement ... how confusing). */
531 if (entries && (depth > svn_depth_empty)
532 && ((! (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
533 || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)))
535 apr_hash_index_t *hi;
536 const svn_wc_entry_t *this_entry;
537 apr_pool_t *loop_pool = svn_pool_create(pool);
539 /* Loop over all other entries in this directory, skipping the
540 "this dir" entry. */
541 for (hi = apr_hash_first(pool, entries);
543 hi = apr_hash_next(hi))
545 const void *key;
546 void *val;
547 const char *name;
548 const char *full_path;
549 const char *used_url = NULL;
550 const char *name_uri = NULL;
551 const char *this_cf_url = cf_url ? cf_url : copyfrom_url;
552 svn_wc_adm_access_t *dir_access = adm_access;
554 svn_pool_clear(loop_pool);
556 /* Get the next entry. Name is an entry name; value is an
557 entry structure. */
558 apr_hash_this(hi, &key, NULL, &val);
559 name = key;
561 /* Skip "this dir" */
562 if (! strcmp(name, SVN_WC_ENTRY_THIS_DIR))
563 continue;
565 this_entry = val;
566 name_uri = svn_path_uri_encode(name, loop_pool);
568 full_path = svn_path_join(path, name, loop_pool);
569 if (this_cf_url)
570 this_cf_url = svn_path_join(this_cf_url, name_uri, loop_pool);
572 /* We'll use the entry's URL if it has one and if we aren't
573 in copy_mode, else, we'll just extend the parent's URL
574 with the entry's basename. */
575 if ((! this_entry->url) || (copy_mode))
576 used_url = svn_path_join(url, name_uri, loop_pool);
578 /* Recurse. */
579 if (this_entry->kind == svn_node_dir)
581 if (depth <= svn_depth_files)
583 /* Don't bother with any of this if it's a directory
584 and depth says not to go there. */
585 continue;
587 else
589 svn_error_t *lockerr;
590 lockerr = svn_wc_adm_retrieve(&dir_access, adm_access,
591 full_path, loop_pool);
593 if (lockerr)
595 if (lockerr->apr_err == SVN_ERR_WC_NOT_LOCKED)
597 /* A missing, schedule-delete child dir is
598 allowable. Just don't try to recurse. */
599 svn_node_kind_t childkind;
600 svn_error_t *err = svn_io_check_path(full_path,
601 &childkind,
602 loop_pool);
603 if (! err
604 && (childkind == svn_node_none)
605 && (this_entry->schedule
606 == svn_wc_schedule_delete))
608 if (considered_committable(changelist_name,
609 entry))
611 add_committable(
612 committables, full_path,
613 this_entry->kind, used_url,
614 SVN_INVALID_REVNUM,
615 NULL,
616 SVN_INVALID_REVNUM,
617 SVN_CLIENT_COMMIT_ITEM_DELETE);
618 svn_error_clear(lockerr);
619 continue; /* don't recurse! */
622 else
624 svn_error_clear(err);
625 return lockerr;
628 else
629 return lockerr;
633 else
635 dir_access = adm_access;
639 svn_depth_t depth_below_here = depth;
641 /* If depth is svn_depth_files, then we must be recursing
642 into a file, or else we wouldn't be here -- either way,
643 svn_depth_empty is the right depth to use. On the
644 other hand, if depth is svn_depth_immediates, then we
645 could be recursing into a directory or a file -- in
646 which case svn_depth_empty is *still* the right depth
647 to use. I know that sounds bizarre (one normally
648 expects to just do depth - 1) but it's correct. */
649 if (depth == svn_depth_immediates
650 || depth == svn_depth_files)
651 depth_below_here = svn_depth_empty;
653 SVN_ERR(harvest_committables
654 (committables, lock_tokens, full_path, dir_access,
655 used_url ? used_url : this_entry->url,
656 this_cf_url,
657 this_entry,
658 entry,
659 adds_only,
660 copy_mode,
661 depth_below_here,
662 just_locked,
663 changelist_name,
664 ctx,
665 loop_pool));
669 svn_pool_destroy(loop_pool);
672 /* Fetch lock tokens for descendants of deleted directories. */
673 if (lock_tokens && entry->kind == svn_node_dir
674 && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
676 SVN_ERR(svn_wc_walk_entries3(path, adm_access, &add_tokens_callbacks,
677 lock_tokens,
678 /* If a directory was deleted, everything
679 under it would better be deleted too,
680 so pass svn_depth_infinity not depth. */
681 svn_depth_infinity, FALSE,
682 ctx->cancel_func, ctx->cancel_baton,
683 pool));
686 return SVN_NO_ERROR;
690 /* BATON is an apr_hash_t * of harvested committables. */
691 static svn_error_t *
692 validate_dangler(void *baton,
693 const void *key, apr_ssize_t klen, void *val,
694 apr_pool_t *pool)
696 const char *dangling_parent = key;
697 const char *dangling_child = val;
699 /* The baton points to the committables hash */
700 if (! look_up_committable(baton, dangling_parent, pool))
702 return svn_error_createf
703 (SVN_ERR_ILLEGAL_TARGET, NULL,
704 _("'%s' is not under version control "
705 "and is not part of the commit, "
706 "yet its child '%s' is part of the commit"),
707 /* Probably one or both of these is an entry, but
708 safest to local_stylize just in case. */
709 svn_path_local_style(dangling_parent, pool),
710 svn_path_local_style(dangling_child, pool));
713 return SVN_NO_ERROR;
717 svn_error_t *
718 svn_client__harvest_committables(apr_hash_t **committables,
719 apr_hash_t **lock_tokens,
720 svn_wc_adm_access_t *parent_dir,
721 apr_array_header_t *targets,
722 svn_depth_t depth,
723 svn_boolean_t just_locked,
724 const char *changelist_name,
725 svn_client_ctx_t *ctx,
726 apr_pool_t *pool)
728 int i = 0;
729 svn_wc_adm_access_t *dir_access;
730 apr_pool_t *subpool = svn_pool_create(pool);
732 /* It's possible that one of the named targets has a parent that is
733 * itself scheduled for addition or replacement -- that is, the
734 * parent is not yet versioned in the repository. This is okay, as
735 * long as the parent itself is part of this same commit, either
736 * directly, or by virtue of a grandparent, great-grandparent, etc,
737 * being part of the commit.
739 * Since we don't know what's included in the commit until we've
740 * harvested all the targets, we can't reliably check this as we
741 * go. So in `danglers', we record named targets whose parents
742 * are unversioned, then after harvesting the total commit group, we
743 * check to make sure those parents are included.
745 * Each key of danglers is an unversioned parent. The (const char *)
746 * value is one of that parent's children which is named as part of
747 * the commit; the child is included only to make a better error
748 * message.
750 * (The reason we don't bother to check unnamed -- i.e, implicit --
751 * targets is that they can only join the commit if their parents
752 * did too, so this situation can't arise for them.)
754 apr_hash_t *danglers = apr_hash_make(pool);
756 /* Create the COMMITTABLES hash. */
757 *committables = apr_hash_make(pool);
759 /* And the LOCK_TOKENS dito. */
760 *lock_tokens = apr_hash_make(pool);
764 svn_wc_adm_access_t *adm_access;
765 const svn_wc_entry_t *entry;
766 const char *target;
768 svn_pool_clear(subpool);
769 /* Add the relative portion of our full path (if there are no
770 relative paths, TARGET will just be PARENT_DIR for a single
771 iteration. */
772 target = svn_path_join_many(subpool,
773 svn_wc_adm_access_path(parent_dir),
774 targets->nelts
775 ? APR_ARRAY_IDX(targets, i, const char *)
776 : NULL,
777 NULL);
779 /* No entry? This TARGET isn't even under version control! */
780 SVN_ERR(svn_wc_adm_probe_retrieve(&adm_access, parent_dir,
781 target, subpool));
782 SVN_ERR(svn_wc__entry_versioned(&entry, target, adm_access, FALSE,
783 subpool));
784 if (! entry->url)
785 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
786 _("Entry for '%s' has no URL"),
787 svn_path_local_style(target, pool));
789 /* We have to be especially careful around entries scheduled for
790 addition or replacement. */
791 if ((entry->schedule == svn_wc_schedule_add)
792 || (entry->schedule == svn_wc_schedule_replace))
794 const char *parent, *base_name;
795 svn_wc_adm_access_t *parent_access;
796 const svn_wc_entry_t *p_entry = NULL;
797 svn_error_t *err;
799 svn_path_split(target, &parent, &base_name, subpool);
800 err = svn_wc_adm_retrieve(&parent_access, parent_dir,
801 parent, subpool);
802 if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
804 svn_error_clear(err);
805 SVN_ERR(svn_wc_adm_open3(&parent_access, NULL, parent,
806 FALSE, 0, ctx->cancel_func,
807 ctx->cancel_baton, subpool));
809 else if (err)
811 return err;
814 SVN_ERR(svn_wc_entry(&p_entry, parent, parent_access,
815 FALSE, subpool));
816 if (! p_entry)
817 return svn_error_createf
818 (SVN_ERR_WC_CORRUPT, NULL,
819 _("'%s' is scheduled for addition within unversioned parent"),
820 svn_path_local_style(target, pool));
821 if ((p_entry->schedule == svn_wc_schedule_add)
822 || (p_entry->schedule == svn_wc_schedule_replace))
824 /* Copy the parent and target into pool; subpool
825 lasts only for this loop iteration, and we check
826 danglers after the loop is over. */
827 apr_hash_set(danglers, apr_pstrdup(pool, parent),
828 APR_HASH_KEY_STRING,
829 apr_pstrdup(pool, target));
833 /* If this entry is marked as 'copied' but scheduled normally, then
834 it should be the child of something else marked for addition with
835 history. */
836 if ((entry->copied) && (entry->schedule == svn_wc_schedule_normal))
837 return svn_error_createf
838 (SVN_ERR_ILLEGAL_TARGET, NULL,
839 _("Entry for '%s' is marked as 'copied' but is not itself scheduled"
840 "\nfor addition. Perhaps you're committing a target that is\n"
841 "inside an unversioned (or not-yet-versioned) directory?"),
842 svn_path_local_style(target, pool));
844 /* Handle our TARGET. */
845 SVN_ERR(svn_wc_adm_retrieve(&dir_access, parent_dir,
846 (entry->kind == svn_node_dir
847 ? target
848 : svn_path_dirname(target, subpool)),
849 subpool));
850 SVN_ERR(harvest_committables(*committables, *lock_tokens, target,
851 dir_access, entry->url, NULL,
852 entry, NULL, FALSE, FALSE, depth,
853 just_locked, changelist_name,
854 ctx, subpool));
856 i++;
858 while (i < targets->nelts);
860 if (changelist_name && apr_hash_count(*committables) == 0)
861 /* None of the paths we examined were in the changelist. */
862 return svn_error_createf(SVN_ERR_UNKNOWN_CHANGELIST, NULL,
863 _("Unknown changelist '%s'"), changelist_name);
865 /* Make sure that every path in danglers is part of the commit. */
866 SVN_ERR(svn_iter_apr_hash(NULL,
867 danglers, validate_dangler, *committables, pool));
869 svn_pool_destroy(subpool);
871 return SVN_NO_ERROR;
874 struct copy_committables_baton
876 svn_wc_adm_access_t *adm_access;
877 svn_client_ctx_t *ctx;
878 apr_hash_t *committables;
881 static svn_error_t *
882 harvest_copy_committables(void *baton, void *item, apr_pool_t *pool)
884 struct copy_committables_baton *btn = baton;
886 const svn_wc_entry_t *entry;
887 svn_client__copy_pair_t *pair =
888 *(svn_client__copy_pair_t **)item;
889 svn_wc_adm_access_t *dir_access;
891 /* Read the entry for this SRC. */
892 SVN_ERR(svn_wc__entry_versioned(&entry, pair->src, btn->adm_access, FALSE,
893 pool));
895 /* Get the right access baton for this SRC. */
896 if (entry->kind == svn_node_dir)
897 SVN_ERR(svn_wc_adm_retrieve(&dir_access, btn->adm_access, pair->src, pool));
898 else
899 SVN_ERR(svn_wc_adm_retrieve(&dir_access, btn->adm_access,
900 svn_path_dirname(pair->src, pool),
901 pool));
903 /* Handle this SRC. Because add_committable() uses the hash pool to
904 allocate the new commit_item, we can safely use the iterpool here. */
905 SVN_ERR(harvest_committables(btn->committables, NULL, pair->src,
906 dir_access, pair->dst, entry->url, entry,
907 NULL, FALSE, TRUE, svn_depth_infinity,
908 FALSE, NULL, btn->ctx, pool));
910 return SVN_NO_ERROR;
915 svn_error_t *
916 svn_client__get_copy_committables(apr_hash_t **committables,
917 const apr_array_header_t *copy_pairs,
918 svn_wc_adm_access_t *adm_access,
919 svn_client_ctx_t *ctx,
920 apr_pool_t *pool)
922 struct copy_committables_baton btn;
924 *committables = apr_hash_make(pool);
926 btn.adm_access = adm_access;
927 btn.ctx = ctx;
928 btn.committables = *committables;
930 /* For each copy pair, harvest the committables for that pair into the
931 committables hash. */
932 SVN_ERR(svn_iter_apr_array(NULL, copy_pairs,
933 harvest_copy_committables, &btn, pool));
935 return SVN_NO_ERROR;
939 int svn_client__sort_commit_item_urls(const void *a, const void *b)
941 const svn_client_commit_item3_t *item1
942 = *((const svn_client_commit_item3_t * const *) a);
943 const svn_client_commit_item3_t *item2
944 = *((const svn_client_commit_item3_t * const *) b);
945 return svn_path_compare_paths(item1->url, item2->url);
950 svn_error_t *
951 svn_client__condense_commit_items(const char **base_url,
952 apr_array_header_t *commit_items,
953 apr_pool_t *pool)
955 apr_array_header_t *ci = commit_items; /* convenience */
956 const char *url;
957 svn_client_commit_item3_t *item, *last_item = NULL;
958 int i;
960 assert(ci && ci->nelts);
962 /* Sort our commit items by their URLs. */
963 qsort(ci->elts, ci->nelts,
964 ci->elt_size, svn_client__sort_commit_item_urls);
966 /* Loop through the URLs, finding the longest usable ancestor common
967 to all of them, and making sure there are no duplicate URLs. */
968 for (i = 0; i < ci->nelts; i++)
970 item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
971 url = item->url;
973 if ((last_item) && (strcmp(last_item->url, url) == 0))
974 return svn_error_createf
975 (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL,
976 _("Cannot commit both '%s' and '%s' as they refer to the same URL"),
977 svn_path_local_style(item->path, pool),
978 svn_path_local_style(last_item->path, pool));
980 /* In the first iteration, our BASE_URL is just our only
981 encountered commit URL to date. After that, we find the
982 longest ancestor between the current BASE_URL and the current
983 commit URL. */
984 if (i == 0)
985 *base_url = apr_pstrdup(pool, url);
986 else
987 *base_url = svn_path_get_longest_ancestor(*base_url, url, pool);
989 /* If our BASE_URL is itself a to-be-committed item, and it is
990 anything other than an already-versioned directory with
991 property mods, we'll call its parent directory URL the
992 BASE_URL. Why? Because we can't have a file URL as our base
993 -- period -- and all other directory operations (removal,
994 addition, etc.) require that we open that directory's parent
995 dir first. */
996 /* ### I don't understand the strlen()s here, hmmm. -kff */
997 if ((strlen(*base_url) == strlen(url))
998 && (! ((item->kind == svn_node_dir)
999 && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS)))
1000 *base_url = svn_path_dirname(*base_url, pool);
1002 /* Stash our item here for the next iteration. */
1003 last_item = item;
1006 /* Now that we've settled on a *BASE_URL, go hack that base off
1007 of all of our URLs. */
1008 for (i = 0; i < ci->nelts; i++)
1010 svn_client_commit_item3_t *this_item
1011 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1012 int url_len = strlen(this_item->url);
1013 int base_url_len = strlen(*base_url);
1015 if (url_len > base_url_len)
1016 this_item->url = apr_pstrdup(pool, this_item->url + base_url_len + 1);
1017 else
1018 this_item->url = "";
1021 #ifdef SVN_CLIENT_COMMIT_DEBUG
1022 /* ### TEMPORARY CODE ### */
1023 fprintf(stderr, "COMMITTABLES: (base URL=%s)\n", *base_url);
1024 fprintf(stderr, " FLAGS REV REL-URL (COPY-URL)\n");
1025 for (i = 0; i < ci->nelts; i++)
1027 svn_client_commit_item3_t *this_item
1028 = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
1029 char flags[6];
1030 flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1031 ? 'a' : '-';
1032 flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1033 ? 'd' : '-';
1034 flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1035 ? 't' : '-';
1036 flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1037 ? 'p' : '-';
1038 flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1039 ? 'c' : '-';
1040 flags[5] = '\0';
1041 fprintf(stderr, " %s %6ld '%s' (%s)\n",
1042 flags,
1043 this_item->revision,
1044 this_item->url ? this_item->url : "",
1045 this_item->copyfrom_url ? this_item->copyfrom_url : "none");
1047 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1049 return SVN_NO_ERROR;
1053 struct file_mod_t
1055 svn_client_commit_item3_t *item;
1056 void *file_baton;
1060 /* A baton for use with the path-based editor driver */
1061 struct path_driver_cb_baton
1063 svn_wc_adm_access_t *adm_access; /* top-level access baton */
1064 const svn_delta_editor_t *editor; /* commit editor */
1065 void *edit_baton; /* commit editor's baton */
1066 apr_hash_t *file_mods; /* hash: path->file_mod_t */
1067 apr_hash_t *tempfiles; /* hash of tempfiles created */
1068 const char *notify_path_prefix; /* notification path prefix */
1069 svn_client_ctx_t *ctx; /* client context baton */
1070 apr_hash_t *commit_items; /* the committables */
1074 /* This implements svn_delta_path_driver_cb_func_t */
1075 static svn_error_t *
1076 do_item_commit(void **dir_baton,
1077 void *parent_baton,
1078 void *callback_baton,
1079 const char *path,
1080 apr_pool_t *pool)
1082 struct path_driver_cb_baton *cb_baton = callback_baton;
1083 svn_client_commit_item3_t *item = apr_hash_get(cb_baton->commit_items,
1084 path, APR_HASH_KEY_STRING);
1085 svn_node_kind_t kind = item->kind;
1086 void *file_baton = NULL;
1087 const char *copyfrom_url = NULL;
1088 apr_pool_t *file_pool = NULL;
1089 svn_wc_adm_access_t *adm_access = cb_baton->adm_access;
1090 const svn_delta_editor_t *editor = cb_baton->editor;
1091 apr_hash_t *file_mods = cb_baton->file_mods;
1092 const char *notify_path_prefix = cb_baton->notify_path_prefix;
1093 svn_client_ctx_t *ctx = cb_baton->ctx;
1095 /* Do some initializations. */
1096 *dir_baton = NULL;
1097 if (item->copyfrom_url)
1098 copyfrom_url = item->copyfrom_url;
1100 /* If this is a file with textual mods, we'll be keeping its baton
1101 around until the end of the commit. So just lump its memory into
1102 a single, big, all-the-file-batons-in-here pool. Otherwise, we
1103 can just use POOL, and trust our caller to clean that mess up. */
1104 if ((kind == svn_node_file)
1105 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1106 file_pool = apr_hash_pool_get(file_mods);
1107 else
1108 file_pool = pool;
1110 /* Call the cancellation function. */
1111 if (ctx->cancel_func)
1112 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1114 /* Validation. */
1115 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)
1117 if (! copyfrom_url)
1118 return svn_error_createf
1119 (SVN_ERR_BAD_URL, NULL,
1120 _("Commit item '%s' has copy flag but no copyfrom URL"),
1121 svn_path_local_style(path, pool));
1122 if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev))
1123 return svn_error_createf
1124 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1125 _("Commit item '%s' has copy flag but an invalid revision"),
1126 svn_path_local_style(path, pool));
1129 /* If a feedback table was supplied by the application layer,
1130 describe what we're about to do to this item. */
1131 if (ctx->notify_func2)
1133 /* Convert an absolute path into a relative one (if possible.) */
1134 const char *npath = NULL;
1135 svn_wc_notify_t *notify;
1137 if (notify_path_prefix)
1139 if (strcmp(notify_path_prefix, item->path))
1140 npath = svn_path_is_child(notify_path_prefix, item->path, pool);
1141 else
1142 npath = ".";
1144 if (! npath)
1145 npath = item->path; /* Otherwise just use full path */
1147 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1148 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
1150 /* We don't print the "(bin)" notice for binary files when
1151 replacing, only when adding. So we don't bother to get
1152 the mime-type here. */
1153 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced,
1154 pool);
1156 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1158 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted,
1159 pool);
1161 else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1163 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added,
1164 pool);
1165 if (item->kind == svn_node_file)
1167 const svn_string_t *propval;
1168 SVN_ERR(svn_wc_prop_get
1169 (&propval, SVN_PROP_MIME_TYPE, item->path, adm_access,
1170 pool));
1171 if (propval)
1172 notify->mime_type = propval->data;
1175 else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1176 || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS))
1178 notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified,
1179 pool);
1180 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)
1181 notify->content_state = svn_wc_notify_state_changed;
1182 else
1183 notify->content_state = svn_wc_notify_state_unchanged;
1184 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1185 notify->prop_state = svn_wc_notify_state_changed;
1186 else
1187 notify->prop_state = svn_wc_notify_state_unchanged;
1189 else
1190 notify = NULL;
1192 if (notify)
1194 notify->kind = item->kind;
1195 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1199 /* If this item is supposed to be deleted, do so. */
1200 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1202 assert(parent_baton);
1203 SVN_ERR(editor->delete_entry(path, item->revision,
1204 parent_baton, pool));
1207 /* If this item is supposed to be added, do so. */
1208 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1210 if (kind == svn_node_file)
1212 assert(parent_baton);
1213 SVN_ERR(editor->add_file
1214 (path, parent_baton, copyfrom_url,
1215 copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1216 file_pool, &file_baton));
1218 else
1220 assert(parent_baton);
1221 SVN_ERR(editor->add_directory
1222 (path, parent_baton, copyfrom_url,
1223 copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM,
1224 pool, dir_baton));
1227 /* Set other prop-changes, if available in the baton */
1228 if (item->outgoing_prop_changes)
1230 svn_prop_t *prop;
1231 apr_array_header_t *prop_changes = item->outgoing_prop_changes;
1232 int ctr;
1233 for (ctr = 0; ctr < prop_changes->nelts; ctr++)
1235 prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *);
1236 if (kind == svn_node_file)
1238 editor->change_file_prop(file_baton, prop->name,
1239 prop->value, pool);
1241 else
1243 editor->change_dir_prop(*dir_baton, prop->name,
1244 prop->value, pool);
1250 /* Now handle property mods. */
1251 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)
1253 const svn_wc_entry_t *tmp_entry;
1255 if (kind == svn_node_file)
1257 if (! file_baton)
1259 assert(parent_baton);
1260 SVN_ERR(editor->open_file(path, parent_baton,
1261 item->revision,
1262 file_pool, &file_baton));
1265 else
1267 if (! *dir_baton)
1269 if (! parent_baton)
1271 SVN_ERR(editor->open_root
1272 (cb_baton->edit_baton, item->revision,
1273 pool, dir_baton));
1275 else
1277 SVN_ERR(editor->open_directory
1278 (path, parent_baton, item->revision,
1279 pool, dir_baton));
1284 SVN_ERR(svn_wc_entry(&tmp_entry, item->path, adm_access, TRUE, pool));
1285 SVN_ERR(svn_wc_transmit_prop_deltas
1286 (item->path, adm_access, tmp_entry, editor,
1287 (kind == svn_node_dir) ? *dir_baton : file_baton, NULL, pool));
1289 /* Make any additional client -> repository prop changes. */
1290 if (item->outgoing_prop_changes)
1292 svn_prop_t *prop;
1293 int i;
1295 for (i = 0; i < item->outgoing_prop_changes->nelts; i++)
1297 prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i,
1298 svn_prop_t *);
1299 if (kind == svn_node_file)
1301 editor->change_file_prop(file_baton, prop->name,
1302 prop->value, pool);
1304 else
1306 editor->change_dir_prop(*dir_baton, prop->name,
1307 prop->value, pool);
1313 /* Finally, handle text mods (in that we need to open a file if it
1314 hasn't already been opened, and we need to put the file baton in
1315 our FILES hash). */
1316 if ((kind == svn_node_file)
1317 && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS))
1319 struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod));
1321 if (! file_baton)
1323 assert(parent_baton);
1324 SVN_ERR(editor->open_file(path, parent_baton,
1325 item->revision,
1326 file_pool, &file_baton));
1329 /* Add this file mod to the FILE_MODS hash. */
1330 mod->item = item;
1331 mod->file_baton = file_baton;
1332 apr_hash_set(file_mods, item->url, APR_HASH_KEY_STRING, mod);
1334 else if (file_baton)
1336 /* Close any outstanding file batons that didn't get caught by
1337 the "has local mods" conditional above. */
1338 SVN_ERR(editor->close_file(file_baton, NULL, file_pool));
1341 return SVN_NO_ERROR;
1345 #ifdef SVN_CLIENT_COMMIT_DEBUG
1346 /* Prototype for function below */
1347 static svn_error_t *get_test_editor(const svn_delta_editor_t **editor,
1348 void **edit_baton,
1349 const svn_delta_editor_t *real_editor,
1350 void *real_eb,
1351 const char *base_url,
1352 apr_pool_t *pool);
1353 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1355 svn_error_t *
1356 svn_client__do_commit(const char *base_url,
1357 apr_array_header_t *commit_items,
1358 svn_wc_adm_access_t *adm_access,
1359 const svn_delta_editor_t *editor,
1360 void *edit_baton,
1361 const char *notify_path_prefix,
1362 apr_hash_t **tempfiles,
1363 apr_hash_t **digests,
1364 svn_client_ctx_t *ctx,
1365 apr_pool_t *pool)
1367 apr_hash_t *file_mods = apr_hash_make(pool);
1368 apr_hash_t *items_hash = apr_hash_make(pool);
1369 apr_pool_t *subpool = svn_pool_create(pool);
1370 apr_hash_index_t *hi;
1371 int i;
1372 struct path_driver_cb_baton cb_baton;
1373 apr_array_header_t *paths =
1374 apr_array_make(pool, commit_items->nelts, sizeof(const char *));
1376 #ifdef SVN_CLIENT_COMMIT_DEBUG
1378 SVN_ERR(get_test_editor(&editor, &edit_baton,
1379 editor, edit_baton,
1380 base_url, pool));
1382 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1384 /* If the caller wants us to track temporary file creation, create a
1385 hash to store those paths in. */
1386 if (tempfiles)
1387 *tempfiles = apr_hash_make(pool);
1389 /* Ditto for the md5 digests. */
1390 if (digests)
1391 *digests = apr_hash_make(pool);
1393 /* Build a hash from our COMMIT_ITEMS array, keyed on the
1394 URI-decoded relative paths (which come from the item URLs). And
1395 keep an array of those decoded paths, too. */
1396 for (i = 0; i < commit_items->nelts; i++)
1398 svn_client_commit_item3_t *item =
1399 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1400 const char *path = svn_path_uri_decode(item->url, pool);
1401 apr_hash_set(items_hash, path, APR_HASH_KEY_STRING, item);
1402 APR_ARRAY_PUSH(paths, const char *) = path;
1405 /* Setup the callback baton. */
1406 cb_baton.adm_access = adm_access;
1407 cb_baton.editor = editor;
1408 cb_baton.edit_baton = edit_baton;
1409 cb_baton.file_mods = file_mods;
1410 cb_baton.tempfiles = tempfiles ? *tempfiles : NULL;
1411 cb_baton.notify_path_prefix = notify_path_prefix;
1412 cb_baton.ctx = ctx;
1413 cb_baton.commit_items = items_hash;
1415 /* Drive the commit editor! */
1416 SVN_ERR(svn_delta_path_driver(editor, edit_baton, SVN_INVALID_REVNUM,
1417 paths, do_item_commit, &cb_baton, pool));
1419 /* Transmit outstanding text deltas. */
1420 for (hi = apr_hash_first(pool, file_mods); hi; hi = apr_hash_next(hi))
1422 struct file_mod_t *mod;
1423 svn_client_commit_item3_t *item;
1424 void *val;
1425 void *file_baton;
1426 const char *tempfile, *dir_path;
1427 unsigned char digest[APR_MD5_DIGESTSIZE];
1428 svn_boolean_t fulltext = FALSE;
1429 svn_wc_adm_access_t *item_access;
1431 svn_pool_clear(subpool);
1432 /* Get the next entry. */
1433 apr_hash_this(hi, NULL, NULL, &val);
1434 mod = val;
1436 /* Transmit the entry. */
1437 item = mod->item;
1438 file_baton = mod->file_baton;
1440 if (ctx->cancel_func)
1441 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1443 if (ctx->notify_func2)
1445 svn_wc_notify_t *notify;
1446 const char *npath = NULL;
1448 if (notify_path_prefix)
1450 if (strcmp(notify_path_prefix, item->path) != 0)
1451 npath = svn_path_is_child(notify_path_prefix, item->path,
1452 subpool);
1453 else
1454 npath = ".";
1456 if (! npath)
1457 npath = item->path;
1458 notify = svn_wc_create_notify(npath,
1459 svn_wc_notify_commit_postfix_txdelta,
1460 subpool);
1461 notify->kind = svn_node_file;
1462 (*ctx->notify_func2)(ctx->notify_baton2, notify, subpool);
1465 if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1466 fulltext = TRUE;
1468 dir_path = svn_path_dirname(item->path, subpool);
1469 SVN_ERR(svn_wc_adm_retrieve(&item_access, adm_access, dir_path,
1470 subpool));
1471 SVN_ERR(svn_wc_transmit_text_deltas2(tempfiles ? &tempfile : NULL,
1472 digest, item->path,
1473 item_access, fulltext, editor,
1474 file_baton, subpool));
1475 if (tempfiles && tempfile && *tempfiles)
1477 tempfile = apr_pstrdup(apr_hash_pool_get(*tempfiles), tempfile);
1478 apr_hash_set(*tempfiles, tempfile, APR_HASH_KEY_STRING, (void *)1);
1480 if (digests)
1482 unsigned char *new_digest = apr_pmemdup(apr_hash_pool_get(*digests),
1483 digest, APR_MD5_DIGESTSIZE);
1484 apr_hash_set(*digests, item->path, APR_HASH_KEY_STRING, new_digest);
1488 svn_pool_destroy(subpool);
1490 /* Close the edit. */
1491 SVN_ERR(editor->close_edit(edit_baton, pool));
1492 return SVN_NO_ERROR;
1495 /* Commit callback baton */
1497 struct commit_baton {
1498 svn_commit_info_t **info;
1499 apr_pool_t *pool;
1502 svn_error_t *svn_client__commit_get_baton(void **baton,
1503 svn_commit_info_t **info,
1504 apr_pool_t *pool)
1506 struct commit_baton *cb = apr_pcalloc(pool, sizeof(*cb));
1507 cb->info = info;
1508 cb->pool = pool;
1509 *baton = cb;
1511 return SVN_NO_ERROR;
1514 svn_error_t *svn_client__commit_callback(const svn_commit_info_t *commit_info,
1515 void *baton,
1516 apr_pool_t *pool)
1518 struct commit_baton *cb = baton;
1520 *(cb->info) = svn_commit_info_dup(commit_info, cb->pool);
1522 return SVN_NO_ERROR;
1526 #ifdef SVN_CLIENT_COMMIT_DEBUG
1528 /*** Temporary test editor ***/
1530 struct edit_baton
1532 const char *path;
1534 const svn_delta_editor_t *real_editor;
1535 void *real_eb;
1538 struct item_baton
1540 struct edit_baton *eb;
1541 void *real_baton;
1543 const char *path;
1546 static struct item_baton *
1547 make_baton(struct edit_baton *eb,
1548 void *real_baton,
1549 const char *path,
1550 apr_pool_t *pool)
1552 struct item_baton *new_baton = apr_pcalloc(pool, sizeof(*new_baton));
1553 new_baton->eb = eb;
1554 new_baton->real_baton = real_baton;
1555 new_baton->path = apr_pstrdup(pool, path);
1556 return new_baton;
1559 static svn_error_t *
1560 set_target_revision(void *edit_baton,
1561 svn_revnum_t target_revision,
1562 apr_pool_t *pool)
1564 struct edit_baton *eb = edit_baton;
1565 return (*eb->real_editor->set_target_revision)(eb->real_eb,
1566 target_revision,
1567 pool);
1570 static svn_error_t *
1571 open_root(void *edit_baton,
1572 svn_revnum_t base_revision,
1573 apr_pool_t *dir_pool,
1574 void **root_baton)
1576 struct edit_baton *eb = edit_baton;
1577 struct item_baton *new_baton = make_baton(eb, NULL, eb->path, dir_pool);
1578 fprintf(stderr, "TEST EDIT STARTED (base URL=%s)\n", eb->path);
1579 *root_baton = new_baton;
1580 return (*eb->real_editor->open_root)(eb->real_eb,
1581 base_revision,
1582 dir_pool,
1583 &new_baton->real_baton);
1586 static svn_error_t *
1587 add_file(const char *path,
1588 void *parent_baton,
1589 const char *copyfrom_path,
1590 svn_revnum_t copyfrom_revision,
1591 apr_pool_t *pool,
1592 void **baton)
1594 struct item_baton *db = parent_baton;
1595 struct item_baton *new_baton = make_baton(db->eb, NULL, path, pool);
1596 const char *copystuffs = "";
1597 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
1598 copystuffs = apr_psprintf(pool,
1599 " (copied from %s:%ld)",
1600 copyfrom_path,
1601 copyfrom_revision);
1602 fprintf(stderr, " Adding : %s%s\n", path, copystuffs);
1603 *baton = new_baton;
1604 return (*db->eb->real_editor->add_file)(path, db->real_baton,
1605 copyfrom_path, copyfrom_revision,
1606 pool, &new_baton->real_baton);
1609 static svn_error_t *
1610 delete_entry(const char *path,
1611 svn_revnum_t revision,
1612 void *parent_baton,
1613 apr_pool_t *pool)
1615 struct item_baton *db = parent_baton;
1616 fprintf(stderr, " Deleting: %s\n", path);
1617 return (*db->eb->real_editor->delete_entry)(path, revision,
1618 db->real_baton, pool);
1621 static svn_error_t *
1622 open_file(const char *path,
1623 void *parent_baton,
1624 svn_revnum_t base_revision,
1625 apr_pool_t *pool,
1626 void **baton)
1628 struct item_baton *db = parent_baton;
1629 struct item_baton *new_baton = make_baton(db->eb, NULL, path, pool);
1630 fprintf(stderr, " Opening : %s\n", path);
1631 *baton = new_baton;
1632 return (*db->eb->real_editor->open_file)(path, db->real_baton,
1633 base_revision, pool,
1634 &new_baton->real_baton);
1637 static svn_error_t *
1638 close_file(void *baton, const char *text_checksum, apr_pool_t *pool)
1640 struct item_baton *fb = baton;
1641 fprintf(stderr, " Closing : %s\n", fb->path);
1642 return (*fb->eb->real_editor->close_file)(fb->real_baton,
1643 text_checksum, pool);
1647 static svn_error_t *
1648 change_file_prop(void *file_baton,
1649 const char *name,
1650 const svn_string_t *value,
1651 apr_pool_t *pool)
1653 struct item_baton *fb = file_baton;
1654 fprintf(stderr, " PropSet (%s=%s)\n", name, value ? value->data : "");
1655 return (*fb->eb->real_editor->change_file_prop)(fb->real_baton,
1656 name, value, pool);
1659 static svn_error_t *
1660 apply_textdelta(void *file_baton,
1661 const char *base_checksum,
1662 apr_pool_t *pool,
1663 svn_txdelta_window_handler_t *handler,
1664 void **handler_baton)
1666 struct item_baton *fb = file_baton;
1667 fprintf(stderr, " Transmitting text...\n");
1668 return (*fb->eb->real_editor->apply_textdelta)(fb->real_baton,
1669 base_checksum, pool,
1670 handler, handler_baton);
1673 static svn_error_t *
1674 close_edit(void *edit_baton, apr_pool_t *pool)
1676 struct edit_baton *eb = edit_baton;
1677 fprintf(stderr, "TEST EDIT COMPLETED\n");
1678 return (*eb->real_editor->close_edit)(eb->real_eb, pool);
1681 static svn_error_t *
1682 add_directory(const char *path,
1683 void *parent_baton,
1684 const char *copyfrom_path,
1685 svn_revnum_t copyfrom_revision,
1686 apr_pool_t *pool,
1687 void **baton)
1689 struct item_baton *db = parent_baton;
1690 struct item_baton *new_baton = make_baton(db->eb, NULL, path, pool);
1691 const char *copystuffs = "";
1692 if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
1693 copystuffs = apr_psprintf(pool,
1694 " (copied from %s:%ld)",
1695 copyfrom_path,
1696 copyfrom_revision);
1697 fprintf(stderr, " Adding : %s%s\n", path, copystuffs);
1698 *baton = new_baton;
1699 return (*db->eb->real_editor->add_directory)(path,
1700 db->real_baton,
1701 copyfrom_path,
1702 copyfrom_revision,
1703 pool,
1704 &new_baton->real_baton);
1707 static svn_error_t *
1708 open_directory(const char *path,
1709 void *parent_baton,
1710 svn_revnum_t base_revision,
1711 apr_pool_t *pool,
1712 void **baton)
1714 struct item_baton *db = parent_baton;
1715 struct item_baton *new_baton = make_baton(db->eb, NULL, path, pool);
1716 fprintf(stderr, " Opening : %s\n", path);
1717 *baton = new_baton;
1718 return (*db->eb->real_editor->open_directory)(path, db->real_baton,
1719 base_revision, pool,
1720 &new_baton->real_baton);
1723 static svn_error_t *
1724 change_dir_prop(void *dir_baton,
1725 const char *name,
1726 const svn_string_t *value,
1727 apr_pool_t *pool)
1729 struct item_baton *db = dir_baton;
1730 fprintf(stderr, " PropSet (%s=%s)\n", name, value ? value->data : "");
1731 return (*db->eb->real_editor->change_dir_prop)(db->real_baton,
1732 name, value, pool);
1735 static svn_error_t *
1736 close_directory(void *baton, apr_pool_t *pool)
1738 struct item_baton *db = baton;
1739 fprintf(stderr, " Closing : %s\n", db->path);
1740 return (*db->eb->real_editor->close_directory)(db->real_baton, pool);
1743 static svn_error_t *
1744 abort_edit(void *edit_baton, apr_pool_t *pool)
1746 struct edit_baton *eb = edit_baton;
1747 fprintf(stderr, "TEST EDIT ABORTED\n");
1748 return (*eb->real_editor->abort_edit)(eb->real_eb, pool);
1751 static svn_error_t *
1752 get_test_editor(const svn_delta_editor_t **editor,
1753 void **edit_baton,
1754 const svn_delta_editor_t *real_editor,
1755 void *real_eb,
1756 const char *base_url,
1757 apr_pool_t *pool)
1759 svn_delta_editor_t *ed = svn_delta_default_editor(pool);
1760 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1762 eb->path = apr_pstrdup(pool, base_url);
1763 eb->real_editor = real_editor;
1764 eb->real_eb = real_eb;
1766 /* We don't implement absent_file() or absent_directory() in this
1767 editor, because presumably commit would never send that. */
1768 ed->set_target_revision = set_target_revision;
1769 ed->open_root = open_root;
1770 ed->add_directory = add_directory;
1771 ed->open_directory = open_directory;
1772 ed->close_directory = close_directory;
1773 ed->add_file = add_file;
1774 ed->open_file = open_file;
1775 ed->close_file = close_file;
1776 ed->delete_entry = delete_entry;
1777 ed->apply_textdelta = apply_textdelta;
1778 ed->change_dir_prop = change_dir_prop;
1779 ed->change_file_prop = change_file_prop;
1780 ed->close_edit = close_edit;
1781 ed->abort_edit = abort_edit;
1783 *editor = ed;
1784 *edit_baton = eb;
1785 return SVN_NO_ERROR;
1787 #endif /* SVN_CLIENT_COMMIT_DEBUG */
1789 svn_error_t *
1790 svn_client__get_log_msg(const char **log_msg,
1791 const char **tmp_file,
1792 const apr_array_header_t *commit_items,
1793 svn_client_ctx_t *ctx,
1794 apr_pool_t *pool)
1796 if (ctx->log_msg_func3)
1798 /* The client provided a callback function for the current API.
1799 Forward the call to it directly. */
1800 return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items,
1801 ctx->log_msg_baton3, pool);
1803 else if (ctx->log_msg_func2 || ctx->log_msg_func)
1805 /* The client provided a pre-1.5 (or pre-1.3) API callback
1806 function. Convert the commit_items list to the appropriate
1807 type, and forward call to it. */
1808 svn_error_t *err;
1809 apr_pool_t *subpool = svn_pool_create(pool);
1810 apr_array_header_t *old_commit_items =
1811 apr_array_make(subpool, commit_items->nelts,
1812 ctx->log_msg_func2 ? sizeof(svn_client_commit_item2_t) :
1813 sizeof(svn_client_commit_item_t));
1814 int i;
1816 for (i = 0; i < commit_items->nelts; i++)
1818 svn_client_commit_item3_t *item =
1819 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1821 if (ctx->log_msg_func2)
1823 svn_client_commit_item2_t *old_item =
1824 apr_pcalloc(subpool, sizeof(*old_item));
1826 old_item->path = item->path;
1827 old_item->kind = item->kind;
1828 old_item->url = item->url;
1829 old_item->revision = item->revision;
1830 old_item->copyfrom_url = item->copyfrom_url;
1831 old_item->copyfrom_rev = item->copyfrom_rev;
1832 old_item->state_flags = item->state_flags;
1833 old_item->wcprop_changes = item->incoming_prop_changes;
1835 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) =
1836 old_item;
1838 else /* ctx->log_msg_func */
1840 svn_client_commit_item_t *old_item =
1841 apr_pcalloc(subpool, sizeof(*old_item));
1843 old_item->path = item->path;
1844 old_item->kind = item->kind;
1845 old_item->url = item->url;
1846 /* The pre-1.3 API used the revision field for copyfrom_rev
1847 and revision depeding of copyfrom_url. */
1848 old_item->revision = item->copyfrom_url ?
1849 item->copyfrom_rev : item->revision;
1850 old_item->copyfrom_url = item->copyfrom_url;
1851 old_item->state_flags = item->state_flags;
1852 old_item->wcprop_changes = item->incoming_prop_changes;
1854 APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) =
1855 old_item;
1859 if (ctx->log_msg_func2)
1860 err = (*ctx->log_msg_func2)(log_msg, tmp_file, commit_items,
1861 ctx->log_msg_baton2, pool);
1862 else
1863 err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items,
1864 ctx->log_msg_baton, pool);
1865 svn_pool_destroy(subpool);
1866 return err;
1868 else
1870 /* No log message callback was provided by the client. */
1871 *log_msg = "";
1872 *tmp_file = NULL;
1873 return SVN_NO_ERROR;
1877 svn_error_t *svn_client__get_revprop_table(apr_hash_t **revprop_table,
1878 const char *log_msg,
1879 svn_client_ctx_t *ctx,
1880 apr_pool_t *pool)
1882 if (ctx->revprop_table && svn_prop_has_svn_prop(ctx->revprop_table, pool))
1883 return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
1884 _("Standard properties can't be set "
1885 "explicitly as revision properties"));
1886 if (ctx->revprop_table)
1887 *revprop_table = apr_hash_copy(pool, ctx->revprop_table);
1888 else
1889 *revprop_table = apr_hash_make(pool);
1890 apr_hash_set(*revprop_table, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
1891 svn_string_create(log_msg, pool));
1893 return SVN_NO_ERROR;