* subversion/mod_dav_svn/reports/replay.c
[svn.git] / subversion / libsvn_client / commit.c
blobc91ee6a6c80c73173a3e59ceb18d3b7c4a50f514
1 /*
2 * commit.c: wrappers around wc commit functionality.
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 /* ==================================================================== */
23 /*** Includes. ***/
25 #include <string.h>
26 #include <assert.h>
27 #include <apr_strings.h>
28 #include <apr_hash.h>
29 #include <apr_md5.h>
30 #include "svn_wc.h"
31 #include "svn_ra.h"
32 #include "svn_delta.h"
33 #include "svn_subst.h"
34 #include "svn_client.h"
35 #include "svn_string.h"
36 #include "svn_pools.h"
37 #include "svn_error.h"
38 #include "svn_error_codes.h"
39 #include "svn_path.h"
40 #include "svn_io.h"
41 #include "svn_md5.h"
42 #include "svn_time.h"
43 #include "svn_sorts.h"
44 #include "svn_props.h"
45 #include "svn_iter.h"
47 #include "client.h"
49 #include "svn_private_config.h"
51 /* Import context baton.
53 ### TODO: Add the following items to this baton:
54 /` import editor/baton. `/
55 const svn_delta_editor_t *editor;
56 void *edit_baton;
58 /` Client context baton `/
59 svn_client_ctx_t `ctx;
61 /` Paths (keys) excluded from the import (values ignored) `/
62 apr_hash_t *excludes;
64 typedef struct import_ctx_t
66 /* Whether any changes were made to the repository */
67 svn_boolean_t repos_changed;
69 } import_ctx_t;
72 /* Apply PATH's contents (as a delta against the empty string) to
73 FILE_BATON in EDITOR. Use POOL for any temporary allocation.
74 PROPERTIES is the set of node properties set on this file.
76 Fill DIGEST with the md5 checksum of the sent file; DIGEST must be
77 at least APR_MD5_DIGESTSIZE bytes long. */
78 static svn_error_t *
79 send_file_contents(const char *path,
80 void *file_baton,
81 const svn_delta_editor_t *editor,
82 apr_hash_t *properties,
83 unsigned char *digest,
84 apr_pool_t *pool)
86 const char *tmpfile_path = NULL;
87 svn_stream_t *contents;
88 svn_txdelta_window_handler_t handler;
89 void *handler_baton;
90 apr_file_t *f;
91 const svn_string_t *eol_style_val = NULL, *keywords_val = NULL;
92 svn_boolean_t special = FALSE;
93 svn_subst_eol_style_t eol_style;
94 const char *eol;
95 apr_hash_t *keywords;
97 /* If there are properties, look for EOL-style and keywords ones. */
98 if (properties)
100 eol_style_val = apr_hash_get(properties, SVN_PROP_EOL_STYLE,
101 sizeof(SVN_PROP_EOL_STYLE) - 1);
102 keywords_val = apr_hash_get(properties, SVN_PROP_KEYWORDS,
103 sizeof(SVN_PROP_KEYWORDS) - 1);
104 if (apr_hash_get(properties, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING))
105 special = TRUE;
108 /* Get an editor func that wants to consume the delta stream. */
109 SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
110 &handler, &handler_baton));
112 if (eol_style_val)
113 svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
114 else
116 eol = NULL;
117 eol_style = svn_subst_eol_style_none;
120 if (keywords_val)
121 SVN_ERR(svn_subst_build_keywords2(&keywords, keywords_val->data,
122 APR_STRINGIFY(SVN_INVALID_REVNUM),
123 "", 0, "", pool));
124 else
125 keywords = NULL;
127 /* If we have EOL styles or keywords to de-translate, do it. */
128 if (svn_subst_translation_required(eol_style, eol, keywords, special, TRUE))
130 const char *temp_dir;
132 /* Now create a new tempfile, and open a stream to it. */
133 SVN_ERR(svn_io_temp_dir(&temp_dir, pool));
134 SVN_ERR(svn_io_open_unique_file2
135 (NULL, &tmpfile_path,
136 svn_path_join(temp_dir, "svn-import", pool),
137 ".tmp", svn_io_file_del_on_pool_cleanup, pool));
139 SVN_ERR(svn_subst_translate_to_normal_form
140 (path, tmpfile_path, eol_style, eol, FALSE,
141 keywords, special, pool));
144 /* Open our contents file, either the original path or the temporary
145 copy we might have made above. */
146 SVN_ERR(svn_io_file_open(&f, tmpfile_path ? tmpfile_path : path,
147 APR_READ, APR_OS_DEFAULT, pool));
148 contents = svn_stream_from_aprfile(f, pool);
150 /* Send the file's contents to the delta-window handler. */
151 SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton,
152 digest, pool));
154 /* Close our contents file. */
155 SVN_ERR(svn_io_file_close(f, pool));
157 /* The temp file is removed by the pool cleanup run by the caller */
159 return SVN_NO_ERROR;
163 /* Import file PATH as EDIT_PATH in the repository directory indicated
164 * by DIR_BATON in EDITOR.
166 * Accumulate file paths and their batons in FILES, which must be
167 * non-null. (These are used to send postfix textdeltas later).
169 * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON
170 * for each file.
172 * Use POOL for any temporary allocation.
174 static svn_error_t *
175 import_file(const svn_delta_editor_t *editor,
176 void *dir_baton,
177 const char *path,
178 const char *edit_path,
179 import_ctx_t *import_ctx,
180 svn_client_ctx_t *ctx,
181 apr_pool_t *pool)
183 void *file_baton;
184 const char *mimetype = NULL;
185 unsigned char digest[APR_MD5_DIGESTSIZE];
186 const char *text_checksum;
187 apr_hash_t* properties;
188 apr_hash_index_t *hi;
189 svn_node_kind_t kind;
190 svn_boolean_t is_special;
192 SVN_ERR(svn_path_check_valid(path, pool));
194 SVN_ERR(svn_io_check_special_path(path, &kind, &is_special, pool));
196 /* Add the file, using the pool from the FILES hash. */
197 SVN_ERR(editor->add_file(edit_path, dir_baton, NULL, SVN_INVALID_REVNUM,
198 pool, &file_baton));
200 /* Remember that the repository was modified */
201 import_ctx->repos_changed = TRUE;
203 if (! is_special)
205 /* add automatic properties */
206 SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, path, ctx,
207 pool));
209 else
210 properties = apr_hash_make(pool);
212 if (properties)
214 for (hi = apr_hash_first(pool, properties); hi; hi = apr_hash_next(hi))
216 const void *pname;
217 void *pval;
219 apr_hash_this(hi, &pname, NULL, &pval);
220 SVN_ERR(editor->change_file_prop(file_baton, pname, pval, pool));
224 if (ctx->notify_func2)
226 svn_wc_notify_t *notify
227 = svn_wc_create_notify(path, svn_wc_notify_commit_added, pool);
228 notify->kind = svn_node_file;
229 notify->mime_type = mimetype;
230 notify->content_state = notify->prop_state
231 = svn_wc_notify_state_inapplicable;
232 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
233 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
236 /* If this is a special file, we need to set the svn:special
237 property and create a temporary detranslated version in order to
238 send to the server. */
239 if (is_special)
241 apr_hash_set(properties, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING,
242 svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
243 SVN_ERR(editor->change_file_prop(file_baton, SVN_PROP_SPECIAL,
244 apr_hash_get(properties,
245 SVN_PROP_SPECIAL,
246 APR_HASH_KEY_STRING),
247 pool));
250 /* Now, transmit the file contents. */
251 SVN_ERR(send_file_contents(path, file_baton, editor,
252 properties, digest, pool));
254 /* Finally, close the file. */
255 text_checksum = svn_md5_digest_to_cstring(digest, pool);
256 SVN_ERR(editor->close_file(file_baton, text_checksum, pool));
258 return SVN_NO_ERROR;
262 /* Import directory PATH into the repository directory indicated by
263 * DIR_BATON in EDITOR. EDIT_PATH is the path imported as the root
264 * directory, so all edits are relative to that.
266 * DEPTH is the depth at this point in the descent (it may be changed
267 * for recursive calls).
269 * Accumulate file paths and their batons in FILES, which must be
270 * non-null. (These are used to send postfix textdeltas later).
272 * EXCLUDES is a hash whose keys are absolute paths to exclude from
273 * the import (values are unused).
275 * If NO_IGNORE is FALSE, don't import files or directories that match
276 * ignore patterns.
278 * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for each
279 * directory.
281 * Use POOL for any temporary allocation. */
282 static svn_error_t *
283 import_dir(const svn_delta_editor_t *editor,
284 void *dir_baton,
285 const char *path,
286 const char *edit_path,
287 svn_depth_t depth,
288 apr_hash_t *excludes,
289 svn_boolean_t no_ignore,
290 svn_boolean_t ignore_unknown_node_types,
291 import_ctx_t *import_ctx,
292 svn_client_ctx_t *ctx,
293 apr_pool_t *pool)
295 apr_pool_t *subpool = svn_pool_create(pool); /* iteration pool */
296 apr_hash_t *dirents;
297 apr_hash_index_t *hi;
298 apr_array_header_t *ignores;
300 SVN_ERR(svn_path_check_valid(path, pool));
302 if (!no_ignore)
303 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
305 SVN_ERR(svn_io_get_dirents2(&dirents, path, pool));
307 for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
309 const char *this_path, *this_edit_path, *abs_path;
310 const svn_io_dirent_t *dirent;
311 const char *filename;
312 const void *key;
313 void *val;
315 svn_pool_clear(subpool);
317 apr_hash_this(hi, &key, NULL, &val);
319 filename = key;
320 dirent = val;
322 if (ctx->cancel_func)
323 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
325 if (svn_wc_is_adm_dir(filename, subpool))
327 /* If someone's trying to import a directory named the same
328 as our administrative directories, that's probably not
329 what they wanted to do. If they are importing a file
330 with that name, something is bound to blow up when they
331 checkout what they've imported. So, just skip items with
332 that name. */
333 if (ctx->notify_func2)
335 svn_wc_notify_t *notify
336 = svn_wc_create_notify(svn_path_join(path, filename,
337 subpool),
338 svn_wc_notify_skip, subpool);
339 notify->kind = svn_node_dir;
340 notify->content_state = notify->prop_state
341 = svn_wc_notify_state_inapplicable;
342 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
343 (*ctx->notify_func2)(ctx->notify_baton2, notify, subpool);
345 continue;
348 /* Typically, we started importing from ".", in which case
349 edit_path is "". So below, this_path might become "./blah",
350 and this_edit_path might become "blah", for example. */
351 this_path = svn_path_join(path, filename, subpool);
352 this_edit_path = svn_path_join(edit_path, filename, subpool);
354 /* If this is an excluded path, exclude it. */
355 SVN_ERR(svn_path_get_absolute(&abs_path, this_path, subpool));
356 if (apr_hash_get(excludes, abs_path, APR_HASH_KEY_STRING))
357 continue;
359 if ((!no_ignore) && svn_wc_match_ignore_list(filename, ignores,
360 subpool))
361 continue;
363 if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
365 void *this_dir_baton;
367 /* Add the new subdirectory, getting a descent baton from
368 the editor. */
369 SVN_ERR(editor->add_directory(this_edit_path, dir_baton,
370 NULL, SVN_INVALID_REVNUM, subpool,
371 &this_dir_baton));
373 /* Remember that the repository was modified */
374 import_ctx->repos_changed = TRUE;
376 /* By notifying before the recursive call below, we display
377 a directory add before displaying adds underneath the
378 directory. To do it the other way around, just move this
379 after the recursive call. */
380 if (ctx->notify_func2)
382 svn_wc_notify_t *notify
383 = svn_wc_create_notify(this_path, svn_wc_notify_commit_added,
384 subpool);
385 notify->kind = svn_node_dir;
386 notify->content_state = notify->prop_state
387 = svn_wc_notify_state_inapplicable;
388 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
389 (*ctx->notify_func2)(ctx->notify_baton2, notify, subpool);
392 /* Recurse. */
394 svn_depth_t depth_below_here = depth;
395 if (depth == svn_depth_immediates)
396 depth_below_here = svn_depth_empty;
398 SVN_ERR(import_dir(editor, this_dir_baton, this_path,
399 this_edit_path, depth_below_here, excludes,
400 no_ignore, ignore_unknown_node_types,
401 import_ctx, ctx,
402 subpool));
405 /* Finally, close the sub-directory. */
406 SVN_ERR(editor->close_directory(this_dir_baton, subpool));
408 else if (dirent->kind == svn_node_file && depth >= svn_depth_files)
410 SVN_ERR(import_file(editor, dir_baton, this_path,
411 this_edit_path, import_ctx, ctx, subpool));
413 else if (dirent->kind != svn_node_dir && dirent->kind != svn_node_file)
415 if (ignore_unknown_node_types)
417 /*## warn about it*/
418 if (ctx->notify_func2)
420 svn_wc_notify_t *notify
421 = svn_wc_create_notify(this_path,
422 svn_wc_notify_skip, subpool);
423 notify->kind = svn_node_dir;
424 notify->content_state = notify->prop_state
425 = svn_wc_notify_state_inapplicable;
426 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
427 (*ctx->notify_func2)(ctx->notify_baton2, notify, subpool);
430 else
431 return svn_error_createf
432 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
433 _("Unknown or unversionable type for '%s'"),
434 svn_path_local_style(this_path, subpool));
438 svn_pool_destroy(subpool);
439 return SVN_NO_ERROR;
443 /* Recursively import PATH to a repository using EDITOR and
444 * EDIT_BATON. PATH can be a file or directory.
446 * DEPTH is the depth at which to import PATH; it behaves as for
447 * svn_client_import3().
449 * NEW_ENTRIES is an ordered array of path components that must be
450 * created in the repository (where the ordering direction is
451 * parent-to-child). If PATH is a directory, NEW_ENTRIES may be empty
452 * -- the result is an import which creates as many new entries in the
453 * top repository target directory as there are importable entries in
454 * the top of PATH; but if NEW_ENTRIES is not empty, its last item is
455 * the name of a new subdirectory in the repository to hold the
456 * import. If PATH is a file, NEW_ENTRIES may not be empty, and its
457 * last item is the name used for the file in the repository. If
458 * NEW_ENTRIES contains more than one item, all but the last item are
459 * the names of intermediate directories that are created before the
460 * real import begins. NEW_ENTRIES may NOT be NULL.
462 * EXCLUDES is a hash whose keys are absolute paths to exclude from
463 * the import (values are unused).
465 * If NO_IGNORE is FALSE, don't import files or directories that match
466 * ignore patterns.
468 * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for
469 * each imported path, passing actions svn_wc_notify_commit_added.
471 * Use POOL for any temporary allocation.
473 * Note: the repository directory receiving the import was specified
474 * when the editor was fetched. (I.e, when EDITOR->open_root() is
475 * called, it returns a directory baton for that directory, which is
476 * not necessarily the root.)
478 static svn_error_t *
479 import(const char *path,
480 apr_array_header_t *new_entries,
481 const svn_delta_editor_t *editor,
482 void *edit_baton,
483 svn_depth_t depth,
484 apr_hash_t *excludes,
485 svn_boolean_t no_ignore,
486 svn_boolean_t ignore_unknown_node_types,
487 svn_client_ctx_t *ctx,
488 apr_pool_t *pool)
490 void *root_baton;
491 svn_node_kind_t kind;
492 apr_array_header_t *ignores;
493 apr_array_header_t *batons = NULL;
494 const char *edit_path = "";
495 import_ctx_t *import_ctx = apr_pcalloc(pool, sizeof(*import_ctx));
497 /* Get a root dir baton. We pass an invalid revnum to open_root
498 to mean "base this on the youngest revision". Should we have an
499 SVN_YOUNGEST_REVNUM defined for these purposes? */
500 SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
501 pool, &root_baton));
503 /* Import a file or a directory tree. */
504 SVN_ERR(svn_io_check_path(path, &kind, pool));
506 /* Make the intermediate directory components necessary for properly
507 rooting our import source tree. */
508 if (new_entries->nelts)
510 int i;
512 batons = apr_array_make(pool, new_entries->nelts, sizeof(void *));
513 for (i = 0; i < new_entries->nelts; i++)
515 const char *component = APR_ARRAY_IDX(new_entries, i, const char *);
516 edit_path = svn_path_join(edit_path, component, pool);
518 /* If this is the last path component, and we're importing a
519 file, then this component is the name of the file, not an
520 intermediate directory. */
521 if ((i == new_entries->nelts - 1) && (kind == svn_node_file))
522 break;
524 APR_ARRAY_PUSH(batons, void *) = root_baton;
525 SVN_ERR(editor->add_directory(edit_path,
526 root_baton,
527 NULL, SVN_INVALID_REVNUM,
528 pool, &root_baton));
530 /* Remember that the repository was modified */
531 import_ctx->repos_changed = TRUE;
534 else if (kind == svn_node_file)
536 return svn_error_create
537 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
538 _("New entry name required when importing a file"));
541 /* Note that there is no need to check whether PATH's basename is
542 the same name that we reserve for our administrative
543 subdirectories. It would be strange -- though not illegal -- to
544 import the contents of a directory of that name, because the
545 directory's own name is not part of those contents. Of course,
546 if something underneath it also has our reserved name, then we'll
547 error. */
549 if (kind == svn_node_file)
551 svn_boolean_t ignores_match = FALSE;
553 if (!no_ignore)
555 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
556 ignores_match = svn_wc_match_ignore_list(path, ignores, pool);
558 if (!ignores_match)
559 SVN_ERR(import_file(editor, root_baton, path, edit_path,
560 import_ctx, ctx, pool));
562 else if (kind == svn_node_dir)
564 SVN_ERR(import_dir(editor, root_baton, path, edit_path,
565 depth, excludes, no_ignore,
566 ignore_unknown_node_types, import_ctx, ctx, pool));
569 else if (kind == svn_node_none
570 || kind == svn_node_unknown)
572 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
573 _("'%s' does not exist"),
574 svn_path_local_style(path, pool));
577 /* Close up shop; it's time to go home. */
578 SVN_ERR(editor->close_directory(root_baton, pool));
579 if (batons && batons->nelts)
581 void **baton;
582 while ((baton = (void **) apr_array_pop(batons)))
584 SVN_ERR(editor->close_directory(*baton, pool));
588 if (import_ctx->repos_changed)
589 SVN_ERR(editor->close_edit(edit_baton, pool));
590 else
591 SVN_ERR(editor->abort_edit(edit_baton, pool));
593 return SVN_NO_ERROR;
597 static svn_error_t *
598 get_ra_editor(svn_ra_session_t **ra_session,
599 svn_revnum_t *latest_rev,
600 const svn_delta_editor_t **editor,
601 void **edit_baton,
602 svn_client_ctx_t *ctx,
603 const char *base_url,
604 const char *base_dir,
605 svn_wc_adm_access_t *base_access,
606 const char *log_msg,
607 apr_array_header_t *commit_items,
608 svn_commit_info_t **commit_info_p,
609 svn_boolean_t is_commit,
610 apr_hash_t *lock_tokens,
611 svn_boolean_t keep_locks,
612 apr_pool_t *pool)
614 void *commit_baton;
615 apr_hash_t *revprop_table;
617 /* Open an RA session to URL. */
618 SVN_ERR(svn_client__open_ra_session_internal(ra_session,
619 base_url, base_dir,
620 base_access, commit_items,
621 is_commit, !is_commit,
622 ctx, pool));
624 /* If this is an import (aka, not a commit), we need to verify that
625 our repository URL exists. */
626 if (! is_commit)
628 svn_node_kind_t kind;
630 SVN_ERR(svn_ra_check_path(*ra_session, "", SVN_INVALID_REVNUM,
631 &kind, pool));
632 if (kind == svn_node_none)
633 return svn_error_createf(SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
634 _("Path '%s' does not exist"),
635 base_url);
638 /* Fetch the latest revision if requested. */
639 if (latest_rev)
640 SVN_ERR(svn_ra_get_latest_revnum(*ra_session, latest_rev, pool));
642 SVN_ERR(svn_client__get_revprop_table(&revprop_table, log_msg, ctx, pool));
644 /* Fetch RA commit editor. */
645 SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
646 return svn_ra_get_commit_editor3(*ra_session, editor, edit_baton,
647 revprop_table,
648 svn_client__commit_callback,
649 commit_baton, lock_tokens, keep_locks,
650 pool);
654 /*** Public Interfaces. ***/
656 svn_error_t *
657 svn_client_import3(svn_commit_info_t **commit_info_p,
658 const char *path,
659 const char *url,
660 svn_depth_t depth,
661 svn_boolean_t no_ignore,
662 svn_boolean_t ignore_unknown_node_types,
663 svn_client_ctx_t *ctx,
664 apr_pool_t *pool)
666 svn_error_t *err = SVN_NO_ERROR;
667 const char *log_msg = "";
668 const svn_delta_editor_t *editor;
669 void *edit_baton;
670 svn_ra_session_t *ra_session;
671 apr_hash_t *excludes = apr_hash_make(pool);
672 svn_node_kind_t kind;
673 const char *base_dir = path;
674 apr_array_header_t *new_entries = apr_array_make(pool, 4,
675 sizeof(const char *));
676 const char *temp;
677 const char *dir;
678 apr_pool_t *subpool;
680 /* Create a new commit item and add it to the array. */
681 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
683 /* If there's a log message gatherer, create a temporary commit
684 item array solely to help generate the log message. The
685 array is not used for the import itself. */
686 svn_client_commit_item3_t *item;
687 const char *tmp_file;
688 apr_array_header_t *commit_items
689 = apr_array_make(pool, 1, sizeof(item));
691 SVN_ERR(svn_client_commit_item_create
692 ((const svn_client_commit_item3_t **) &item, pool));
693 item->path = apr_pstrdup(pool, path);
694 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
695 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
697 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
698 ctx, pool));
699 if (! log_msg)
700 return SVN_NO_ERROR;
701 if (tmp_file)
703 const char *abs_path;
704 SVN_ERR(svn_path_get_absolute(&abs_path, tmp_file, pool));
705 apr_hash_set(excludes, abs_path, APR_HASH_KEY_STRING, (void *)1);
709 SVN_ERR(svn_io_check_path(path, &kind, pool));
710 if (kind == svn_node_file)
711 svn_path_split(path, &base_dir, NULL, pool);
713 /* Figure out all the path components we need to create just to have
714 a place to stick our imported tree. */
715 subpool = svn_pool_create(pool);
718 svn_pool_clear(subpool);
720 /* See if the user is interested in cancelling this operation. */
721 if (ctx->cancel_func)
722 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
724 if (err)
726 /* If get_ra_editor below failed we either tried to open
727 an invalid url, or else some other kind of error. In case
728 the url was bad we back up a directory and try again. */
730 if (err->apr_err != SVN_ERR_FS_NO_SUCH_ENTRY)
731 return err;
732 else
733 svn_error_clear(err);
735 svn_path_split(url, &temp, &dir, pool);
736 APR_ARRAY_PUSH(new_entries, const char *) =
737 svn_path_uri_decode(dir, pool);
738 url = temp;
741 while ((err = get_ra_editor(&ra_session, NULL,
742 &editor, &edit_baton, ctx, url, base_dir,
743 NULL, log_msg, NULL, commit_info_p,
744 FALSE, NULL, TRUE, subpool)));
746 /* Reverse the order of the components we added to our NEW_ENTRIES array. */
747 if (new_entries->nelts)
749 int i, j;
750 const char *component;
751 for (i = 0; i < (new_entries->nelts / 2); i++)
753 j = new_entries->nelts - i - 1;
754 component =
755 APR_ARRAY_IDX(new_entries, i, const char *);
756 APR_ARRAY_IDX(new_entries, i, const char *) =
757 APR_ARRAY_IDX(new_entries, j, const char *);
758 APR_ARRAY_IDX(new_entries, j, const char *) =
759 component;
763 /* An empty NEW_ENTRIES list the first call to get_ra_editor() above
764 succeeded. That means that URL corresponds to an already
765 existing filesystem entity. */
766 if (kind == svn_node_file && (! new_entries->nelts))
767 return svn_error_createf
768 (SVN_ERR_ENTRY_EXISTS, NULL,
769 _("Path '%s' already exists"), url);
771 /* The repository doesn't know about the reserved administrative
772 directory. */
773 if (new_entries->nelts
774 /* What's this, what's this? This assignment is here because we
775 use the value to construct the error message just below. It
776 may not be aesthetically pleasing, but it's less ugly than
777 calling APR_ARRAY_IDX twice. */
778 && svn_wc_is_adm_dir(temp = APR_ARRAY_IDX(new_entries,
779 new_entries->nelts - 1,
780 const char *),
781 pool))
782 return svn_error_createf
783 (SVN_ERR_CL_ADM_DIR_RESERVED, NULL,
784 _("'%s' is a reserved name and cannot be imported"),
785 /* ### Is svn_path_local_style() really necessary for this? */
786 svn_path_local_style(temp, pool));
789 /* If an error occurred during the commit, abort the edit and return
790 the error. We don't even care if the abort itself fails. */
791 if ((err = import(path, new_entries, editor, edit_baton,
792 depth, excludes, no_ignore,
793 ignore_unknown_node_types, ctx, subpool)))
795 svn_error_clear(editor->abort_edit(edit_baton, subpool));
796 return err;
799 /* Transfer *COMMIT_INFO from the subpool to the callers pool */
800 if (*commit_info_p)
802 svn_commit_info_t *tmp_commit_info;
804 tmp_commit_info = svn_create_commit_info(pool);
805 *tmp_commit_info = **commit_info_p;
806 if (tmp_commit_info->date)
807 tmp_commit_info->date = apr_pstrdup(pool, tmp_commit_info->date);
808 if (tmp_commit_info->author)
809 tmp_commit_info->author = apr_pstrdup(pool, tmp_commit_info->author);
810 if (tmp_commit_info->post_commit_err)
811 tmp_commit_info->post_commit_err
812 = apr_pstrdup(pool, tmp_commit_info->post_commit_err);
813 *commit_info_p = tmp_commit_info;
816 svn_pool_destroy(subpool);
818 return SVN_NO_ERROR;
822 svn_error_t *
823 svn_client_import2(svn_commit_info_t **commit_info_p,
824 const char *path,
825 const char *url,
826 svn_boolean_t nonrecursive,
827 svn_boolean_t no_ignore,
828 svn_client_ctx_t *ctx,
829 apr_pool_t *pool)
831 return svn_client_import3(commit_info_p,
832 path, url,
833 SVN_DEPTH_INFINITY_OR_FILES(! nonrecursive),
834 no_ignore, FALSE, ctx, pool);
837 svn_error_t *
838 svn_client_import(svn_client_commit_info_t **commit_info_p,
839 const char *path,
840 const char *url,
841 svn_boolean_t nonrecursive,
842 svn_client_ctx_t *ctx,
843 apr_pool_t *pool)
845 svn_commit_info_t *commit_info = NULL;
846 svn_error_t *err;
848 err = svn_client_import2(&commit_info,
849 path, url, nonrecursive,
850 FALSE, ctx, pool);
851 /* These structs have the same layout for the common fields. */
852 *commit_info_p = (svn_client_commit_info_t *) commit_info;
853 return err;
856 static svn_error_t *
857 remove_tmpfiles(apr_hash_t *tempfiles,
858 apr_pool_t *pool)
860 apr_hash_index_t *hi;
861 apr_pool_t *subpool;
863 /* Split if there's nothing to be done. */
864 if (! tempfiles)
865 return SVN_NO_ERROR;
867 /* Make a subpool. */
868 subpool = svn_pool_create(pool);
870 /* Clean up any tempfiles. */
871 for (hi = apr_hash_first(pool, tempfiles); hi; hi = apr_hash_next(hi))
873 const void *key;
874 void *val;
875 svn_error_t *err;
877 svn_pool_clear(subpool);
878 apr_hash_this(hi, &key, NULL, &val);
880 err = svn_io_remove_file((const char *)key, subpool);
882 if (err)
884 if (! APR_STATUS_IS_ENOENT(err->apr_err))
885 return err;
886 else
887 svn_error_clear(err);
891 /* Remove the subpool. */
892 svn_pool_destroy(subpool);
894 return SVN_NO_ERROR;
899 static svn_error_t *
900 reconcile_errors(svn_error_t *commit_err,
901 svn_error_t *unlock_err,
902 svn_error_t *bump_err,
903 svn_error_t *cleanup_err,
904 apr_pool_t *pool)
906 svn_error_t *err;
908 /* Early release (for good behavior). */
909 if (! (commit_err || unlock_err || bump_err || cleanup_err))
910 return SVN_NO_ERROR;
912 /* If there was a commit error, start off our error chain with
913 that. */
914 if (commit_err)
916 commit_err = svn_error_quick_wrap
917 (commit_err, _("Commit failed (details follow):"));
918 err = commit_err;
921 /* Else, create a new "general" error that will lead off the errors
922 that follow. */
923 else
924 err = svn_error_create(SVN_ERR_BASE, NULL,
925 _("Commit succeeded, but other errors follow:"));
927 /* If there was an unlock error... */
928 if (unlock_err)
930 /* Wrap the error with some headers. */
931 unlock_err = svn_error_quick_wrap
932 (unlock_err, _("Error unlocking locked dirs (details follow):"));
934 /* Append this error to the chain. */
935 svn_error_compose(err, unlock_err);
938 /* If there was a bumping error... */
939 if (bump_err)
941 /* Wrap the error with some headers. */
942 bump_err = svn_error_quick_wrap
943 (bump_err, _("Error bumping revisions post-commit (details follow):"));
945 /* Append this error to the chain. */
946 svn_error_compose(err, bump_err);
949 /* If there was a cleanup error... */
950 if (cleanup_err)
952 /* Wrap the error with some headers. */
953 cleanup_err = svn_error_quick_wrap
954 (cleanup_err, _("Error in post-commit clean-up (details follow):"));
956 /* Append this error to the chain. */
957 svn_error_compose(err, cleanup_err);
960 return err;
963 /* Remove redundancies by removing duplicates from NONRECURSIVE_TARGETS,
964 * and removing any target that either is, or is a descendant of, a path in
965 * RECURSIVE_TARGETS. Return the result in *PUNIQUE_TARGETS.
967 static svn_error_t *
968 remove_redundancies(apr_array_header_t **punique_targets,
969 const apr_array_header_t *nonrecursive_targets,
970 const apr_array_header_t *recursive_targets,
971 apr_pool_t *pool)
973 apr_pool_t *temp_pool;
974 apr_array_header_t *abs_recursive_targets = NULL;
975 apr_hash_t *abs_targets;
976 apr_array_header_t *rel_targets;
977 int i;
979 if ((nonrecursive_targets->nelts <= 0) || (! punique_targets))
981 /* No targets or no place to store our work means this function
982 really has nothing to do. */
983 if (punique_targets)
984 *punique_targets = NULL;
985 return SVN_NO_ERROR;
988 /* Initialize our temporary pool. */
989 temp_pool = svn_pool_create(pool);
991 /* Create our list of absolute paths for our "keepers" */
992 abs_targets = apr_hash_make(temp_pool);
994 /* Create our list of absolute paths for our recursive targets */
995 if (recursive_targets)
997 abs_recursive_targets = apr_array_make(temp_pool,
998 recursive_targets->nelts,
999 sizeof(const char *));
1001 for (i = 0; i < recursive_targets->nelts; i++)
1003 const char *rel_path =
1004 APR_ARRAY_IDX(recursive_targets, i, const char *);
1005 const char *abs_path;
1007 /* Get the absolute path for this target. */
1008 SVN_ERR(svn_path_get_absolute(&abs_path, rel_path, temp_pool));
1010 APR_ARRAY_PUSH(abs_recursive_targets, const char *) = abs_path;
1014 /* Create our list of untainted paths for our "keepers" */
1015 rel_targets = apr_array_make(pool, nonrecursive_targets->nelts,
1016 sizeof(const char *));
1018 /* For each target in our list we do the following:
1020 1. Calculate its absolute path (ABS_PATH).
1021 2. See if any of the keepers in RECURSIVE_TARGETS is a parent of, or
1022 is the same path as, ABS_PATH. If so, we ignore this
1023 target. If not, however, add this target's original path to
1024 REL_TARGETS. */
1025 for (i = 0; i < nonrecursive_targets->nelts; i++)
1027 const char *rel_path = APR_ARRAY_IDX(nonrecursive_targets, i,
1028 const char *);
1029 const char *abs_path;
1030 int j;
1031 svn_boolean_t keep_me;
1033 /* Get the absolute path for this target. */
1034 SVN_ERR(svn_path_get_absolute(&abs_path, rel_path, temp_pool));
1036 /* For each keeper in ABS_TARGETS, see if this target is the
1037 same as or a child of that keeper. */
1038 keep_me = TRUE;
1040 if (abs_recursive_targets)
1042 for (j = 0; j < abs_recursive_targets->nelts; j++)
1044 const char *keeper = APR_ARRAY_IDX(abs_recursive_targets, j,
1045 const char *);
1047 /* Quit here if we find this path already in the keepers. */
1048 if (strcmp(keeper, abs_path) == 0)
1050 keep_me = FALSE;
1051 break;
1054 /* Quit here if this path is a child of one of the keepers. */
1055 if (svn_path_is_child(keeper, abs_path, temp_pool))
1057 keep_me = FALSE;
1058 break;
1063 /* If this is a new keeper, add its absolute path to ABS_TARGETS
1064 and its original path to REL_TARGETS. */
1065 if (keep_me
1066 && apr_hash_get(abs_targets, abs_path, APR_HASH_KEY_STRING) == NULL)
1068 APR_ARRAY_PUSH(rel_targets, const char *) = rel_path;
1069 apr_hash_set(abs_targets, abs_path, APR_HASH_KEY_STRING, abs_path);
1073 /* Destroy our temporary pool. */
1074 svn_pool_destroy(temp_pool);
1076 /* Make sure we return the list of untainted keeper paths. */
1077 *punique_targets = rel_targets;
1079 return SVN_NO_ERROR;
1082 /* Adjust relative targets. If there is an empty string in REL_TARGETS
1083 * get the actual target anchor point. It is likely that this is one dir up
1084 * from BASE_DIR, therefor we need to prepend the name part of the actual
1085 * target to all paths in REL_TARGETS. Return the new anchor in *PBASE_DIR,
1086 * and the adjusted relative paths in *PREL_TARGETS.
1088 static svn_error_t *
1089 adjust_rel_targets(const char **pbase_dir,
1090 apr_array_header_t **prel_targets,
1091 const char *base_dir,
1092 apr_array_header_t *rel_targets,
1093 apr_pool_t *pool)
1095 const char *target;
1096 int i;
1097 svn_boolean_t anchor_one_up = FALSE;
1098 apr_array_header_t *new_rel_targets;
1100 for (i = 0; i < rel_targets->nelts; i++)
1102 target = APR_ARRAY_IDX(rel_targets, i, const char *);
1104 if (target[0] == '\0')
1106 anchor_one_up = TRUE;
1107 break;
1111 /* Default to not doing anything */
1112 new_rel_targets = rel_targets;
1114 if (anchor_one_up)
1116 const char *parent_dir, *name;
1118 SVN_ERR(svn_wc_get_actual_target(base_dir, &parent_dir, &name, pool));
1120 if (*name)
1122 /* Our new "grandfather directory" is the parent directory
1123 of the former one. */
1124 base_dir = apr_pstrdup(pool, parent_dir);
1126 new_rel_targets = apr_array_make(pool, rel_targets->nelts,
1127 sizeof(name));
1128 for (i = 0; i < rel_targets->nelts; i++)
1130 target = APR_ARRAY_IDX(rel_targets, i, const char *);
1131 target = svn_path_join(name, target, pool);
1132 APR_ARRAY_PUSH(new_rel_targets, const char *) = target;
1137 *pbase_dir = base_dir;
1138 *prel_targets = new_rel_targets;
1140 return SVN_NO_ERROR;
1144 /* For all lock tokens in ALL_TOKENS for URLs under BASE_URL, add them
1145 to a new hashtable allocated in POOL. *RESULT is set to point to this
1146 new hash table. *RESULT will be keyed on const char * URI-decoded paths
1147 relative to BASE_URL. The lock tokens will not be duplicated. */
1148 static svn_error_t *
1149 collect_lock_tokens(apr_hash_t **result,
1150 apr_hash_t *all_tokens,
1151 const char *base_url,
1152 apr_pool_t *pool)
1154 apr_hash_index_t *hi;
1155 size_t base_len = strlen(base_url);
1157 *result = apr_hash_make(pool);
1159 for (hi = apr_hash_first(pool, all_tokens); hi; hi = apr_hash_next(hi))
1161 const void *key;
1162 void *val;
1163 const char *url;
1164 const char *token;
1166 apr_hash_this(hi, &key, NULL, &val);
1167 url = key;
1168 token = val;
1170 if (strncmp(base_url, url, base_len) == 0
1171 && (url[base_len] == '\0' || url[base_len] == '/'))
1173 if (url[base_len] == '\0')
1174 url = "";
1175 else
1176 url = svn_path_uri_decode(url + base_len + 1, pool);
1177 apr_hash_set(*result, url, APR_HASH_KEY_STRING, token);
1181 return SVN_NO_ERROR;
1184 struct post_commit_baton
1186 svn_wc_committed_queue_t *queue;
1187 apr_pool_t *qpool;
1188 svn_wc_adm_access_t *base_dir_access;
1189 svn_boolean_t keep_changelist;
1190 svn_boolean_t keep_locks;
1191 apr_hash_t *digests;
1194 static svn_error_t *
1195 post_process_commit_item(void *baton, void *this_item, apr_pool_t *pool)
1197 struct post_commit_baton *btn = baton;
1198 apr_pool_t *subpool = btn->qpool;
1200 svn_client_commit_item3_t *item =
1201 *(svn_client_commit_item3_t **)this_item;
1202 svn_boolean_t loop_recurse = FALSE;
1203 const char *adm_access_path;
1204 svn_wc_adm_access_t *adm_access;
1205 svn_boolean_t remove_lock;
1206 svn_error_t *bump_err;
1208 if (item->kind == svn_node_dir)
1209 adm_access_path = item->path;
1210 else
1211 svn_path_split(item->path, &adm_access_path, NULL, pool);
1213 bump_err = svn_wc_adm_retrieve(&adm_access, btn->base_dir_access,
1214 adm_access_path, pool);
1215 if (bump_err
1216 && bump_err->apr_err == SVN_ERR_WC_NOT_LOCKED)
1218 /* Is it a directory that was deleted in the commit?
1219 Then we probably committed a missing directory. */
1220 if (item->kind == svn_node_dir
1221 && item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
1223 /* Mark it as deleted in the parent. */
1224 svn_error_clear(bump_err);
1225 return svn_wc_mark_missing_deleted(item->path,
1226 btn->base_dir_access, pool);
1229 if (bump_err)
1230 return bump_err;
1233 if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
1234 && (item->kind == svn_node_dir)
1235 && (item->copyfrom_url))
1236 loop_recurse = TRUE;
1238 remove_lock = (! btn->keep_locks && (item->state_flags
1239 & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN));
1241 /* Allocate the queue in a longer-lived pool than (iter)pool:
1242 we want it to survive the next iteration. */
1243 SVN_ERR(svn_wc_queue_committed
1244 (&(btn->queue),
1245 item->path, adm_access, loop_recurse,
1246 item->incoming_prop_changes,
1247 remove_lock, (! btn->keep_changelist),
1248 apr_hash_get(btn->digests, item->path, APR_HASH_KEY_STRING),
1249 subpool));
1251 return SVN_NO_ERROR;
1255 static svn_error_t *
1256 commit_item_is_changed(void *baton, void *this_item, apr_pool_t *pool)
1258 svn_client_commit_item3_t **item = this_item;
1260 if ((*item)->state_flags != SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)
1261 svn_iter_break(pool);
1263 return SVN_NO_ERROR;
1266 struct lock_dirs_baton
1268 svn_client_ctx_t *ctx;
1269 svn_wc_adm_access_t *base_dir_access;
1270 int levels_to_lock;
1273 static svn_error_t *
1274 lock_dirs_for_commit(void *baton, void *this_item, apr_pool_t *pool)
1276 struct lock_dirs_baton *btn = baton;
1277 svn_wc_adm_access_t *adm_access;
1279 return svn_wc_adm_open3(&adm_access, btn->base_dir_access,
1280 *(const char **)this_item,
1281 TRUE, /* Write lock */
1282 btn->levels_to_lock,
1283 btn->ctx->cancel_func,
1284 btn->ctx->cancel_baton,
1285 pool);
1288 struct check_dir_delete_baton
1290 svn_wc_adm_access_t *base_dir_access;
1291 svn_depth_t depth;
1294 static svn_error_t *
1295 check_nonrecursive_dir_delete(void *baton, void *this_item, apr_pool_t *pool)
1297 struct check_dir_delete_baton *btn = baton;
1298 svn_wc_adm_access_t *adm_access;
1299 const char *target;
1301 SVN_ERR(svn_path_get_absolute(&target, *(const char **)this_item, pool));
1302 SVN_ERR_W(svn_wc_adm_probe_retrieve(&adm_access, btn->base_dir_access,
1303 target, pool),
1304 _("Are all the targets part of the same working copy?"));
1306 /* ### TODO(sd): This check is slightly too strict. It should be
1307 ### possible to:
1309 ### * delete an empty directory when depth==svn_depth_empty;
1311 ### * delete a directory containing only files when
1312 ### depth==svn_depth_files;
1314 ### * delete a directory containing only files and empty
1315 ### subdirs when depth==svn_depth_immediates.
1317 ### But for now, we insist on svn_depth_infinity if you're
1318 ### going to delete a directory, because we're lazy and
1319 ### trying to get depthy commits working in the first place.
1321 ### This would be fairly easy to fix, though: just, well,
1322 ### check the above conditions!
1324 if (btn->depth != svn_depth_infinity)
1326 svn_wc_status2_t *status;
1327 svn_node_kind_t kind;
1329 SVN_ERR(svn_io_check_path(target, &kind, pool));
1331 if (kind == svn_node_dir)
1333 SVN_ERR(svn_wc_status2(&status, target, adm_access, pool));
1334 if (status->text_status == svn_wc_status_deleted ||
1335 status->text_status == svn_wc_status_replaced)
1336 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1337 _("Cannot non-recursively commit a "
1338 "directory deletion"));
1341 return SVN_NO_ERROR;
1344 svn_error_t *
1345 svn_client_commit4(svn_commit_info_t **commit_info_p,
1346 const apr_array_header_t *targets,
1347 svn_depth_t depth,
1348 svn_boolean_t keep_locks,
1349 svn_boolean_t keep_changelist,
1350 const char *changelist_name,
1351 svn_client_ctx_t *ctx,
1352 apr_pool_t *pool)
1354 const svn_delta_editor_t *editor;
1355 void *edit_baton;
1356 svn_ra_session_t *ra_session;
1357 const char *log_msg;
1358 const char *base_dir;
1359 const char *base_url;
1360 const char *target;
1361 apr_array_header_t *rel_targets;
1362 apr_array_header_t *dirs_to_lock;
1363 apr_array_header_t *dirs_to_lock_recursive;
1364 svn_boolean_t lock_base_dir_recursive = FALSE;
1365 apr_hash_t *committables, *lock_tokens, *tempfiles = NULL, *digests;
1366 svn_wc_adm_access_t *base_dir_access;
1367 apr_array_header_t *commit_items;
1368 svn_error_t *cmt_err = SVN_NO_ERROR, *unlock_err = SVN_NO_ERROR;
1369 svn_error_t *bump_err = SVN_NO_ERROR, *cleanup_err = SVN_NO_ERROR;
1370 svn_boolean_t commit_in_progress = FALSE;
1371 const char *display_dir = "";
1372 int i;
1374 /* Committing URLs doesn't make sense, so error if it's tried. */
1375 for (i = 0; i < targets->nelts; i++)
1377 target = APR_ARRAY_IDX(targets, i, const char *);
1378 if (svn_path_is_url(target))
1379 return svn_error_createf
1380 (SVN_ERR_ILLEGAL_TARGET, NULL,
1381 _("'%s' is a URL, but URLs cannot be commit targets"), target);
1384 /* Condense the target list. */
1385 SVN_ERR(svn_path_condense_targets(&base_dir, &rel_targets, targets,
1386 depth == svn_depth_infinity, pool));
1388 /* When svn_path_condense_targets() was written, we didn't have real
1389 * depths, we just had recursive / nonrecursive.
1391 * Nowadays things are more complex. If depth == svn_depth_files,
1392 * for example, and two targets are "foo" and "foo/bar", then
1393 * ideally we should condense out "foo/bar" if it's a file and not
1394 * if it's a directory. And, of course, later when we get adm
1395 * access batons for the commit, we'd ideally lock directories to
1396 * precisely the depth required and no deeper.
1398 * But for now we don't do that. Instead, we lock recursively from
1399 * base_dir, if depth indicates that we might need anything below
1400 * there (but note that above, we don't condense away targets that
1401 * need to be named explicitly when depth != svn_depth_infinity).
1403 * Here's a case where this all matters:
1405 * $ svn st -q
1406 * M A/D/G/rho
1407 * M iota
1408 * $ svn ci -m "log msg" --depth=immediates . A/D/G
1410 * If we don't lock base_dir recursively, then it will get an error...
1412 * subversion/libsvn_wc/lock.c:570: (apr_err=155004)
1413 * svn: Working copy '/blah/blah/blah/wc' locked
1414 * svn: run 'svn cleanup' to remove locks \
1415 * (type 'svn help cleanup' for details)
1417 * ...because later (see dirs_to_lock_recursively and dirs_to_lock)
1418 * we'd call svn_wc_adm_open3() to get access objects for "" and
1419 * "A/D/G", but the request for "" would fail because base_dir_access
1420 * would already be open for that directory. (In that circumstance,
1421 * you're supposed to use svn_wc_adm_retrieve() instead; but it
1422 * would be clumsy to have a conditional path just to decide between
1423 * open3() and retrieve().)
1425 * (Note that the results would be the same if even the working copy
1426 * were an explicit argument, e.g.:
1427 * 'svn ci -m "log msg" --depth=immediates wc wc/A/D/G'.)
1429 * So we set lock_base_dir_recursive=TRUE now, and end up locking
1430 * more than we need to, but this keeps the code simple and correct.
1432 * In an inspired bit of foresight, the adm locking code anticipated
1433 * the eventual addition of svn_depth_immediates, and allows us to
1434 * set the exact number of lock levels. So optimizing the code here
1435 * at least shouldn't require any changes to the adm locking system.
1437 if (depth == svn_depth_files || depth == svn_depth_immediates)
1439 const char *rel_target;
1440 for (i = 0; i < rel_targets->nelts; ++i)
1442 rel_target = APR_ARRAY_IDX(rel_targets, i, const char *);
1443 if (rel_target[0] == '\0')
1444 lock_base_dir_recursive = TRUE;
1448 /* No targets means nothing to commit, so just return. */
1449 if (! base_dir)
1450 goto cleanup;
1452 /* Prepare an array to accumulate dirs to lock */
1453 dirs_to_lock = apr_array_make(pool, 1, sizeof(target));
1454 dirs_to_lock_recursive = apr_array_make(pool, 1, sizeof(target));
1456 /* If we calculated only a base_dir and no relative targets, this
1457 must mean that we are being asked to commit (effectively) a
1458 single path. */
1459 if ((! rel_targets) || (! rel_targets->nelts))
1461 const char *parent_dir, *name;
1463 SVN_ERR(svn_wc_get_actual_target(base_dir, &parent_dir, &name, pool));
1464 if (*name)
1466 svn_node_kind_t kind;
1468 /* Our new "grandfather directory" is the parent directory
1469 of the former one. */
1470 base_dir = apr_pstrdup(pool, parent_dir);
1472 /* Make the array if it wasn't already created. */
1473 if (! rel_targets)
1474 rel_targets = apr_array_make(pool, targets->nelts, sizeof(name));
1476 /* Now, push this name as a relative path to our new
1477 base directory. */
1478 APR_ARRAY_PUSH(rel_targets, const char *) = name;
1480 target = svn_path_join(base_dir, name, pool);
1481 SVN_ERR(svn_io_check_path(target, &kind, pool));
1483 /* If the final target is a dir, we want to recursively lock it */
1484 if (kind == svn_node_dir)
1486 if (depth == svn_depth_infinity || depth == svn_depth_immediates)
1487 APR_ARRAY_PUSH(dirs_to_lock_recursive, const char *) = target;
1488 else
1489 APR_ARRAY_PUSH(dirs_to_lock, const char *) = target;
1492 else
1494 /* Unconditionally lock recursively down from base_dir. */
1495 lock_base_dir_recursive = TRUE;
1498 else if (! lock_base_dir_recursive)
1500 apr_pool_t *subpool = svn_pool_create(pool);
1502 SVN_ERR(adjust_rel_targets(&base_dir, &rel_targets,
1503 base_dir, rel_targets,
1504 pool));
1506 for (i = 0; i < rel_targets->nelts; i++)
1508 const char *parent_dir, *name;
1509 svn_node_kind_t kind;
1511 svn_pool_clear(subpool);
1513 target = svn_path_join(base_dir,
1514 APR_ARRAY_IDX(rel_targets, i, const char *),
1515 subpool);
1517 SVN_ERR(svn_io_check_path(target, &kind, subpool));
1519 /* If the final target is a dir, we want to lock it */
1520 if (kind == svn_node_dir)
1522 /* Notice how here we test infinity||immediates, but up
1523 in the call to svn_path_condense_targets(), we only
1524 tested depth==infinity. That's because condensation
1525 and adm lock acquisition serve different purposes. */
1526 if (depth == svn_depth_infinity || depth == svn_depth_immediates)
1527 APR_ARRAY_PUSH(dirs_to_lock_recursive,
1528 const char *) = apr_pstrdup(pool, target);
1529 else
1530 /* Don't lock if target is the base_dir, base_dir will be
1531 locked anyway and we can't lock it twice */
1532 if (strcmp(target, base_dir) != 0)
1533 APR_ARRAY_PUSH(dirs_to_lock,
1534 const char *) = apr_pstrdup(pool, target);
1537 /* Now we need to iterate over the parent paths of this path
1538 adding them to the set of directories we want to lock.
1539 Do nothing if target is already the base_dir. */
1540 if (strcmp(target, base_dir) != 0)
1542 svn_path_split(target, &parent_dir, &name, subpool);
1544 target = parent_dir;
1546 while (strcmp(target, base_dir) != 0)
1548 if ((target[0] == '\0') ||
1549 svn_dirent_is_root(target, strlen(target))
1551 abort();
1553 APR_ARRAY_PUSH(dirs_to_lock,
1554 const char *) = apr_pstrdup(pool, target);
1555 target = svn_path_dirname(target, subpool);
1560 svn_pool_destroy(subpool);
1563 SVN_ERR(svn_wc_adm_open3(&base_dir_access, NULL, base_dir,
1564 TRUE, /* Write lock */
1565 lock_base_dir_recursive ? -1 : 0, /* lock levels */
1566 ctx->cancel_func, ctx->cancel_baton,
1567 pool));
1569 if (!lock_base_dir_recursive)
1571 apr_array_header_t *unique_dirs_to_lock;
1572 struct lock_dirs_baton btn;
1574 /* Sort the paths in a depth-last directory-ish order. */
1575 qsort(dirs_to_lock->elts, dirs_to_lock->nelts,
1576 dirs_to_lock->elt_size, svn_sort_compare_paths);
1577 qsort(dirs_to_lock_recursive->elts, dirs_to_lock_recursive->nelts,
1578 dirs_to_lock_recursive->elt_size, svn_sort_compare_paths);
1580 /* Remove any duplicates */
1581 SVN_ERR(svn_path_remove_redundancies(&unique_dirs_to_lock,
1582 dirs_to_lock_recursive,
1583 pool));
1584 dirs_to_lock_recursive = unique_dirs_to_lock;
1586 /* Remove dirs and descendants from dirs_to_lock if there is
1587 any ancestor in dirs_to_lock_recursive */
1588 SVN_ERR(remove_redundancies(&unique_dirs_to_lock,
1589 dirs_to_lock,
1590 dirs_to_lock_recursive,
1591 pool));
1592 dirs_to_lock = unique_dirs_to_lock;
1594 btn.base_dir_access = base_dir_access;
1595 btn.ctx = ctx;
1596 btn.levels_to_lock = 0;
1597 /* First lock all the dirs to be locked non-recursively */
1598 if (dirs_to_lock)
1599 SVN_ERR(svn_iter_apr_array(NULL, dirs_to_lock,
1600 lock_dirs_for_commit, &btn, pool));
1602 /* Lock the rest of the targets (recursively) */
1603 btn.levels_to_lock = -1;
1604 if (dirs_to_lock_recursive)
1605 SVN_ERR(svn_iter_apr_array(NULL, dirs_to_lock_recursive,
1606 lock_dirs_for_commit, &btn, pool));
1609 /* One day we might support committing from multiple working copies, but
1610 we don't yet. This check ensures that we don't silently commit a
1611 subset of the targets.
1613 At the same time, if a non-recursive commit is desired, do not
1614 allow a deleted directory as one of the targets. */
1616 struct check_dir_delete_baton btn;
1618 btn.base_dir_access = base_dir_access;
1619 btn.depth = depth;
1620 SVN_ERR(svn_iter_apr_array(NULL, targets,
1621 check_nonrecursive_dir_delete, &btn,
1622 pool));
1625 /* Crawl the working copy for commit items. */
1626 if ((cmt_err = svn_client__harvest_committables(&committables,
1627 &lock_tokens,
1628 base_dir_access,
1629 rel_targets,
1630 depth,
1631 ! keep_locks,
1632 changelist_name,
1633 ctx,
1634 pool)))
1635 goto cleanup;
1637 /* ### todo: Currently there should be only one hash entry, which
1638 has a hacked name until we have the entries files storing
1639 canonical repository URLs. Then, the hacked name can go away
1640 and be replaced with a canonical repos URL, and from there we
1641 are poised to started handling nested working copies. See
1642 http://subversion.tigris.org/issues/show_bug.cgi?id=960. */
1643 if (! ((commit_items = apr_hash_get(committables,
1644 SVN_CLIENT__SINGLE_REPOS_NAME,
1645 APR_HASH_KEY_STRING))))
1646 goto cleanup;
1648 /* If our array of targets contains only locks (and no actual file
1649 or prop modifications), then we return here to avoid committing a
1650 revision with no changes. */
1652 svn_boolean_t not_found_changed_path = TRUE;
1655 cmt_err = svn_iter_apr_array(&not_found_changed_path,
1656 commit_items,
1657 commit_item_is_changed, NULL, pool);
1658 if (not_found_changed_path || cmt_err)
1659 goto cleanup;
1662 /* Go get a log message. If an error occurs, or no log message is
1663 specified, abort the operation. */
1664 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1666 const char *tmp_file;
1667 cmt_err = svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
1668 ctx, pool);
1670 if (cmt_err || (! log_msg))
1671 goto cleanup;
1673 else
1674 log_msg = "";
1676 /* Sort and condense our COMMIT_ITEMS. */
1677 if ((cmt_err = svn_client__condense_commit_items(&base_url,
1678 commit_items,
1679 pool)))
1680 goto cleanup;
1682 /* Collect our lock tokens with paths relative to base_url. */
1683 if ((cmt_err = collect_lock_tokens(&lock_tokens, lock_tokens, base_url,
1684 pool)))
1685 goto cleanup;
1687 if ((cmt_err = get_ra_editor(&ra_session, NULL,
1688 &editor, &edit_baton, ctx,
1689 base_url, base_dir, base_dir_access,
1690 log_msg, commit_items, commit_info_p,
1691 TRUE, lock_tokens, keep_locks, pool)))
1692 goto cleanup;
1694 /* Make a note that we have a commit-in-progress. */
1695 commit_in_progress = TRUE;
1697 /* Determine prefix to strip from the commit notify messages */
1698 if ((cmt_err = svn_path_get_absolute(&display_dir,
1699 display_dir, pool)))
1700 goto cleanup;
1701 display_dir = svn_path_get_longest_ancestor(display_dir, base_dir, pool);
1703 /* Perform the commit. */
1704 cmt_err = svn_client__do_commit(base_url, commit_items, base_dir_access,
1705 editor, edit_baton,
1706 display_dir,
1707 &tempfiles, &digests, ctx, pool);
1709 /* Handle a successful commit. */
1710 if ((! cmt_err)
1711 || (cmt_err->apr_err == SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED))
1713 svn_wc_committed_queue_t *queue = svn_wc_committed_queue_create(pool);
1714 struct post_commit_baton btn;
1716 btn.queue = queue;
1717 btn.qpool = pool;
1718 btn.base_dir_access = base_dir_access;
1719 btn.keep_changelist = keep_changelist;
1720 btn.keep_locks = keep_locks;
1721 btn.digests = digests;
1723 /* Make a note that our commit is finished. */
1724 commit_in_progress = FALSE;
1726 bump_err = svn_iter_apr_array(NULL, commit_items,
1727 post_process_commit_item, &btn,
1728 pool);
1729 if (bump_err)
1730 goto cleanup;
1732 assert(*commit_info_p);
1733 bump_err
1734 = svn_wc_process_committed_queue(queue, base_dir_access,
1735 (*commit_info_p)->revision,
1736 (*commit_info_p)->date,
1737 (*commit_info_p)->author,
1738 pool);
1741 /* Sleep to ensure timestamp integrity. */
1742 svn_sleep_for_timestamps();
1744 cleanup:
1745 /* Abort the commit if it is still in progress. */
1746 if (commit_in_progress)
1747 svn_error_clear(editor->abort_edit(edit_baton, pool));
1749 /* A bump error is likely to occur while running a working copy log file,
1750 explicitly unlocking and removing temporary files would be wrong in
1751 that case. A commit error (cmt_err) should only occur before any
1752 attempt to modify the working copy, so it doesn't prevent explicit
1753 clean-up. */
1754 if (! bump_err)
1756 unlock_err = svn_wc_adm_close(base_dir_access);
1758 if (! unlock_err)
1759 cleanup_err = remove_tmpfiles(tempfiles, pool);
1762 /* As per our promise, if *commit_info_p isn't set, provide a default where
1763 rev = SVN_INVALID_REVNUM. */
1764 if (! *commit_info_p)
1765 *commit_info_p = svn_create_commit_info(pool);
1767 return reconcile_errors(cmt_err, unlock_err, bump_err, cleanup_err, pool);
1770 svn_error_t *
1771 svn_client_commit3(svn_commit_info_t **commit_info_p,
1772 const apr_array_header_t *targets,
1773 svn_boolean_t recurse,
1774 svn_boolean_t keep_locks,
1775 svn_client_ctx_t *ctx,
1776 apr_pool_t *pool)
1778 svn_depth_t depth = SVN_DEPTH_INFINITY_OR_FILES(recurse);
1780 return svn_client_commit4(commit_info_p, targets, depth, keep_locks,
1781 FALSE, NULL, ctx, pool);
1784 svn_error_t *
1785 svn_client_commit2(svn_client_commit_info_t **commit_info_p,
1786 const apr_array_header_t *targets,
1787 svn_boolean_t recurse,
1788 svn_boolean_t keep_locks,
1789 svn_client_ctx_t *ctx,
1790 apr_pool_t *pool)
1792 svn_commit_info_t *commit_info = NULL;
1793 svn_error_t *err;
1795 err = svn_client_commit3(&commit_info, targets, recurse, keep_locks,
1796 ctx, pool);
1797 /* These structs have the same layout for the common fields. */
1798 *commit_info_p = (svn_client_commit_info_t *) commit_info;
1799 return err;
1802 svn_error_t *
1803 svn_client_commit(svn_client_commit_info_t **commit_info_p,
1804 const apr_array_header_t *targets,
1805 svn_boolean_t nonrecursive,
1806 svn_client_ctx_t *ctx,
1807 apr_pool_t *pool)
1809 return svn_client_commit2(commit_info_p, targets,
1810 nonrecursive ? FALSE : TRUE,
1811 TRUE,
1812 ctx, pool);