41
41
#include <apr_lib.h>
43
#include "svn_private_config.h"
43
44
#include "svn_hash.h"
44
45
#include "svn_client.h"
46
#include "private/svn_client_mtcc.h"
45
47
#include "svn_cmdline.h"
46
48
#include "svn_config.h"
47
49
#include "svn_error.h"
48
50
#include "svn_path.h"
49
51
#include "svn_pools.h"
50
52
#include "svn_props.h"
52
53
#include "svn_string.h"
53
54
#include "svn_subst.h"
54
55
#include "svn_utf.h"
55
56
#include "svn_version.h"
57
58
#include "private/svn_cmdline_private.h"
58
#include "private/svn_ra_private.h"
59
#include "private/svn_string_private.h"
60
59
#include "private/svn_subr_private.h"
62
#include "svn_private_config.h"
64
static void handle_error(svn_error_t *err, apr_pool_t *pool)
67
svn_handle_error2(err, stderr, FALSE, "svnmucc: ");
70
svn_pool_destroy(pool);
75
init(const char *application)
78
const svn_version_checklist_t checklist[] = {
79
{"svn_client", svn_client_version},
80
{"svn_subr", svn_subr_version},
81
{"svn_ra", svn_ra_version},
61
/* Version compatibility check */
63
check_lib_versions(void)
65
static const svn_version_checklist_t checklist[] =
67
{ "svn_client", svn_client_version },
68
{ "svn_subr", svn_subr_version },
69
{ "svn_ra", svn_ra_version },
84
72
SVN_VERSION_DEFINE(my_version);
86
if (svn_cmdline_init(application, stderr))
89
err = svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
91
handle_error(err, NULL);
93
return apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
97
open_tmp_file(apr_file_t **fp,
101
/* Open a unique file; use APR_DELONCLOSE. */
102
return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close,
107
create_ra_callbacks(svn_ra_callbacks2_t **callbacks,
108
const char *username,
109
const char *password,
110
const char *config_dir,
111
svn_config_t *cfg_config,
112
svn_boolean_t non_interactive,
113
svn_boolean_t trust_server_cert,
114
svn_boolean_t no_auth_cache,
117
SVN_ERR(svn_ra_create_callbacks(callbacks, pool));
119
SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton,
121
username, password, config_dir,
124
cfg_config, NULL, NULL, pool));
126
(*callbacks)->open_tmp_file = open_tmp_file;
74
return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
133
77
static svn_error_t *
134
78
commit_callback(const svn_commit_info_t *commit_info,
163
OP_PROPSET /* only for files for which no other operation is
164
occuring; directories are OP_OPEN with non-empty
167
svn_node_kind_t kind; /* to copy, mkdir, put or set revprops */
168
svn_revnum_t rev; /* to copy, valid for add and replace */
169
const char *url; /* to copy, valid for add and replace */
170
const char *src_file; /* for put, the source file for contents */
171
apr_hash_t *children; /* const char *path -> struct operation * */
172
apr_hash_t *prop_mods; /* const char *prop_name ->
173
const svn_string_t *prop_value */
174
apr_array_header_t *prop_dels; /* const char *prop_name deletions */
175
void *baton; /* as returned by the commit editor */
179
/* An iterator (for use via apr_table_do) which sets node properties.
180
REC is a pointer to a struct driver_state. */
182
change_props(const svn_delta_editor_t *editor,
184
struct operation *child,
187
apr_pool_t *iterpool = svn_pool_create(pool);
189
if (child->prop_dels)
192
for (i = 0; i < child->prop_dels->nelts; i++)
194
const char *prop_name;
196
svn_pool_clear(iterpool);
197
prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *);
198
if (child->kind == svn_node_dir)
199
SVN_ERR(editor->change_dir_prop(baton, prop_name,
202
SVN_ERR(editor->change_file_prop(baton, prop_name,
206
if (apr_hash_count(child->prop_mods))
208
apr_hash_index_t *hi;
209
for (hi = apr_hash_first(pool, child->prop_mods);
210
hi; hi = apr_hash_next(hi))
212
const char *propname = svn__apr_hash_index_key(hi);
213
const svn_string_t *val = svn__apr_hash_index_val(hi);
215
svn_pool_clear(iterpool);
216
if (child->kind == svn_node_dir)
217
SVN_ERR(editor->change_dir_prop(baton, propname, val, iterpool));
219
SVN_ERR(editor->change_file_prop(baton, propname, val, iterpool));
223
svn_pool_destroy(iterpool);
228
/* Drive EDITOR to affect the change represented by OPERATION. HEAD
229
is the last-known youngest revision in the repository. */
231
drive(struct operation *operation,
233
const svn_delta_editor_t *editor,
236
apr_pool_t *subpool = svn_pool_create(pool);
237
apr_hash_index_t *hi;
239
for (hi = apr_hash_first(pool, operation->children);
240
hi; hi = apr_hash_next(hi))
242
const char *key = svn__apr_hash_index_key(hi);
243
struct operation *child = svn__apr_hash_index_val(hi);
244
void *file_baton = NULL;
246
svn_pool_clear(subpool);
248
/* Deletes and replacements are simple -- delete something. */
249
if (child->operation == OP_DELETE || child->operation == OP_REPLACE)
251
SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool));
253
/* Opens could be for directories or files. */
254
if (child->operation == OP_OPEN || child->operation == OP_PROPSET)
256
if (child->kind == svn_node_dir)
258
SVN_ERR(editor->open_directory(key, operation->baton, head,
259
subpool, &child->baton));
263
SVN_ERR(editor->open_file(key, operation->baton, head,
264
subpool, &file_baton));
267
/* Adds and replacements could also be for directories or files. */
268
if (child->operation == OP_ADD || child->operation == OP_REPLACE)
270
if (child->kind == svn_node_dir)
272
SVN_ERR(editor->add_directory(key, operation->baton,
273
child->url, child->rev,
274
subpool, &child->baton));
278
SVN_ERR(editor->add_file(key, operation->baton, child->url,
279
child->rev, subpool, &file_baton));
282
/* If there's a source file and an open file baton, we get to
283
change textual contents. */
284
if ((child->src_file) && (file_baton))
286
svn_txdelta_window_handler_t handler;
288
svn_stream_t *contents;
290
SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool,
291
&handler, &handler_baton));
292
if (strcmp(child->src_file, "-") != 0)
294
SVN_ERR(svn_stream_open_readonly(&contents, child->src_file,
299
SVN_ERR(svn_stream_for_stdin(&contents, pool));
301
SVN_ERR(svn_txdelta_send_stream(contents, handler,
302
handler_baton, NULL, pool));
304
/* If we opened a file, we need to apply outstanding propmods,
308
if (child->kind == svn_node_file)
310
SVN_ERR(change_props(editor, file_baton, child, subpool));
312
SVN_ERR(editor->close_file(file_baton, NULL, subpool));
314
/* If we opened, added, or replaced a directory, we need to
315
recurse, apply outstanding propmods, and then close it. */
316
if ((child->kind == svn_node_dir)
317
&& child->operation != OP_DELETE)
319
SVN_ERR(change_props(editor, child->baton, child, subpool));
321
SVN_ERR(drive(child, head, editor, subpool));
323
SVN_ERR(editor->close_directory(child->baton, subpool));
326
svn_pool_destroy(subpool);
331
/* Find the operation associated with PATH, which is a single-path
332
component representing a child of the path represented by
333
OPERATION. If no such child operation exists, create a new one of
335
static struct operation *
336
get_operation(const char *path,
337
struct operation *operation,
340
struct operation *child = svn_hash_gets(operation->children, path);
343
child = apr_pcalloc(pool, sizeof(*child));
344
child->children = apr_hash_make(pool);
345
child->operation = OP_OPEN;
346
child->rev = SVN_INVALID_REVNUM;
347
child->kind = svn_node_dir;
348
child->prop_mods = apr_hash_make(pool);
349
child->prop_dels = apr_array_make(pool, 1, sizeof(const char *));
350
svn_hash_sets(operation->children, path, child);
355
101
/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */
356
102
static const char *
357
103
subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool)
359
105
return svn_uri_skip_ancestor(anchor, url, pool);
362
/* Add PATH to the operations tree rooted at OPERATION, creating any
363
intermediate nodes that are required. Here's what's expected for
366
ACTION URL REV SRC-FILE PROPNAME
367
------------ ----- ------- -------- --------
368
ACTION_MKDIR NULL invalid NULL NULL
369
ACTION_CP valid valid NULL NULL
370
ACTION_PUT NULL invalid valid NULL
371
ACTION_RM NULL invalid NULL NULL
372
ACTION_PROPSET valid invalid NULL valid
373
ACTION_PROPDEL valid invalid NULL valid
375
Node type information is obtained for any copy source (to determine
376
whether to create a file or directory) and for any deleted path (to
377
ensure it exists since svn_delta_editor_t->delete_entry doesn't
378
return an error on non-existent nodes). */
380
build(action_code_t action,
384
const char *prop_name,
385
const svn_string_t *prop_value,
386
const char *src_file,
389
svn_ra_session_t *session,
390
struct operation *operation,
393
apr_array_header_t *path_bits = svn_path_decompose(path, pool);
394
const char *path_so_far = "";
395
const char *copy_src = NULL;
396
svn_revnum_t copy_rev = SVN_INVALID_REVNUM;
399
/* Look for any previous operations we've recognized for PATH. If
400
any of PATH's ancestors have not yet been traversed, we'll be
401
creating OP_OPEN operations for them as we walk down PATH's path
403
for (i = 0; i < path_bits->nelts; ++i)
405
const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *);
406
path_so_far = svn_relpath_join(path_so_far, path_bit, pool);
407
operation = get_operation(path_so_far, operation, pool);
409
/* If we cross a replace- or add-with-history, remember the
410
source of those things in case we need to lookup the node kind
411
of one of their children. And if this isn't such a copy,
412
but we've already seen one in of our parent paths, we just need
413
to extend that copy source path by our current path
416
&& SVN_IS_VALID_REVNUM(operation->rev)
417
&& (operation->operation == OP_REPLACE
418
|| operation->operation == OP_ADD))
420
copy_src = subtract_anchor(anchor, operation->url, pool);
421
copy_rev = operation->rev;
425
copy_src = svn_relpath_join(copy_src, path_bit, pool);
429
/* Handle property changes. */
432
if (operation->operation == OP_DELETE)
433
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
434
"cannot set properties on a location being"
435
" deleted ('%s')", path);
436
/* If we're not adding this thing ourselves, check for existence. */
437
if (! ((operation->operation == OP_ADD) ||
438
(operation->operation == OP_REPLACE)))
440
SVN_ERR(svn_ra_check_path(session,
441
copy_src ? copy_src : path,
442
copy_src ? copy_rev : head,
443
&operation->kind, pool));
444
if (operation->kind == svn_node_none)
445
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
446
"propset: '%s' not found", path);
447
else if ((operation->kind == svn_node_file)
448
&& (operation->operation == OP_OPEN))
449
operation->operation = OP_PROPSET;
452
APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name;
454
svn_hash_sets(operation->prop_mods, prop_name, prop_value);
456
operation->rev = rev;
460
/* We won't fuss about multiple operations on the same path in the
463
- the prior operation was, in fact, a no-op (open)
464
- the prior operation was a propset placeholder
465
- the prior operation was a deletion
467
Note: while the operation structure certainly supports the
468
ability to do a copy of a file followed by a put of new contents
469
for the file, we don't let that happen (yet).
471
if (operation->operation != OP_OPEN
472
&& operation->operation != OP_PROPSET
473
&& operation->operation != OP_DELETE)
474
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
475
"unsupported multiple operations on '%s'", path);
477
/* For deletions, we validate that there's actually something to
478
delete. If this is a deletion of the child of a copied
479
directory, we need to remember to look in the copy source tree to
480
verify that this thing actually exists. */
481
if (action == ACTION_RM)
483
operation->operation = OP_DELETE;
484
SVN_ERR(svn_ra_check_path(session,
485
copy_src ? copy_src : path,
486
copy_src ? copy_rev : head,
487
&operation->kind, pool));
488
if (operation->kind == svn_node_none)
490
if (copy_src && strcmp(path, copy_src))
491
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
492
"'%s' (from '%s:%ld') not found",
493
path, copy_src, copy_rev);
495
return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found",
499
/* Handle copy operations (which can be adds or replacements). */
500
else if (action == ACTION_CP)
503
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
504
"Copy source revision cannot be younger "
505
"than base revision");
506
operation->operation =
507
operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
508
if (operation->operation == OP_ADD)
510
/* There is a bug in the current version of mod_dav_svn
511
which incorrectly replaces existing directories.
512
Therefore we need to check if the target exists
513
and raise an error here. */
514
SVN_ERR(svn_ra_check_path(session,
515
copy_src ? copy_src : path,
516
copy_src ? copy_rev : head,
517
&operation->kind, pool));
518
if (operation->kind != svn_node_none)
520
if (copy_src && strcmp(path, copy_src))
521
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
522
"'%s' (from '%s:%ld') already exists",
523
path, copy_src, copy_rev);
525
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
526
"'%s' already exists", path);
529
SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool),
530
rev, &operation->kind, pool));
531
if (operation->kind == svn_node_none)
532
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
534
subtract_anchor(anchor, url, pool));
535
operation->url = url;
536
operation->rev = rev;
538
/* Handle mkdir operations (which can be adds or replacements). */
539
else if (action == ACTION_MKDIR)
541
operation->operation =
542
operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD;
543
operation->kind = svn_node_dir;
545
/* Handle put operations (which can be adds, replacements, or opens). */
546
else if (action == ACTION_PUT)
548
if (operation->operation == OP_DELETE)
550
operation->operation = OP_REPLACE;
554
SVN_ERR(svn_ra_check_path(session,
555
copy_src ? copy_src : path,
556
copy_src ? copy_rev : head,
557
&operation->kind, pool));
558
if (operation->kind == svn_node_file)
559
operation->operation = OP_OPEN;
560
else if (operation->kind == svn_node_none)
561
operation->operation = OP_ADD;
563
return svn_error_createf(SVN_ERR_BAD_URL, NULL,
564
"'%s' is not a file", path);
566
operation->kind = svn_node_file;
567
operation->src_file = src_file;
571
/* We shouldn't get here. */
572
SVN_ERR_MALFUNCTION();
579
110
action_code_t action;
597
128
const svn_string_t *prop_value;
602
svn_ra_session_t *session;
607
fetch_base_func(const char **filename,
610
svn_revnum_t base_revision,
611
apr_pool_t *result_pool,
612
apr_pool_t *scratch_pool)
614
struct fetch_baton *fb = baton;
615
svn_stream_t *fstream;
618
if (! SVN_IS_VALID_REVNUM(base_revision))
619
base_revision = fb->head;
621
SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
622
svn_io_file_del_on_pool_cleanup,
623
result_pool, scratch_pool));
625
err = svn_ra_get_file(fb->session, path, base_revision, fstream, NULL, NULL,
627
if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
629
svn_error_clear(err);
630
SVN_ERR(svn_stream_close(fstream));
636
return svn_error_trace(err);
638
SVN_ERR(svn_stream_close(fstream));
644
fetch_props_func(apr_hash_t **props,
647
svn_revnum_t base_revision,
648
apr_pool_t *result_pool,
649
apr_pool_t *scratch_pool)
651
struct fetch_baton *fb = baton;
652
svn_node_kind_t node_kind;
654
if (! SVN_IS_VALID_REVNUM(base_revision))
655
base_revision = fb->head;
657
SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, &node_kind,
660
if (node_kind == svn_node_file)
662
SVN_ERR(svn_ra_get_file(fb->session, path, base_revision, NULL, NULL,
663
props, result_pool));
665
else if (node_kind == svn_node_dir)
667
apr_array_header_t *tmp_props;
669
SVN_ERR(svn_ra_get_dir2(fb->session, NULL, NULL, props, path,
670
base_revision, 0 /* Dirent fields */,
672
tmp_props = svn_prop_hash_to_array(*props, result_pool);
673
SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
675
*props = svn_prop_array_to_hash(tmp_props, result_pool);
679
*props = apr_hash_make(result_pool);
686
fetch_kind_func(svn_node_kind_t *kind,
689
svn_revnum_t base_revision,
690
apr_pool_t *scratch_pool)
692
struct fetch_baton *fb = baton;
694
if (! SVN_IS_VALID_REVNUM(base_revision))
695
base_revision = fb->head;
697
SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, kind,
703
static svn_delta_shim_callbacks_t *
704
get_shim_callbacks(svn_ra_session_t *session,
706
apr_pool_t *result_pool)
708
svn_delta_shim_callbacks_t *callbacks =
709
svn_delta_shim_callbacks_default(result_pool);
710
struct fetch_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
712
fb->session = session;
715
callbacks->fetch_props_func = fetch_props_func;
716
callbacks->fetch_kind_func = fetch_kind_func;
717
callbacks->fetch_base_func = fetch_base_func;
718
callbacks->fetch_baton = fb;
723
131
static svn_error_t *
724
132
execute(const apr_array_header_t *actions,
725
133
const char *anchor,
726
134
apr_hash_t *revprops,
727
const char *username,
728
const char *password,
729
const char *config_dir,
730
const apr_array_header_t *config_options,
731
svn_boolean_t non_interactive,
732
svn_boolean_t trust_server_cert,
733
svn_boolean_t no_auth_cache,
734
135
svn_revnum_t base_revision,
136
svn_client_ctx_t *ctx,
735
137
apr_pool_t *pool)
737
svn_ra_session_t *session;
738
svn_ra_session_t *aux_session;
739
const char *repos_root;
741
const svn_delta_editor_t *editor;
742
svn_ra_callbacks2_t *ra_callbacks;
744
struct operation root;
139
svn_client__mtcc_t *mtcc;
140
apr_pool_t *iterpool = svn_pool_create(pool);
745
141
svn_error_t *err;
747
svn_config_t *cfg_config;
750
SVN_ERR(svn_config_get_config(&config, config_dir, pool));
751
SVN_ERR(svn_cmdline__apply_config_options(config, config_options,
752
"svnmucc: ", "--config-option"));
753
cfg_config = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
755
if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
757
svn_string_t *msg = svn_string_create("", pool);
759
/* If we can do so, try to pop up $EDITOR to fetch a log message. */
762
return svn_error_create
763
(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
764
_("Cannot invoke editor to get log message "
765
"when non-interactive"));
769
SVN_ERR(svn_cmdline__edit_string_externally(
770
&msg, NULL, NULL, "", msg, "svnmucc-commit", config,
771
TRUE, NULL, apr_hash_pool_get(revprops)));
774
svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, msg);
777
SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir,
778
cfg_config, non_interactive, trust_server_cert,
779
no_auth_cache, pool));
780
SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks,
781
NULL, config, pool));
782
/* Open, then reparent to avoid AUTHZ errors when opening the reposroot */
783
SVN_ERR(svn_ra_open4(&aux_session, NULL, anchor, NULL, ra_callbacks,
784
NULL, config, pool));
785
SVN_ERR(svn_ra_get_repos_root2(aux_session, &repos_root, pool));
786
SVN_ERR(svn_ra_reparent(aux_session, repos_root, pool));
787
SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool));
789
/* Reparent to ANCHOR's dir, if ANCHOR is not a directory. */
791
svn_node_kind_t kind;
793
SVN_ERR(svn_ra_check_path(aux_session,
794
svn_uri_skip_ancestor(repos_root, anchor, pool),
796
if (kind != svn_node_dir)
798
anchor = svn_uri_dirname(anchor, pool);
799
SVN_ERR(svn_ra_reparent(session, anchor, pool));
803
if (SVN_IS_VALID_REVNUM(base_revision))
805
if (base_revision > head)
806
return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
807
"No such revision %ld (youngest is %ld)",
808
base_revision, head);
809
head = base_revision;
812
memset(&root, 0, sizeof(root));
813
root.children = apr_hash_make(pool);
814
root.operation = OP_OPEN;
815
root.kind = svn_node_dir; /* For setting properties */
816
root.prop_mods = apr_hash_make(pool);
817
root.prop_dels = apr_array_make(pool, 1, sizeof(const char *));
144
SVN_ERR(svn_client__mtcc_create(&mtcc, anchor,
145
SVN_IS_VALID_REVNUM(base_revision)
147
: SVN_INVALID_REVNUM,
148
ctx, pool, iterpool));
819
150
for (i = 0; i < actions->nelts; ++i)
821
152
struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
822
153
const char *path1, *path2;
154
svn_node_kind_t kind;
156
svn_pool_clear(iterpool);
823
158
switch (action->action)
826
161
path1 = subtract_anchor(anchor, action->path[0], pool);
827
162
path2 = subtract_anchor(anchor, action->path[1], pool);
828
SVN_ERR(build(ACTION_RM, path1, NULL,
829
SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
830
session, &root, pool));
831
SVN_ERR(build(ACTION_CP, path2, action->path[0],
832
head, NULL, NULL, NULL, head, anchor,
833
session, &root, pool));
163
SVN_ERR(svn_client__mtcc_add_move(path1, path2, mtcc, iterpool));
166
path1 = subtract_anchor(anchor, action->path[0], pool);
836
167
path2 = subtract_anchor(anchor, action->path[1], pool);
837
if (action->rev == SVN_INVALID_REVNUM)
839
SVN_ERR(build(ACTION_CP, path2, action->path[0],
840
action->rev, NULL, NULL, NULL, head, anchor,
841
session, &root, pool));
168
SVN_ERR(svn_client__mtcc_add_copy(path1, action->rev, path2,
844
172
path1 = subtract_anchor(anchor, action->path[0], pool);
845
SVN_ERR(build(ACTION_RM, path1, NULL,
846
SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
847
session, &root, pool));
173
SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, iterpool));
849
175
case ACTION_MKDIR:
850
176
path1 = subtract_anchor(anchor, action->path[0], pool);
851
SVN_ERR(build(ACTION_MKDIR, path1, action->path[0],
852
SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor,
853
session, &root, pool));
177
SVN_ERR(svn_client__mtcc_add_mkdir(path1, mtcc, iterpool));
856
180
path1 = subtract_anchor(anchor, action->path[0], pool);
857
SVN_ERR(build(ACTION_PUT, path1, action->path[0],
858
SVN_INVALID_REVNUM, NULL, NULL, action->path[1],
859
head, anchor, session, &root, pool));
181
SVN_ERR(svn_client__mtcc_check_path(&kind, path1, TRUE, mtcc, pool));
183
if (kind == svn_node_dir)
185
SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, pool));
186
kind = svn_node_none;
192
if (strcmp(action->path[1], "-") != 0)
193
SVN_ERR(svn_stream_open_readonly(&src, action->path[1],
196
SVN_ERR(svn_stream_for_stdin(&src, pool));
199
if (kind == svn_node_file)
200
SVN_ERR(svn_client__mtcc_add_update_file(path1, src, NULL,
203
else if (kind == svn_node_none)
204
SVN_ERR(svn_client__mtcc_add_add_file(path1, src, NULL,
861
208
case ACTION_PROPSET:
862
209
case ACTION_PROPDEL:
863
210
path1 = subtract_anchor(anchor, action->path[0], pool);
864
SVN_ERR(build(action->action, path1, action->path[0],
866
action->prop_name, action->prop_value,
867
NULL, head, anchor, session, &root, pool));
211
SVN_ERR(svn_client__mtcc_add_propset(path1, action->prop_name,
212
action->prop_value, FALSE,
869
215
case ACTION_PROPSETF:
1008
/* Ensure that the REVPROPS hash contains a command-line-provided log
1009
message, if any, and that there was but one source of such a thing
1010
provided on that command-line. */
352
/* Obtain the log message from multiple sources, producing an error
353
if there are multiple sources. Store the result in *FINAL_MESSAGE. */
1011
354
static svn_error_t *
1012
sanitize_log_sources(apr_hash_t *revprops,
355
sanitize_log_sources(const char **final_message,
1013
356
const char *message,
1014
svn_stringbuf_t *filedata)
357
apr_hash_t *revprops,
358
svn_stringbuf_t *filedata,
359
apr_pool_t *result_pool,
360
apr_pool_t *scratch_pool)
1016
apr_pool_t *hash_pool = apr_hash_pool_get(revprops);
364
*final_message = NULL;
1018
365
/* If we already have a log message in the revprop hash, then just
1019
366
make sure the user didn't try to also use -m or -F. Otherwise,
1020
367
we need to consult -m or -F to find a log message, if any. */
1021
if (svn_hash_gets(revprops, SVN_PROP_REVISION_LOG))
368
msg = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG);
1023
371
if (filedata || message)
1024
372
return mutually_exclusive_logs_error();
374
*final_message = apr_pstrdup(result_pool, msg->data);
376
/* Will be re-added by libsvn_client */
377
svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL);
1026
379
else if (filedata)
1029
382
return mutually_exclusive_logs_error();
1031
SVN_ERR(svn_utf_cstring_to_utf8(&message, filedata->data, hash_pool));
1032
svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
1033
svn_stringbuf__morph_into_string(filedata));
384
*final_message = apr_pstrdup(result_pool, filedata->data);
1035
386
else if (message)
1037
svn_hash_sets(revprops, SVN_PROP_REVISION_LOG,
1038
svn_string_create(message, hash_pool));
388
*final_message = apr_pstrdup(result_pool, message);
1041
391
return SVN_NO_ERROR;
1045
main(int argc, const char **argv)
1047
apr_pool_t *pool = init("svnmucc");
394
/* Baton for log_message_func */
395
struct log_message_baton
397
svn_boolean_t non_interactive;
398
const char *log_message;
399
svn_client_ctx_t *ctx;
402
/* Implements svn_client_get_commit_log3_t */
404
log_message_func(const char **log_msg,
405
const char **tmp_file,
406
const apr_array_header_t *commit_items,
410
struct log_message_baton *lmb = baton;
414
if (lmb->log_message)
416
svn_string_t *message = svn_string_create(lmb->log_message, pool);
418
SVN_ERR_W(svn_subst_translate_string2(&message, NULL, NULL,
419
message, NULL, FALSE,
421
_("Error normalizing log message to internal format"));
423
*log_msg = message->data;
428
if (lmb->non_interactive)
430
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
431
_("Cannot invoke editor to get log message "
432
"when non-interactive"));
436
svn_string_t *msg = svn_string_create("", pool);
438
SVN_ERR(svn_cmdline__edit_string_externally(
439
&msg, NULL, NULL, "", msg, "svnmucc-commit",
440
lmb->ctx->config, TRUE, NULL, pool));
442
if (msg && msg->data)
443
*log_msg = msg->data;
452
* On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
453
* either return an error to be displayed, or set *EXIT_CODE to non-zero and
454
* return SVN_NO_ERROR.
457
sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
1048
459
apr_array_header_t *actions = apr_array_make(pool, 1,
1049
460
sizeof(struct action *));
1050
461
const char *anchor = NULL;
1170
593
case force_interactive_opt:
1171
594
force_interactive = TRUE;
1173
case trust_server_cert_opt:
1174
trust_server_cert = TRUE;
596
case trust_server_cert_opt: /* backward compat */
597
trust_unknown_ca = TRUE;
599
case trust_server_cert_failures_opt:
600
SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
601
SVN_ERR(svn_cmdline__parse_trust_options(
605
&trust_not_yet_valid,
606
&trust_other_failure,
1176
609
case config_dir_opt:
1177
err = svn_utf_cstring_to_utf8(&config_dir, arg, pool);
1179
handle_error(err, pool);
610
SVN_ERR(svn_utf_cstring_to_utf8(&config_dir, arg, pool));
1181
612
case config_inline_opt:
1182
err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool);
1184
handle_error(err, pool);
1186
err = svn_cmdline__parse_config_option(config_options, opt_arg,
1189
handle_error(err, pool);
613
SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
614
SVN_ERR(svn_cmdline__parse_config_option(config_options, opt_arg,
1191
618
case no_auth_cache_opt:
1192
619
no_auth_cache = TRUE;
1194
621
case version_opt:
1195
SVN_INT_ERR(display_version(opts, pool));
1200
usage(pool, EXIT_SUCCESS);
639
SVN_ERR(display_version(pool));
1205
643
if (non_interactive && force_interactive)
1207
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1208
_("--non-interactive and --force-interactive "
1209
"are mutually exclusive"));
1210
return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
645
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
646
_("--non-interactive and --force-interactive "
647
"are mutually exclusive"));
1213
650
non_interactive = !svn_cmdline__be_interactive(non_interactive,
1214
651
force_interactive);
1216
if (trust_server_cert && !non_interactive)
653
if (!non_interactive)
1218
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1219
_("--trust-server-cert requires "
1220
"--non-interactive"));
1221
return svn_cmdline_handle_exit_error(err, pool, "svnmucc: ");
655
if (trust_unknown_ca || trust_cn_mismatch || trust_expired
656
|| trust_not_yet_valid || trust_other_failure)
657
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
658
_("--trust-server-cert-failures requires "
659
"--non-interactive"));
1224
/* Make sure we have a log message to use. */
1225
err = sanitize_log_sources(revprops, message, filedata);
1227
handle_error(err, pool);
1229
662
/* Copy the rest of our command-line arguments to an array,
1230
663
UTF-8-ing them along the way. */
1231
664
action_args = apr_array_make(pool, opts->argc, sizeof(const char *));
1232
665
while (opts->ind < opts->argc)
1234
667
const char *arg = opts->argv[opts->ind++];
1235
if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args,
1238
handle_error(err, pool);
668
SVN_ERR(svn_utf_cstring_to_utf8(&APR_ARRAY_PUSH(action_args,
1241
673
/* If there are extra arguments in a supplementary file, tack those
1434
914
anchor = svn_uri_get_longest_ancestor(anchor, url, pool);
1435
915
if (!anchor || !anchor[0])
1436
handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
1437
"URLs in the action list do not "
1438
"share a common ancestor"),
916
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
917
"URLs in the action list do not "
918
"share a common ancestor");
1442
921
if ((++i == action_args->nelts) && (j + 1 < num_url_args))
922
return insufficient();
1445
925
APR_ARRAY_PUSH(actions, struct action *) = action;
1448
928
if (! actions->nelts)
1449
usage(pool, EXIT_FAILURE);
930
*exit_code = EXIT_FAILURE;
1451
if ((err = execute(actions, anchor, revprops, username, password,
1452
config_dir, config_options, non_interactive,
1453
trust_server_cert, no_auth_cache, base_revision, pool)))
935
if ((err = execute(actions, anchor, revprops, base_revision, ctx, pool)))
1455
937
if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1456
938
err = svn_error_quick_wrap(err,
1457
939
_("Authentication failed and interactive"
1458
940
" prompting is disabled; see the"
1459
941
" --force-interactive option"));
1460
handle_error(err, pool);
1463
/* Ensure that stdout is flushed, so the user will see all results. */
1464
svn_error_clear(svn_cmdline_fflush(stdout));
949
main(int argc, const char *argv[])
952
int exit_code = EXIT_SUCCESS;
955
/* Initialize the app. */
956
if (svn_cmdline_init("svnmucc", stderr) != EXIT_SUCCESS)
959
/* Create our top-level pool. Use a separate mutexless allocator,
960
* given this application is single threaded.
962
pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
964
err = sub_main(&exit_code, argc, argv, pool);
966
/* Flush stdout and report if it fails. It would be flushed on exit anyway
967
but this makes sure that output is not silently lost if it fails. */
968
err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
972
exit_code = EXIT_FAILURE;
973
svn_cmdline_handle_exit_error(err, NULL, "svnmucc: ");
1466
976
svn_pool_destroy(pool);
1467
return EXIT_SUCCESS;