42
40
#include "svn_error_codes.h"
43
41
#include "svn_dirent_uri.h"
44
42
#include "svn_path.h"
47
43
#include "svn_sorts.h"
48
#include "svn_props.h"
50
45
#include "client.h"
51
46
#include "private/svn_wc_private.h"
52
#include "private/svn_magic.h"
47
#include "private/svn_ra_private.h"
54
49
#include "svn_private_config.h"
56
/* Import context baton.
58
### TODO: Add the following items to this baton:
59
/` import editor/baton. `/
60
const svn_delta_editor_t *editor;
63
/` Client context baton `/
64
svn_client_ctx_t `ctx;
66
/` Paths (keys) excluded from the import (values ignored) `/
69
typedef struct import_ctx_t
71
/* Whether any changes were made to the repository */
72
svn_boolean_t repos_changed;
74
/* A magic cookie for mime-type detection. */
75
svn_magic__cookie_t *magic_cookie;
79
/* Apply PATH's contents (as a delta against the empty string) to
80
FILE_BATON in EDITOR. Use POOL for any temporary allocation.
81
PROPERTIES is the set of node properties set on this file.
83
Fill DIGEST with the md5 checksum of the sent file; DIGEST must be
84
at least APR_MD5_DIGESTSIZE bytes long. */
86
/* ### how does this compare against svn_wc_transmit_text_deltas2() ??? */
89
send_file_contents(const char *path,
91
const svn_delta_editor_t *editor,
92
apr_hash_t *properties,
93
unsigned char *digest,
96
svn_stream_t *contents;
97
svn_txdelta_window_handler_t handler;
99
const svn_string_t *eol_style_val = NULL, *keywords_val = NULL;
100
svn_boolean_t special = FALSE;
101
svn_subst_eol_style_t eol_style;
103
apr_hash_t *keywords;
105
/* If there are properties, look for EOL-style and keywords ones. */
108
eol_style_val = apr_hash_get(properties, SVN_PROP_EOL_STYLE,
109
sizeof(SVN_PROP_EOL_STYLE) - 1);
110
keywords_val = apr_hash_get(properties, SVN_PROP_KEYWORDS,
111
sizeof(SVN_PROP_KEYWORDS) - 1);
112
if (apr_hash_get(properties, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING))
116
/* Get an editor func that wants to consume the delta stream. */
117
SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
118
&handler, &handler_baton));
121
svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
125
eol_style = svn_subst_eol_style_none;
129
SVN_ERR(svn_subst_build_keywords2(&keywords, keywords_val->data,
130
APR_STRINGIFY(SVN_INVALID_REVNUM),
137
SVN_ERR(svn_subst_read_specialfile(&contents, path, pool, pool));
141
/* Open the working copy file. */
142
SVN_ERR(svn_stream_open_readonly(&contents, path, pool, pool));
144
/* If we have EOL styles or keywords, then detranslate the file. */
145
if (svn_subst_translation_required(eol_style, eol, keywords,
148
if (eol_style == svn_subst_eol_style_unknown)
149
return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
150
_("%s property on '%s' contains "
151
"unrecognized EOL-style '%s'"),
152
SVN_PROP_EOL_STYLE, path,
153
eol_style_val->data);
155
/* We're importing, so translate files with 'native' eol-style to
156
* repository-normal form, not to this platform's native EOL. */
157
if (eol_style == svn_subst_eol_style_native)
158
eol = SVN_SUBST_NATIVE_EOL_STR;
160
/* Wrap the working copy stream with a filter to detranslate it. */
161
contents = svn_subst_stream_translated(contents,
170
/* Send the file's contents to the delta-window handler. */
171
return svn_error_trace(svn_txdelta_send_stream(contents, handler,
172
handler_baton, digest,
177
/* Import file PATH as EDIT_PATH in the repository directory indicated
178
* by DIR_BATON in EDITOR.
180
* Accumulate file paths and their batons in FILES, which must be
181
* non-null. (These are used to send postfix textdeltas later).
183
* If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON
186
* Use POOL for any temporary allocation.
189
import_file(const svn_delta_editor_t *editor,
192
const char *edit_path,
193
import_ctx_t *import_ctx,
194
svn_client_ctx_t *ctx,
198
const char *mimetype = NULL;
199
unsigned char digest[APR_MD5_DIGESTSIZE];
200
const char *text_checksum;
201
apr_hash_t* properties;
202
apr_hash_index_t *hi;
203
svn_node_kind_t kind;
204
svn_boolean_t is_special;
206
SVN_ERR(svn_path_check_valid(path, pool));
208
SVN_ERR(svn_io_check_special_path(path, &kind, &is_special, pool));
210
/* Add the file, using the pool from the FILES hash. */
211
SVN_ERR(editor->add_file(edit_path, dir_baton, NULL, SVN_INVALID_REVNUM,
214
/* Remember that the repository was modified */
215
import_ctx->repos_changed = TRUE;
219
/* add automatic properties */
220
SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, path,
221
import_ctx->magic_cookie,
225
properties = apr_hash_make(pool);
229
for (hi = apr_hash_first(pool, properties); hi; hi = apr_hash_next(hi))
231
const char *pname = svn__apr_hash_index_key(hi);
232
const svn_string_t *pval = svn__apr_hash_index_val(hi);
234
SVN_ERR(editor->change_file_prop(file_baton, pname, pval, pool));
238
if (ctx->notify_func2)
240
svn_wc_notify_t *notify
241
= svn_wc_create_notify(path, svn_wc_notify_commit_added, pool);
242
notify->kind = svn_node_file;
243
notify->mime_type = mimetype;
244
notify->content_state = notify->prop_state
245
= svn_wc_notify_state_inapplicable;
246
notify->lock_state = svn_wc_notify_lock_state_inapplicable;
247
(*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
250
/* If this is a special file, we need to set the svn:special
251
property and create a temporary detranslated version in order to
252
send to the server. */
255
apr_hash_set(properties, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING,
256
svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
257
SVN_ERR(editor->change_file_prop(file_baton, SVN_PROP_SPECIAL,
258
apr_hash_get(properties,
260
APR_HASH_KEY_STRING),
264
/* Now, transmit the file contents. */
265
SVN_ERR(send_file_contents(path, file_baton, editor,
266
properties, digest, pool));
268
/* Finally, close the file. */
270
svn_checksum_to_cstring(svn_checksum__from_digest(digest, svn_checksum_md5,
273
return editor->close_file(file_baton, text_checksum, pool);
277
/* Import directory PATH into the repository directory indicated by
278
* DIR_BATON in EDITOR. EDIT_PATH is the path imported as the root
279
* directory, so all edits are relative to that.
281
* DEPTH is the depth at this point in the descent (it may be changed
282
* for recursive calls).
284
* Accumulate file paths and their batons in FILES, which must be
285
* non-null. (These are used to send postfix textdeltas later).
287
* EXCLUDES is a hash whose keys are absolute paths to exclude from
288
* the import (values are unused).
290
* If NO_IGNORE is FALSE, don't import files or directories that match
293
* If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for each
296
* Use POOL for any temporary allocation. */
298
import_dir(const svn_delta_editor_t *editor,
301
const char *edit_path,
303
apr_hash_t *excludes,
304
svn_boolean_t no_ignore,
305
svn_boolean_t ignore_unknown_node_types,
306
import_ctx_t *import_ctx,
307
svn_client_ctx_t *ctx,
310
apr_pool_t *subpool = svn_pool_create(pool); /* iteration pool */
312
apr_hash_index_t *hi;
313
apr_array_header_t *ignores;
315
SVN_ERR(svn_path_check_valid(path, pool));
318
SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
320
SVN_ERR(svn_io_get_dirents3(&dirents, path, TRUE, pool, pool));
322
for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
324
const char *this_path, *this_edit_path, *abs_path;
325
const char *filename = svn__apr_hash_index_key(hi);
326
const svn_io_dirent_t *dirent = svn__apr_hash_index_val(hi);
328
svn_pool_clear(subpool);
330
if (ctx->cancel_func)
331
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
333
if (svn_wc_is_adm_dir(filename, subpool))
335
/* If someone's trying to import a directory named the same
336
as our administrative directories, that's probably not
337
what they wanted to do. If they are importing a file
338
with that name, something is bound to blow up when they
339
checkout what they've imported. So, just skip items with
341
if (ctx->notify_func2)
343
svn_wc_notify_t *notify
344
= svn_wc_create_notify(svn_dirent_join(path, filename,
346
svn_wc_notify_skip, subpool);
347
notify->kind = svn_node_dir;
348
notify->content_state = notify->prop_state
349
= svn_wc_notify_state_inapplicable;
350
notify->lock_state = svn_wc_notify_lock_state_inapplicable;
351
(*ctx->notify_func2)(ctx->notify_baton2, notify, subpool);
356
/* Typically, we started importing from ".", in which case
357
edit_path is "". So below, this_path might become "./blah",
358
and this_edit_path might become "blah", for example. */
359
this_path = svn_dirent_join(path, filename, subpool);
360
this_edit_path = svn_relpath_join(edit_path, filename, subpool);
362
/* If this is an excluded path, exclude it. */
363
SVN_ERR(svn_dirent_get_absolute(&abs_path, this_path, subpool));
364
if (apr_hash_get(excludes, abs_path, APR_HASH_KEY_STRING))
367
if ((!no_ignore) && svn_wc_match_ignore_list(filename, ignores,
371
if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
373
void *this_dir_baton;
375
/* Add the new subdirectory, getting a descent baton from
377
SVN_ERR(editor->add_directory(this_edit_path, dir_baton,
378
NULL, SVN_INVALID_REVNUM, subpool,
381
/* Remember that the repository was modified */
382
import_ctx->repos_changed = TRUE;
384
/* By notifying before the recursive call below, we display
385
a directory add before displaying adds underneath the
386
directory. To do it the other way around, just move this
387
after the recursive call. */
388
if (ctx->notify_func2)
390
svn_wc_notify_t *notify
391
= svn_wc_create_notify(this_path, svn_wc_notify_commit_added,
393
notify->kind = svn_node_dir;
394
notify->content_state = notify->prop_state
395
= svn_wc_notify_state_inapplicable;
396
notify->lock_state = svn_wc_notify_lock_state_inapplicable;
397
(*ctx->notify_func2)(ctx->notify_baton2, notify, subpool);
402
svn_depth_t depth_below_here = depth;
403
if (depth == svn_depth_immediates)
404
depth_below_here = svn_depth_empty;
406
SVN_ERR(import_dir(editor, this_dir_baton, this_path,
407
this_edit_path, depth_below_here, excludes,
408
no_ignore, ignore_unknown_node_types,
413
/* Finally, close the sub-directory. */
414
SVN_ERR(editor->close_directory(this_dir_baton, subpool));
416
else if (dirent->kind == svn_node_file && depth >= svn_depth_files)
418
SVN_ERR(import_file(editor, dir_baton, this_path,
419
this_edit_path, import_ctx, ctx, subpool));
421
else if (dirent->kind != svn_node_dir && dirent->kind != svn_node_file)
423
if (ignore_unknown_node_types)
426
if (ctx->notify_func2)
428
svn_wc_notify_t *notify
429
= svn_wc_create_notify(this_path,
430
svn_wc_notify_skip, subpool);
431
notify->kind = svn_node_dir;
432
notify->content_state = notify->prop_state
433
= svn_wc_notify_state_inapplicable;
434
notify->lock_state = svn_wc_notify_lock_state_inapplicable;
435
(*ctx->notify_func2)(ctx->notify_baton2, notify, subpool);
439
return svn_error_createf
440
(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
441
_("Unknown or unversionable type for '%s'"),
442
svn_dirent_local_style(this_path, subpool));
446
svn_pool_destroy(subpool);
451
/* Recursively import PATH to a repository using EDITOR and
452
* EDIT_BATON. PATH can be a file or directory.
454
* DEPTH is the depth at which to import PATH; it behaves as for
455
* svn_client_import4().
457
* NEW_ENTRIES is an ordered array of path components that must be
458
* created in the repository (where the ordering direction is
459
* parent-to-child). If PATH is a directory, NEW_ENTRIES may be empty
460
* -- the result is an import which creates as many new entries in the
461
* top repository target directory as there are importable entries in
462
* the top of PATH; but if NEW_ENTRIES is not empty, its last item is
463
* the name of a new subdirectory in the repository to hold the
464
* import. If PATH is a file, NEW_ENTRIES may not be empty, and its
465
* last item is the name used for the file in the repository. If
466
* NEW_ENTRIES contains more than one item, all but the last item are
467
* the names of intermediate directories that are created before the
468
* real import begins. NEW_ENTRIES may NOT be NULL.
470
* EXCLUDES is a hash whose keys are absolute paths to exclude from
471
* the import (values are unused).
473
* If NO_IGNORE is FALSE, don't import files or directories that match
476
* If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for
477
* each imported path, passing actions svn_wc_notify_commit_added.
479
* Use POOL for any temporary allocation.
481
* Note: the repository directory receiving the import was specified
482
* when the editor was fetched. (I.e, when EDITOR->open_root() is
483
* called, it returns a directory baton for that directory, which is
484
* not necessarily the root.)
487
import(const char *path,
488
const apr_array_header_t *new_entries,
489
const svn_delta_editor_t *editor,
492
apr_hash_t *excludes,
493
svn_boolean_t no_ignore,
494
svn_boolean_t ignore_unknown_node_types,
495
svn_client_ctx_t *ctx,
499
svn_node_kind_t kind;
500
apr_array_header_t *ignores;
501
apr_array_header_t *batons = NULL;
502
const char *edit_path = "";
503
import_ctx_t *import_ctx = apr_pcalloc(pool, sizeof(*import_ctx));
505
svn_magic__init(&import_ctx->magic_cookie, pool);
507
/* Get a root dir baton. We pass an invalid revnum to open_root
508
to mean "base this on the youngest revision". Should we have an
509
SVN_YOUNGEST_REVNUM defined for these purposes? */
510
SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
513
/* Import a file or a directory tree. */
514
SVN_ERR(svn_io_check_path(path, &kind, pool));
516
/* Make the intermediate directory components necessary for properly
517
rooting our import source tree. */
518
if (new_entries->nelts)
522
batons = apr_array_make(pool, new_entries->nelts, sizeof(void *));
523
for (i = 0; i < new_entries->nelts; i++)
525
const char *component = APR_ARRAY_IDX(new_entries, i, const char *);
526
edit_path = svn_relpath_join(edit_path, component, pool);
528
/* If this is the last path component, and we're importing a
529
file, then this component is the name of the file, not an
530
intermediate directory. */
531
if ((i == new_entries->nelts - 1) && (kind == svn_node_file))
534
APR_ARRAY_PUSH(batons, void *) = root_baton;
535
SVN_ERR(editor->add_directory(edit_path,
537
NULL, SVN_INVALID_REVNUM,
540
/* Remember that the repository was modified */
541
import_ctx->repos_changed = TRUE;
544
else if (kind == svn_node_file)
546
return svn_error_create
547
(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
548
_("New entry name required when importing a file"));
551
/* Note that there is no need to check whether PATH's basename is
552
the same name that we reserve for our administrative
553
subdirectories. It would be strange -- though not illegal -- to
554
import the contents of a directory of that name, because the
555
directory's own name is not part of those contents. Of course,
556
if something underneath it also has our reserved name, then we'll
559
if (kind == svn_node_file)
561
svn_boolean_t ignores_match = FALSE;
565
SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool));
566
ignores_match = svn_wc_match_ignore_list(path, ignores, pool);
569
SVN_ERR(import_file(editor, root_baton, path, edit_path,
570
import_ctx, ctx, pool));
572
else if (kind == svn_node_dir)
574
SVN_ERR(import_dir(editor, root_baton, path, edit_path,
575
depth, excludes, no_ignore,
576
ignore_unknown_node_types, import_ctx, ctx, pool));
579
else if (kind == svn_node_none
580
|| kind == svn_node_unknown)
582
return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
583
_("'%s' does not exist"),
584
svn_dirent_local_style(path, pool));
587
/* Close up shop; it's time to go home. */
588
SVN_ERR(editor->close_directory(root_baton, pool));
589
if (batons && batons->nelts)
592
while ((baton = (void **) apr_array_pop(batons)))
594
SVN_ERR(editor->close_directory(*baton, pool));
598
if (import_ctx->repos_changed)
599
return editor->close_edit(edit_baton, pool);
601
return editor->abort_edit(edit_baton, pool);
605
51
struct capture_baton_t {
606
52
svn_commit_callback2_t original_callback;
607
53
void *original_baton;
647
90
apr_hash_t *commit_revprops;
649
/* Open an RA session to URL. */
650
SVN_ERR(svn_client__open_ra_session_internal(ra_session, NULL, base_url,
651
base_dir_abspath, commit_items,
652
is_commit, !is_commit,
655
/* If this is an import (aka, not a commit), we need to verify that
656
our repository URL exists. */
659
svn_node_kind_t kind;
661
SVN_ERR(svn_ra_check_path(*ra_session, "", SVN_INVALID_REVNUM,
663
if (kind == svn_node_none)
664
return svn_error_createf(SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
665
_("Path '%s' does not exist"),
91
apr_hash_t *relpath_map = NULL;
669
93
SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
670
94
log_msg, ctx, pool));
96
#ifdef ENABLE_EV2_SHIMS
100
apr_pool_t *iterpool = svn_pool_create(pool);
102
relpath_map = apr_hash_make(pool);
103
for (i = 0; i < commit_items->nelts; i++)
105
svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
106
svn_client_commit_item3_t *);
112
svn_pool_clear(iterpool);
113
SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
114
ctx->wc_ctx, item->path, FALSE, pool,
117
svn_hash_sets(relpath_map, relpath, item->path);
119
svn_pool_destroy(iterpool);
672
123
/* Fetch RA commit editor. */
673
return svn_ra_get_commit_editor3(*ra_session, editor, edit_baton,
674
commit_revprops, commit_callback,
675
commit_baton, lock_tokens, keep_locks,
124
SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
125
svn_client__get_shim_callbacks(ctx->wc_ctx,
126
relpath_map, pool)));
127
SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton,
128
commit_revprops, commit_callback,
129
commit_baton, lock_tokens, keep_locks,
680
136
/*** Public Interfaces. ***/
683
svn_client_import4(const char *path,
686
svn_boolean_t no_ignore,
687
svn_boolean_t ignore_unknown_node_types,
688
const apr_hash_t *revprop_table,
689
svn_commit_callback2_t commit_callback,
691
svn_client_ctx_t *ctx,
694
svn_error_t *err = SVN_NO_ERROR;
695
const char *log_msg = "";
696
const svn_delta_editor_t *editor;
698
svn_ra_session_t *ra_session;
699
apr_hash_t *excludes = apr_hash_make(pool);
700
svn_node_kind_t kind;
701
const char *local_abspath;
702
apr_array_header_t *new_entries = apr_array_make(pool, 4,
703
sizeof(const char *));
708
if (svn_path_is_url(path))
709
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
710
_("'%s' is not a local path"), path);
712
SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
714
/* Create a new commit item and add it to the array. */
715
if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
717
/* If there's a log message gatherer, create a temporary commit
718
item array solely to help generate the log message. The
719
array is not used for the import itself. */
720
svn_client_commit_item3_t *item;
721
const char *tmp_file;
722
apr_array_header_t *commit_items
723
= apr_array_make(pool, 1, sizeof(item));
725
item = svn_client_commit_item3_create(pool);
726
item->path = apr_pstrdup(pool, path);
727
item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
728
APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
730
SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
736
const char *abs_path;
737
SVN_ERR(svn_dirent_get_absolute(&abs_path, tmp_file, pool));
738
apr_hash_set(excludes, abs_path, APR_HASH_KEY_STRING, (void *)1);
742
SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
744
/* Figure out all the path components we need to create just to have
745
a place to stick our imported tree. */
746
subpool = svn_pool_create(pool);
749
svn_pool_clear(subpool);
751
/* See if the user is interested in cancelling this operation. */
752
if (ctx->cancel_func)
753
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
757
/* If get_ra_editor below failed we either tried to open
758
an invalid url, or else some other kind of error. In case
759
the url was bad we back up a directory and try again. */
761
if (err->apr_err != SVN_ERR_FS_NO_SUCH_ENTRY)
764
svn_error_clear(err);
766
svn_uri_split(&temp, &dir, url, pool);
767
APR_ARRAY_PUSH(new_entries, const char *) = dir;
771
while ((err = get_ra_editor(&ra_session,
772
&editor, &edit_baton, ctx, url, NULL,
773
log_msg, NULL, revprop_table, FALSE, NULL, TRUE,
774
commit_callback, commit_baton, subpool)));
776
/* Reverse the order of the components we added to our NEW_ENTRIES array. */
777
if (new_entries->nelts)
780
const char *component;
781
for (i = 0; i < (new_entries->nelts / 2); i++)
783
j = new_entries->nelts - i - 1;
785
APR_ARRAY_IDX(new_entries, i, const char *);
786
APR_ARRAY_IDX(new_entries, i, const char *) =
787
APR_ARRAY_IDX(new_entries, j, const char *);
788
APR_ARRAY_IDX(new_entries, j, const char *) =
793
/* An empty NEW_ENTRIES list the first call to get_ra_editor() above
794
succeeded. That means that URL corresponds to an already
795
existing filesystem entity. */
796
if (kind == svn_node_file && (! new_entries->nelts))
797
return svn_error_createf
798
(SVN_ERR_ENTRY_EXISTS, NULL,
799
_("Path '%s' already exists"), url);
801
/* The repository doesn't know about the reserved administrative
803
if (new_entries->nelts
804
/* What's this, what's this? This assignment is here because we
805
use the value to construct the error message just below. It
806
may not be aesthetically pleasing, but it's less ugly than
807
calling APR_ARRAY_IDX twice. */
808
&& svn_wc_is_adm_dir(temp = APR_ARRAY_IDX(new_entries,
809
new_entries->nelts - 1,
812
return svn_error_createf
813
(SVN_ERR_CL_ADM_DIR_RESERVED, NULL,
814
_("'%s' is a reserved name and cannot be imported"),
815
/* ### Is svn_path_local_style() really necessary for this? */
816
svn_dirent_local_style(temp, pool));
819
/* If an error occurred during the commit, abort the edit and return
820
the error. We don't even care if the abort itself fails. */
821
if ((err = import(path, new_entries, editor, edit_baton,
822
depth, excludes, no_ignore,
823
ignore_unknown_node_types, ctx, subpool)))
825
svn_error_clear(editor->abort_edit(edit_baton, subpool));
829
svn_pool_destroy(subpool);
835
138
static svn_error_t *
836
139
reconcile_errors(svn_error_t *commit_err,
837
140
svn_error_t *unlock_err,
769
/* For every target that was moved verify that both halves of the
770
* move are part of the commit. */
771
for (i = 0; i < commit_items->nelts; i++)
773
svn_client_commit_item3_t *item =
774
APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
776
svn_pool_clear(iterpool);
778
if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE)
780
/* ### item->moved_from_abspath contains the move origin */
781
const char *moved_from_abspath;
782
const char *delete_op_root_abspath;
784
cmt_err = svn_error_trace(svn_wc__node_was_moved_here(
786
&delete_op_root_abspath,
787
ctx->wc_ctx, item->path,
788
iterpool, iterpool));
792
if (moved_from_abspath && delete_op_root_abspath &&
793
strcmp(moved_from_abspath, delete_op_root_abspath) == 0)
796
svn_boolean_t found_delete_half =
797
(svn_hash_gets(committables->by_path, delete_op_root_abspath)
800
if (!found_delete_half)
802
const char *delete_half_parent_abspath;
804
/* The delete-half isn't in the commit target list.
805
* However, it might itself be the child of a deleted node,
806
* either because of another move or a deletion.
808
* For example, consider: mv A/B B; mv B/C C; commit;
809
* C's moved-from A/B/C is a child of the deleted A/B.
810
* A/B/C does not appear in the commit target list, but
812
* (Note that moved-from information is always stored
813
* relative to the BASE tree, so we have 'C moved-from
814
* A/B/C', not 'C moved-from B/C'.)
816
* An example involving a move and a delete would be:
817
* mv A/B C; rm A; commit;
818
* Now C is moved-from A/B which does not appear in the
819
* commit target list, but A does appear.
822
/* Scan upwards for a deletion op-root from the
823
* delete-half's parent directory. */
824
delete_half_parent_abspath =
825
svn_dirent_dirname(delete_op_root_abspath, iterpool);
826
if (strcmp(delete_op_root_abspath,
827
delete_half_parent_abspath) != 0)
829
const char *parent_delete_op_root_abspath;
831
cmt_err = svn_error_trace(
832
svn_wc__node_get_deleted_ancestor(
833
&parent_delete_op_root_abspath,
834
ctx->wc_ctx, delete_half_parent_abspath,
835
iterpool, iterpool));
839
if (parent_delete_op_root_abspath)
841
(svn_hash_gets(committables->by_path,
842
parent_delete_op_root_abspath)
847
if (!found_delete_half)
849
cmt_err = svn_error_createf(
850
SVN_ERR_ILLEGAL_TARGET, NULL,
851
_("Cannot commit '%s' because it was moved from "
852
"'%s' which is not part of the commit; both "
853
"sides of the move must be committed together"),
854
svn_dirent_local_style(item->path, iterpool),
855
svn_dirent_local_style(delete_op_root_abspath,
862
if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
864
const char *moved_to_abspath;
865
const char *copy_op_root_abspath;
867
cmt_err = svn_error_trace(svn_wc__node_was_moved_away(
869
©_op_root_abspath,
870
ctx->wc_ctx, item->path,
871
iterpool, iterpool));
875
if (moved_to_abspath && copy_op_root_abspath &&
876
strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
877
svn_hash_gets(committables->by_path, copy_op_root_abspath)
880
cmt_err = svn_error_createf(
881
SVN_ERR_ILLEGAL_TARGET, NULL,
882
_("Cannot commit '%s' because it was moved to '%s' "
883
"which is not part of the commit; both sides of "
884
"the move must be committed together"),
885
svn_dirent_local_style(item->path, iterpool),
886
svn_dirent_local_style(copy_op_root_abspath,
1359
893
/* Go get a log message. If an error occurs, or no log message is
1360
894
specified, abort the operation. */
1361
895
if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))